<?xml version="1.0" encoding="utf-8"?>
  <rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
    xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
    xmlns:georss="http://www.georss.org/georss"
    xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
  >
    <channel>
      <title>Adebola Adesina</title>
      <link>www.aadesina.com/</link>
      <atom:link href="www.aadesina.com/rss/FrontEnd.xml" rel="self" type="application/rss+xml" />
      <description>Posts from Adebola's Adesina</description>
      <language>en-us</language>
      <copyright>Adebola Adesina 2024</copyright>
      <docs>https://www.rssboard.org/rss-specification</docs>
      <pubDate>Sat, 27 Jan 2024 01:06:11 GMT</pubDate>
      <lastBuildDate>Sat, 27 Jan 2024 01:06:11 GMT</lastBuildDate>
      <managingEditor>rss@aadesina.com (Adebola Adesina)</managingEditor>
      <webMaster>rss@aadesina.com (Adebola Adesina)</webMaster>
      <image>
        <url>www.aadesina.com/asset/dr3-no-background-2.png</url>
        <title>Adebola Adesina</title>
        <link>www.aadesina.com/</link>
      </image>
      
      <item>
        <title>Testing for front end category</title>
        <link>www.aadesina.com/posts/testing-for-front-end-category</link>
        <author>rss@aadesina.com (Adebola Adesina)</author>
        <description><![CDATA[<h1>Test Post</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<blockquote>
<p>"One of Jhey’s main mantras is to make learning fun. In this article, he shows you ways to level up your skills by bringing your ideas to life, and not forgetting that <a href="https://www.smashingmagazine.com/2020/11/playfulness-code-supercharge-fun-learning/">you can be playful with code</a>. With that mindset, every idea is bound to become an opportunity to try something new." – Vitaly Friedman</p>
</blockquote>

<p>Naming things is hard, right? "Flippy Snaps" was the best thing I could come up with 😂  I saw an effect like this on TV one evening and made a note to myself to make something similar. Although this isn't something I'd look to drop on a website any time soon, it's a neat little challenge to make. It fits in with my whole stance on "<a href="https://www.smashingmagazine.com/2020/11/playfulness-code-supercharge-fun-learning/">Playfulness in Code</a>" to learn. Anyway, a few days later, I sat down at the keyboard, and a couple of hours later I had this.</p>
<blockquote><p><a href="https://twitter.com/Dr3_147/status/1457830342413455369">Check out the related tweet!</a></p></blockquote>
<p>My final demo is a React app, but we don't need to dig into using React to explain the mechanics of making this work. We will create the React app once we’ve established how to make things work.</p>
<h2>Audio</h2>
<p><audio src="https://aadesina.com/media/audio/test-audio.mp3" controls></audio></p>
<p>Oh. And here's an unordered list:</p>
<ul>
<li>Cool</li>
<li>Beans</li>
<li>For</li>
</ul>
<p>And how about numbered lists:</p>
<ol>
<li>Cool thing</li>
<li>Awesome thing</li>
<li>Nice thing</li>
<li>Ended things</li>
</ol>
<p>Check lists?</p>
<ul>
<li> Cool</li>
<li> Cool</li>
<li> Cool</li>
<li> Cool</li>
</ul>
<aside><div>
  Before we get started. It’s worth noting that performance of this demo is affected by the grid size and the demos are best viewed in Chromium based browsers.
</div></aside>
<p>Let's start by creating a grid. Let's say we want a 10 by 10 grid. That's 100 cells (This is why React is handy for something like this). Each cell is going to consist of an element that contains the front and back for a flippable card.</p>
<pre><code><span>&lt;</span><span>div</span><span> </span><span>class</span><span>=</span><span>"</span><span>flippy-snap</span><span>"</span><span>&gt;</span><span>
</span><span>  </span><span>&lt;!-- 100 of these --&gt;</span><span>
</span><span>  </span><span>&lt;</span><span>div</span><span> </span><span>class</span><span>=</span><span>"</span><span>flippy-snap__card flippy-card</span><span>"</span><span>&gt;</span><span>
</span><span>    </span><span>&lt;</span><span>div</span><span> </span><span>class</span><span>=</span><span>"</span><span>flippy-card__front</span><span>"</span><span>&gt;</span><span>&lt;/</span><span>div</span><span>&gt;</span><span>
</span><span>    </span><span>&lt;</span><span>div</span><span> </span><span>class</span><span>=</span><span>"</span><span>flippy-card__rear</span><span>"</span><span>&gt;</span><span>&lt;/</span><span>div</span><span>&gt;</span><span>
</span><span>  </span><span>&lt;/</span><span>div</span><span>&gt;</span><span>
</span><span></span><span>&lt;/</span><span>div</span><span>&gt;</span></code></pre>
<p>The styles for our grid are quite straightforward. We can use <code>display: grid</code> and use a custom property for the grid size. Here we are defaulting to <code>10</code>.</p>
<pre><code><span>console</span><span>.</span><span>info</span><span>(</span><span>true</span><span> </span><span>===</span><span> </span><span>true</span><span>)</span></code></pre>
<aside><span></span><div>
  Before we get started. It’s worth noting that performance of this demo is affected by the grid size and the demos are best viewed in Chromium based browsers.
</div></aside>
<aside><span></span><div>
  Before we get started. It’s worth noting that performance of this demo is affected by the grid size and the demos are best viewed in Chromium based browsers.
</div></aside>
<pre><code><span>.flippy-snap</span><span> </span><span>{</span><span>
</span><span>  </span><span>display</span><span>:</span><span> grid</span><span>;</span><span>
</span><span>  </span><span>grid-gap</span><span>:</span><span> </span><span>1</span><span>px</span><span>;</span><span>
</span><span>  </span><span>grid-template-columns</span><span>:</span><span> </span><span>repeat</span><span>(</span><span>var</span><span>(</span><span>--grid-size</span><span>,</span><span> </span><span>10</span><span>)</span><span>,</span><span> </span><span>1</span><span>fr</span><span>)</span><span>;</span><span>
</span><span>  </span><span>grid-template-rows</span><span>:</span><span> </span><span>repeat</span><span>(</span><span>var</span><span>(</span><span>--grid-size</span><span>,</span><span> </span><span>10</span><span>)</span><span>,</span><span> </span><span>1</span><span>fr</span><span>)</span><span>;</span><span>
</span><span></span><span>}</span></code></pre>
<p>We won’t use <code>grid-gap</code> in the final demo, but, it’s good for seeing the cells easier whilst developing.</p>
<blockquote><p>Check out the <a href="https://codepen.io/dr3apap/XWayzVK">demo pen</a> by dr3apap (<a href="https://codepen.io/dr3apap">@dr3apap</a>) over on <a href="https://codepen.io/dr3apap/XWayzVK">CodePen</a>.</p></blockquote>
<p>Next, we need to style the sides to our cards and display images. We can do this by leveraging inline CSS custom properties. Let's start by updating the markup. We need each card to know its <code>x</code> and <code>y</code> position in the grid.</p>
<pre><code><span>&lt;</span><span>div</span><span> </span><span>class</span><span>=</span><span>"</span><span>flippy-snap</span><span>"</span><span>&gt;</span><span>
</span><span>  </span><span>&lt;</span><span>div</span><span> </span><span>class</span><span>=</span><span>"</span><span>flippy-snap__card flippy-card</span><span>"</span><span> </span><span>style</span><span>=</span><span>"</span><span>--x</span><span>:</span><span> </span><span>0</span><span>;</span><span> </span><span>--y</span><span>:</span><span> </span><span>0</span><span>;</span><span>"</span><span>&gt;</span><span>
</span><span>    </span><span>&lt;</span><span>div</span><span> </span><span>class</span><span>=</span><span>"</span><span>flippy-card__front</span><span>"</span><span>&gt;</span><span>&lt;/</span><span>div</span><span>&gt;</span><span>
</span><span>    </span><span>&lt;</span><span>div</span><span> </span><span>class</span><span>=</span><span>"</span><span>flippy-card__rear</span><span>"</span><span>&gt;</span><span>&lt;/</span><span>div</span><span>&gt;</span><span>
</span><span>  </span><span>&lt;/</span><span>div</span><span>&gt;</span><span>
</span><span>  </span><span>&lt;</span><span>div</span><span> </span><span>class</span><span>=</span><span>"</span><span>flippy-snap__card flippy-card</span><span>"</span><span> </span><span>style</span><span>=</span><span>"</span><span>--x</span><span>:</span><span> </span><span>1</span><span>;</span><span> </span><span>--y</span><span>:</span><span> </span><span>0</span><span>;</span><span>"</span><span>&gt;</span><span>
</span><span>    </span><span>&lt;</span><span>div</span><span> </span><span>class</span><span>=</span><span>"</span><span>flippy-card__front</span><span>"</span><span>&gt;</span><span>&lt;/</span><span>div</span><span>&gt;</span><span>
</span><span>    </span><span>&lt;</span><span>div</span><span> </span><span>class</span><span>=</span><span>"</span><span>flippy-card__rear</span><span>"</span><span>&gt;</span><span>&lt;/</span><span>div</span><span>&gt;</span><span>
</span><span>  </span><span>&lt;/</span><span>div</span><span>&gt;</span><span>
</span><span>  </span><span>&lt;!-- Other cards --&gt;</span><span>
</span><span></span><span>&lt;/</span><span>div</span><span>&gt;</span></code></pre>
<p>For the demo, I'm using <code>Pug</code> to generate this for me. You can see the compiled HTML by clicking “View Compiled HTML” in the demo.</p>
<pre><code><span>-</span><span> </span><span>const</span><span> </span><span>GRID_SIZE</span><span> </span><span>=</span><span> </span><span>10</span><span>
</span><span></span><span>-</span><span> </span><span>const</span><span> </span><span>COUNT</span><span> </span><span>=</span><span> </span><span>Math</span><span>.</span><span>pow</span><span>(</span><span>GRID_SIZE</span><span>,</span><span> </span><span>2</span><span>)</span><span>
</span><span></span><span>.flippy-snap</span><span>
</span><span>  </span><span>-</span><span> </span><span>for</span><span>(</span><span>let</span><span> f </span><span>=</span><span> </span><span>0</span><span>;</span><span> f </span><span>&lt;</span><span> </span><span>COUNT</span><span>;</span><span> f</span><span>++</span><span>)</span><span>
</span><span>    </span><span>-</span><span> </span><span>const</span><span> x </span><span>=</span><span> f </span><span>%</span><span> </span><span>GRID_SIZE</span><span>
</span><span>    </span><span>-</span><span> </span><span>const</span><span> y </span><span>=</span><span> </span><span>Math</span><span>.</span><span>floor</span><span>(</span><span>f </span><span>/</span><span> </span><span>GRID_SIZE</span><span>)</span><span>
</span><span>    </span><span>.flippy-snap__card</span><span>.flippy-card</span><span>(</span><span>style</span><span>=</span><span>`</span><span>--x: </span><span>${</span><span>x</span><span>}</span><span>; --y: </span><span>${</span><span>y</span><span>}</span><span>;</span><span>`</span><span>)</span><span>
</span><span>      </span><span>.flippy-card__front</span><span>
</span><span>      </span><span>.flippy-card__rear</span></code></pre>
<p>Then we need some styles.</p>
<pre><code><span>.flippy-card</span><span> </span><span>{</span><span>
</span><span>  </span><span>--current-image</span><span>:</span><span> </span><span>url</span><span>(</span><span>"https://random-image.com/768"</span><span>)</span><span>;</span><span>
</span><span>  </span><span>--next-image</span><span>:</span><span> </span><span>url</span><span>(</span><span>"https://random-image.com/124"</span><span>)</span><span>;</span><span>
</span><span>  </span><span>height</span><span>:</span><span> </span><span>100</span><span>%</span><span>;</span><span>
</span><span>  </span><span>width</span><span>:</span><span> </span><span>100</span><span>%</span><span>;</span><span>
</span><span>  </span><span>position</span><span>:</span><span> relative</span><span>;</span><span>
</span><span></span><span>}</span><span>
</span><span></span><span>.flippy-card__front</span><span>,</span><span>
</span><span></span><span>.flippy-card__rear</span><span> </span><span>{</span><span>
</span><span>  </span><span>position</span><span>:</span><span> absolute</span><span>;</span><span>
</span><span>  </span><span>height</span><span>:</span><span> </span><span>100</span><span>%</span><span>;</span><span>
</span><span>  </span><span>width</span><span>:</span><span> </span><span>100</span><span>%</span><span>;</span><span>
</span><span>  </span><span>backface-visibility</span><span>:</span><span> hidden</span><span>;</span><span>
</span><span>  </span><span>background-image</span><span>:</span><span> </span><span>var</span><span>(</span><span>--current-image</span><span>)</span><span>;</span><span>
</span><span>  </span><span>background-position</span><span>:</span><span> </span><span>calc</span><span>(</span><span>var</span><span>(</span><span>--x</span><span>,</span><span> </span><span>0</span><span>)</span><span> </span><span>*</span><span> </span><span>-100</span><span>%</span><span>)</span><span> </span><span>calc</span><span>(</span><span>var</span><span>(</span><span>--y</span><span>,</span><span> </span><span>0</span><span>)</span><span> </span><span>*</span><span> </span><span>-100</span><span>%</span><span>)</span><span>;</span><span>
</span><span>  </span><span>background-size</span><span>:</span><span> </span><span>calc</span><span>(</span><span>var</span><span>(</span><span>--grid-size</span><span>,</span><span> </span><span>10</span><span>)</span><span> </span><span>*</span><span> </span><span>100</span><span>%</span><span>)</span><span>;</span><span>
</span><span></span><span>}</span><span>
</span><span></span><span>.flippy-card__rear</span><span> </span><span>{</span><span>
</span><span>  </span><span>background-image</span><span>:</span><span> </span><span>var</span><span>(</span><span>--next-image</span><span>)</span><span>;</span><span>
</span><span>  </span><span>transform</span><span>:</span><span> </span><span>rotateY</span><span>(</span><span>180</span><span>deg</span><span>)</span><span> </span><span>rotate</span><span>(</span><span>180</span><span>deg</span><span>)</span><span>;</span><span>
</span><span></span><span>}</span></code></pre>
<p>The rear of the card gets its position using a combination of rotations via <code>transform</code>. But, the interesting part is how we show the image part for each card. In this demo, we are using a custom property to define the URLs for two images. And then we set those as the <code>background-image</code> for each card face. But the trick is how we define the <code>background-size</code> and <code>background-position</code>. Using the custom properties <code>--x</code> and <code>--y</code> we multiply the value by <code>-100%</code>. And then we set the <code>background-size</code> to <code>--grid-size</code> multiplied by <code>100%</code>. This gives displays the correct part of the image for a given card.</p>
<blockquote><p>Check out the <a href="https://codepen.io/dr3apap/YzxBKLP">demo pen</a> by dr3apap (<a href="https://codepen.io/dr3apap">@dr3apap</a>) over on <a href="https://codepen.io/dr3apap/YzxBKLP">CodePen</a>.</p></blockquote>
<p>You may have noticed that we had <code>--current-image</code> and <code>--next-image</code>. But, currently, there is no way to see the next image. For that, we need a way to flip our cards. We can use another custom property for this. Let's introduce a <code>--count</code> property and set a <code>transform</code> for our cards.</p>
<pre><code><span>.flippy-snap</span><span> </span><span>{</span><span>
</span><span>  </span><span>--count</span><span>:</span><span> </span><span>0</span><span>;</span><span>
</span><span>  </span><span>perspective</span><span>:</span><span> </span><span>50</span><span>vmin</span><span>;</span><span>
</span><span></span><span>}</span><span>
</span><span></span><span>.flippy-card</span><span> </span><span>{</span><span>
</span><span>  </span><span>transform</span><span>:</span><span> </span><span>rotateX</span><span>(</span><span>calc</span><span>(</span><span>var</span><span>(</span><span>--count</span><span>)</span><span> </span><span>*</span><span> </span><span>-180</span><span>deg</span><span>)</span><span>)</span><span>;</span><span>
</span><span>  </span><span>transition</span><span>:</span><span> transform </span><span>0.25</span><span>s</span><span>;</span><span>
</span><span>  </span><span>transform-style</span><span>:</span><span> preserve-3</span><span>d</span><span>;</span><span>
</span><span></span><span>}</span></code></pre>
<p>We can set the <code>--count</code> property on the containing element. Scoping means all the cards can pick up that value and use it to <code>transform</code> their rotation on the x-axis. We also need to set <code>transform-style: preserve-3d</code> so that we see the back of the cards. Setting a <code>perspective</code> gives us that 3D perspective.</p>
<p>This demo lets you update the <code>--count</code> property value so you can see the effect it has.</p>
<blockquote><p>Check out the <a href="https://codepen.io/dr3apap/YzxBKLP">demo pen</a> by dr3apap (<a href="https://codepen.io/dr3apap">@dr3apap</a>) over on <a href="https://codepen.io/dr3apap/YzxBKLP">CodePen</a>.</p></blockquote>
<h2>What next?</h2>
<p>At this point, you could wrap it up there and set a simple click handler that increments <code>--count</code> by one on each click.</p>
<pre><code><span>const</span><span> </span><span>SNAP</span><span> </span><span>=</span><span> </span><span>document</span><span>.</span><span>querySelector</span><span>(</span><span>'.flippy-snap'</span><span>)</span><span>
</span><span></span><span>let</span><span> count </span><span>=</span><span> </span><span>0</span><span>
</span><span></span><span>const</span><span> </span><span>UPDATE</span><span> </span><span>=</span><span> </span><span>(</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>SNAP</span><span>.</span><span>style</span><span>.</span><span>setProperty</span><span>(</span><span>'--count'</span><span>,</span><span> count</span><span>++</span><span>)</span><span>
</span><span></span><span>SNAP</span><span>.</span><span>addEventListener</span><span>(</span><span>'click'</span><span>,</span><span> </span><span>UPDATE</span><span>)</span></code></pre>
<p>Remove the <code>grid-gap</code> and you'd get this. Click the snap to flip it.</p>
<blockquote><p>Check out the <a href="https://codepen.io/dr3apap/YzxBKLP">demo pen</a> by dr3apap (<a href="https://codepen.io/dr3apap">@dr3apap</a>) over on <a href="https://codepen.io/dr3apap/YzxBKLP">CodePen</a>.</p></blockquote>
<hr />
<p>Now we have the basic mechanics worked out, it's time to turn this into a React app. There's a bit to break down here.</p>
<pre><code><span>const</span><span> </span><span>App</span><span> </span><span>=</span><span> </span><span>(</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>  </span><span>const</span><span> </span><span>[</span><span>snaps</span><span>,</span><span> setSnaps</span><span>]</span><span> </span><span>=</span><span> </span><span>useState</span><span>(</span><span>[</span><span>]</span><span>)</span><span>
</span><span>  </span><span>const</span><span> </span><span>[</span><span>disabled</span><span>,</span><span> setDisabled</span><span>]</span><span> </span><span>=</span><span> </span><span>useState</span><span>(</span><span>true</span><span>)</span><span>
</span><span>  </span><span>const</span><span> </span><span>[</span><span>gridSize</span><span>,</span><span> setGridSize</span><span>]</span><span> </span><span>=</span><span> </span><span>useState</span><span>(</span><span>9</span><span>)</span><span>
</span><span>  </span><span>const</span><span> snapRef </span><span>=</span><span> </span><span>useRef</span><span>(</span><span>null</span><span>)</span><span>
</span>
<span>  </span><span>const</span><span> </span><span>grabPic</span><span> </span><span>=</span><span> </span><span>async</span><span> </span><span>(</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>    </span><span>const</span><span> pic </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(</span><span>'https://source.unsplash.com/random/1000x1000'</span><span>)</span><span>
</span><span>    </span><span>return</span><span> pic</span><span>.</span><span>url</span><span>
</span><span>  </span><span>}</span><span>
</span>
<span>  </span><span>useEffect</span><span>(</span><span>(</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>    </span><span>const</span><span> </span><span>setup</span><span> </span><span>=</span><span> </span><span>async</span><span> </span><span>(</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>      </span><span>const</span><span> url </span><span>=</span><span> </span><span>await</span><span> </span><span>grabPic</span><span>(</span><span>)</span><span>
</span><span>      </span><span>const</span><span> nextUrl </span><span>=</span><span> </span><span>await</span><span> </span><span>grabPic</span><span>(</span><span>)</span><span>
</span><span>      </span><span>setSnaps</span><span>(</span><span>[</span><span>url</span><span>,</span><span> nextUrl</span><span>]</span><span>)</span><span>
</span><span>      </span><span>setDisabled</span><span>(</span><span>false</span><span>)</span><span>
</span><span>    </span><span>}</span><span>
</span><span>    </span><span>setup</span><span>(</span><span>)</span><span>
</span><span>  </span><span>}</span><span>,</span><span> </span><span>[</span><span>]</span><span>)</span><span>
</span>
<span>  </span><span>const</span><span> </span><span>setNewImage</span><span> </span><span>=</span><span> </span><span>async</span><span> </span><span>count</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>    </span><span>const</span><span> newSnap </span><span>=</span><span> </span><span>await</span><span> </span><span>grabPic</span><span>(</span><span>)</span><span>
</span><span>    </span><span>setSnaps</span><span>(</span><span>
</span><span>      count</span><span>.</span><span>current</span><span> </span><span>%</span><span> </span><span>2</span><span> </span><span>!==</span><span> </span><span>0</span><span> </span><span>?</span><span> </span><span>[</span><span>newSnap</span><span>,</span><span> snaps</span><span>[</span><span>1</span><span>]</span><span>]</span><span> </span><span>:</span><span> </span><span>[</span><span>snaps</span><span>[</span><span>0</span><span>]</span><span>,</span><span> newSnap</span><span>]</span><span>
</span><span>    </span><span>)</span><span>
</span><span>    </span><span>setDisabled</span><span>(</span><span>false</span><span>)</span><span>
</span><span>  </span><span>}</span><span>
</span>
<span>  </span><span>const</span><span> </span><span>onFlip</span><span> </span><span>=</span><span> </span><span>async</span><span> </span><span>count</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>    </span><span>setDisabled</span><span>(</span><span>true</span><span>)</span><span>
</span><span>    </span><span>setNewImage</span><span>(</span><span>count</span><span>)</span><span>
</span><span>  </span><span>}</span><span>
</span>
<span>  </span><span>if</span><span> </span><span>(</span><span>snaps</span><span>.</span><span>length</span><span> </span><span>!==</span><span> </span><span>2</span><span>)</span><span> </span><span>return</span><span> </span><span>&lt;</span><span>h1</span><span> </span><span>className</span><span>=</span><span>"</span><span>loader</span><span>"</span><span>&gt;</span><span>Loading...</span><span>&lt;/</span><span>h1</span><span>&gt;</span><span>
</span>
<span>  </span><span>return</span><span> </span><span>(</span><span>
</span><span>    </span><span>&lt;</span><span>FlippySnap</span><span>
</span><span>      </span><span>gridSize</span><span>=</span><span>{</span><span>gridSize</span><span>}</span><span>
</span><span>      </span><span>disabled</span><span>=</span><span>{</span><span>disabled</span><span>}</span><span>
</span><span>      </span><span>snaps</span><span>=</span><span>{</span><span>snaps</span><span>}</span><span>
</span><span>      </span><span>onFlip</span><span>=</span><span>{</span><span>onFlip</span><span>}</span><span>
</span><span>      </span><span>snapRef</span><span>=</span><span>{</span><span>snapRef</span><span>}</span><span>
</span><span>    </span><span>/&gt;</span><span>
</span><span>  </span><span>)</span><span>
</span><span></span><span>}</span></code></pre>
<p>Our <code>App</code> component handles grabbing images and passing them to our <code>FlippySnap</code> component. That's the bulk of what's happening here.  For this demo, we're grabbing images from Unsplash.</p>
<pre><code><span>const</span><span> </span><span>grabPic</span><span> </span><span>=</span><span> </span><span>async</span><span> </span><span>(</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>  </span><span>const</span><span> pic </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(</span><span>'https://source.unsplash.com/random/1000x1000'</span><span>)</span><span>
</span><span>  </span><span>return</span><span> pic</span><span>.</span><span>url</span><span>
</span><span></span><span>}</span><span>
</span>
<span></span><span>// Initial effect grabs two snaps to be used by FlippySnap</span><span>
</span><span></span><span>useEffect</span><span>(</span><span>(</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>  </span><span>const</span><span> </span><span>setup</span><span> </span><span>=</span><span> </span><span>async</span><span> </span><span>(</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>    </span><span>const</span><span> url </span><span>=</span><span> </span><span>await</span><span> </span><span>grabPic</span><span>(</span><span>)</span><span>
</span><span>    </span><span>const</span><span> nextUrl </span><span>=</span><span> </span><span>await</span><span> </span><span>grabPic</span><span>(</span><span>)</span><span>
</span><span>    </span><span>setSnaps</span><span>(</span><span>[</span><span>url</span><span>,</span><span> nextUrl</span><span>]</span><span>)</span><span>
</span><span>    </span><span>setDisabled</span><span>(</span><span>false</span><span>)</span><span>
</span><span>  </span><span>}</span><span>
</span><span>  </span><span>setup</span><span>(</span><span>)</span><span>
</span><span></span><span>}</span><span>,</span><span> </span><span>[</span><span>]</span><span>)</span></code></pre>
<h2>Cool Beans</h2>
<p>If there aren't two snaps to show, then we show a "Loading..." message.</p>
<pre><code><span>if</span><span> </span><span>(</span><span>snaps</span><span>.</span><span>length</span><span> </span><span>!==</span><span> </span><span>2</span><span>)</span><span> </span><span>return</span><span> </span><span>&lt;</span><span>h1 className</span><span>=</span><span>"loader"</span><span>&gt;</span><span>Loading</span><span>...</span><span>&lt;</span><span>/</span><span>h1</span><span>&gt;</span></code></pre>
<p>If we are grabbing a new image, we need to disable <code>FlippySnap</code> so we can't spam click it.</p>
<pre><code><span>&lt;</span><span>FlippySnap</span><span>
</span><span>  gridSize</span><span>=</span><span>{</span><span>gridSize</span><span>}</span><span>
</span><span>  disabled</span><span>=</span><span>{</span><span>disabled</span><span>}</span><span> </span><span>// Toggle a "disabled" prop to stop spam clicks</span><span>
</span><span>  snaps</span><span>=</span><span>{</span><span>snaps</span><span>}</span><span>
</span><span>  onFlip</span><span>=</span><span>{</span><span>onFlip</span><span>}</span><span>
</span><span>  snapRef</span><span>=</span><span>{</span><span>snapRef</span><span>}</span><span>
</span><span></span><span>/</span><span>&gt;</span></code></pre>
<p>We're letting <code>App</code> dictate the snaps that get displayed by <code>FlippySnap</code> and in which order. On each flip, we grab a new image, and depending on how many times we've flipped, we set the correct snaps. The alternative would be to set the snaps and let the component figure out the order.</p>
<pre><code><span>const</span><span> </span><span>setNewImage</span><span> </span><span>=</span><span> </span><span>async</span><span> </span><span>count</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>  </span><span>const</span><span> newSnap </span><span>=</span><span> </span><span>await</span><span> </span><span>grabPic</span><span>(</span><span>)</span><span> </span><span>// Grab the snap</span><span>
</span><span>  </span><span>setSnaps</span><span>(</span><span>
</span><span>    count</span><span>.</span><span>current</span><span> </span><span>%</span><span> </span><span>2</span><span> </span><span>!==</span><span> </span><span>0</span><span> </span><span>?</span><span> </span><span>[</span><span>newSnap</span><span>,</span><span> snaps</span><span>[</span><span>1</span><span>]</span><span>]</span><span> </span><span>:</span><span> </span><span>[</span><span>snaps</span><span>[</span><span>0</span><span>]</span><span>,</span><span> newSnap</span><span>]</span><span>
</span><span>  </span><span>)</span><span> </span><span>// Set the snaps based on the current "count" which we get from FlippySnap</span><span>
</span><span>  </span><span>setDisabled</span><span>(</span><span>false</span><span>)</span><span> </span><span>// Enable clicks again</span><span>
</span><span></span><span>}</span><span>
</span>
<span></span><span>const</span><span> </span><span>onFlip</span><span> </span><span>=</span><span> </span><span>async</span><span> </span><span>count</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>  </span><span>setDisabled</span><span>(</span><span>true</span><span>)</span><span> </span><span>// Disable so we can't spam click</span><span>
</span><span>  </span><span>setNewImage</span><span>(</span><span>count</span><span>)</span><span> </span><span>// Grab a new snap to display</span><span>
</span><span></span><span>}</span></code></pre>
<h3>Beanies</h3>
<p>How might <code>FlippySnap</code> look? There isn’t much to it at all!</p>
<pre><code><span>const</span><span> </span><span>FlippySnap</span><span> </span><span>=</span><span> </span><span>(</span><span>{</span><span> disabled</span><span>,</span><span> gridSize</span><span>,</span><span> onFlip</span><span>,</span><span> snaps </span><span>}</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>  </span><span>const</span><span> </span><span>CELL_COUNT</span><span> </span><span>=</span><span> </span><span>Math</span><span>.</span><span>pow</span><span>(</span><span>gridSize</span><span>,</span><span> </span><span>2</span><span>)</span><span>
</span><span>  </span><span>const</span><span> count </span><span>=</span><span> </span><span>useRef</span><span>(</span><span>0</span><span>)</span><span>
</span>
<span>  </span><span>const</span><span> </span><span>flip</span><span> </span><span>=</span><span> </span><span>e</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>    </span><span>if</span><span> </span><span>(</span><span>disabled</span><span>)</span><span> </span><span>return</span><span>
</span><span>    count</span><span>.</span><span>current</span><span> </span><span>=</span><span> count</span><span>.</span><span>current</span><span> </span><span>+</span><span> </span><span>1</span><span>
</span><span>    </span><span>if</span><span> </span><span>(</span><span>onFlip</span><span>)</span><span> </span><span>onFlip</span><span>(</span><span>count</span><span>)</span><span>
</span><span>  </span><span>}</span><span>
</span>
<span>  </span><span>const</span><span> buttonStyle </span><span>=</span><span> </span><span>{</span><span>
</span><span>    </span><span>"--grid-size"</span><span>:</span><span> gridSize</span><span>,</span><span>
</span><span>    </span><span>"--count"</span><span>:</span><span> count</span><span>.</span><span>current</span><span>,</span><span>
</span><span>    </span><span>"--current-image"</span><span>:</span><span> </span><span>`</span><span>url('</span><span>${</span><span>snaps</span><span>[</span><span>0</span><span>]</span><span>}</span><span>')</span><span>`</span><span>,</span><span>
</span><span>    </span><span>"--next-image"</span><span>:</span><span> </span><span>`</span><span>url('</span><span>${</span><span>snaps</span><span>[</span><span>1</span><span>]</span><span>}</span><span>')</span><span>`</span><span>,</span><span>
</span><span>  </span><span>}</span><span>
</span>
<span>  </span><span>return</span><span> </span><span>(</span><span>
</span><span>    </span><span>&lt;</span><span>button
</span><span>      className</span><span>=</span><span>"flippy-snap"</span><span>
</span><span>      ref</span><span>=</span><span>{</span><span>containerRef</span><span>}</span><span>
</span><span>      style</span><span>=</span><span>{</span><span>buttonStyle</span><span>}</span><span>&gt;</span><span>
</span><span>      </span><span>{</span><span>new</span><span> </span><span>Array</span><span>(</span><span>CELL_COUNT</span><span>)</span><span>.</span><span>fill</span><span>(</span><span>)</span><span>.</span><span>map</span><span>(</span><span>(</span><span>cell</span><span>,</span><span> index</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>        </span><span>const</span><span> x </span><span>=</span><span> index </span><span>%</span><span> gridSize
</span><span>        </span><span>const</span><span> y </span><span>=</span><span> </span><span>Math</span><span>.</span><span>floor</span><span>(</span><span>index </span><span>/</span><span> gridSize</span><span>)</span><span>
</span><span>        </span><span>const</span><span> cellStyle </span><span>=</span><span> </span><span>{</span><span>
</span><span>          </span><span>"--x"</span><span>:</span><span> x</span><span>,</span><span>
</span><span>          </span><span>"--y"</span><span>:</span><span> y</span><span>,</span><span>
</span><span>        </span><span>}</span><span>
</span><span>        </span><span>return</span><span> </span><span>(</span><span>
</span><span>          </span><span>&lt;</span><span>span
</span><span>            key</span><span>=</span><span>{</span><span>index</span><span>}</span><span>
</span><span>            className</span><span>=</span><span>"flippy-card"</span><span>
</span><span>            style</span><span>=</span><span>{</span><span>cellStyle</span><span>}</span><span>&gt;</span><span>
</span><span>            </span><span>&lt;</span><span>span className</span><span>=</span><span>"flippy-card__front"</span><span>&gt;</span><span>&lt;</span><span>/</span><span>span</span><span>&gt;</span><span>
</span><span>            </span><span>&lt;</span><span>span className</span><span>=</span><span>"flippy-card__rear"</span><span>&gt;</span><span>&lt;</span><span>/</span><span>span</span><span>&gt;</span><span>
</span><span>          </span><span>&lt;</span><span>/</span><span>span</span><span>&gt;</span><span>
</span><span>        </span><span>)</span><span>
</span><span>      </span><span>}</span><span>}</span><span>
</span><span>    </span><span>&lt;</span><span>/</span><span>button</span><span>&gt;</span><span>
</span><span>  </span><span>)</span><span>
</span><span></span><span>}</span></code></pre>
<p>The component handles rendering all the cards and setting the inline custom properties. The <code>onClick</code> handler for the container increments the <code>count</code>. It also triggers the <code>onFlip</code> callback. If the state is currently <code>disabled</code>, it does nothing. That flip of the <code>disabled</code> state and grabbing a new snap triggers the flip when the component re-renders.</p>
<blockquote><p>Check out the <a href="https://codepen.io/dr3apap/YzxBKLP">demo pen</a> by dr3apap (<a href="https://codepen.io/dr3apap">@dr3apap</a>) over on <a href="https://codepen.io/dr3apap/YzxBKLP">CodePen</a>.</p></blockquote>
<hr />
<p>We have a React component that will now flip through images for as long as we want to keep requesting new ones. But, that flip transition is a bit boring. To spice it up, we're going to make use of <a href="https://greensock.com/">GreenSock</a> and it's utilities. In particular, the "<a href="https://greensock.com/docs/v3/GSAP/UtilityMethods/distribute">distribute</a>" utility. This will allow us to distribute the delay of flipping our cards in a grid like burst from wherever we click. To do this, we're going to use GreenSock to animate the <code>--count</code> value on each card.</p>
<p>It's worth noting that we have a choice here. We could opt to apply the styles with GreenSock. Instead of animating the <code>--count</code> property value, we could animate <code>rotateX</code>. We could do this based on the <code>count</code> ref we have. And this also goes for any other things we choose to animate with GreenSock in this article. It's down to preference and use case. You may feel that updating the custom property value makes sense. The benefit being that you don't need to update any JavaScript to get a different styled behavior. We could change the CSS to use <code>rotateY</code> for example.</p>
<p>Our updated <code>flip</code> function could look like this:</p>
<pre><code><span>const</span><span> </span><span>flip</span><span> </span><span>=</span><span> </span><span>e</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>  </span><span>if</span><span> </span><span>(</span><span>disabled</span><span>)</span><span> </span><span>return</span><span>
</span><span>  </span><span>const</span><span> x </span><span>=</span><span> </span><span>parseInt</span><span>(</span><span>e</span><span>.</span><span>target</span><span>.</span><span>parentNode</span><span>.</span><span>getAttribute</span><span>(</span><span>'data-snap-x'</span><span>)</span><span>,</span><span> </span><span>10</span><span>)</span><span>
</span><span>  </span><span>const</span><span> y </span><span>=</span><span> </span><span>parseInt</span><span>(</span><span>e</span><span>.</span><span>target</span><span>.</span><span>parentNode</span><span>.</span><span>getAttribute</span><span>(</span><span>'data-snap-y'</span><span>)</span><span>,</span><span> </span><span>10</span><span>)</span><span>
</span><span>  count</span><span>.</span><span>current</span><span> </span><span>=</span><span> count</span><span>.</span><span>current</span><span> </span><span>+</span><span> </span><span>1</span><span>
</span><span>  gsap</span><span>.</span><span>to</span><span>(</span><span>containerRef</span><span>.</span><span>current</span><span>.</span><span>querySelectorAll</span><span>(</span><span>'.flippy-card'</span><span>)</span><span>,</span><span> </span><span>{</span><span>
</span><span>    </span><span>'--count'</span><span>:</span><span> count</span><span>.</span><span>current</span><span>,</span><span>
</span><span>    </span><span>delay</span><span>:</span><span> gsap</span><span>.</span><span>utils</span><span>.</span><span>distribute</span><span>(</span><span>{</span><span>
</span><span>      </span><span>from</span><span>:</span><span> </span><span>[</span><span>x </span><span>/</span><span> gridSize</span><span>,</span><span> y </span><span>/</span><span> gridSize</span><span>]</span><span>,</span><span>
</span><span>      </span><span>amount</span><span>:</span><span> gridSize </span><span>/</span><span> </span><span>20</span><span>,</span><span>
</span><span>      </span><span>base</span><span>:</span><span> </span><span>0</span><span>,</span><span>
</span><span>      </span><span>grid</span><span>:</span><span> </span><span>[</span><span>gridSize</span><span>,</span><span> gridSize</span><span>]</span><span>,</span><span>
</span><span>      </span><span>ease</span><span>:</span><span> </span><span>'power1.inOut'</span><span>,</span><span>
</span><span>    </span><span>}</span><span>)</span><span>,</span><span>
</span><span>    </span><span>duration</span><span>:</span><span> </span><span>0.2</span><span>,</span><span>
</span><span>    </span><span>onComplete</span><span>:</span><span> </span><span>(</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>      </span><span>// At this point update the images</span><span>
</span><span>      </span><span>if</span><span> </span><span>(</span><span>onFlip</span><span>)</span><span> </span><span>onFlip</span><span>(</span><span>count</span><span>)</span><span>
</span><span>    </span><span>}</span><span>,</span><span>
</span><span>  </span><span>}</span><span>)</span><span>
</span><span></span><span>}</span></code></pre>
<p>Note how we're getting an <code>x</code> and <code>y</code> value by reading attributes of the clicked card. For this demo, we've opted for adding some <code>data</code> attributes to each card. These attributes communicate a cards position in the grid. We’re also using a new <code>ref</code> called <code>containerRef</code>. This is so we reference only the cards for a <code>FlippySnap</code> instance when using GreenSock.</p>
<pre><code><span>{</span><span>new</span><span> </span><span>Array</span><span>(</span><span>CELL_COUNT</span><span>)</span><span>.</span><span>fill</span><span>(</span><span>)</span><span>.</span><span>map</span><span>(</span><span>(</span><span>cell</span><span>,</span><span> index</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>  </span><span>const</span><span> x </span><span>=</span><span> index </span><span>%</span><span> gridSize
</span><span>  </span><span>const</span><span> y </span><span>=</span><span> </span><span>Math</span><span>.</span><span>floor</span><span>(</span><span>index </span><span>/</span><span> gridSize</span><span>)</span><span>
</span><span>  </span><span>const</span><span> cellStyle </span><span>=</span><span> </span><span>{</span><span>
</span><span>    </span><span>"--x"</span><span>:</span><span> x</span><span>,</span><span>
</span><span>    </span><span>"--y"</span><span>:</span><span> y</span><span>,</span><span>
</span><span>  </span><span>}</span><span>
</span><span>  </span><span>return</span><span> </span><span>(</span><span>
</span><span>    </span><span>&lt;</span><span>span
</span><span>      className</span><span>=</span><span>"flippy-card"</span><span>
</span><span>      data</span><span>-</span><span>snap</span><span>-</span><span>x</span><span>=</span><span>{</span><span>x</span><span>}</span><span>
</span><span>      data</span><span>-</span><span>snap</span><span>-</span><span>y</span><span>=</span><span>{</span><span>y</span><span>}</span><span>
</span><span>      style</span><span>=</span><span>{</span><span>cellStyle</span><span>}</span><span>&gt;</span><span>
</span><span>      </span><span>&lt;</span><span>span className</span><span>=</span><span>"flippy-card__front"</span><span>&gt;</span><span>&lt;</span><span>/</span><span>span</span><span>&gt;</span><span>
</span><span>      </span><span>&lt;</span><span>span className</span><span>=</span><span>"flippy-card__rear"</span><span>&gt;</span><span>&lt;</span><span>/</span><span>span</span><span>&gt;</span><span>
</span><span>    </span><span>&lt;</span><span>/</span><span>span</span><span>&gt;</span><span>
</span><span>  </span><span>)</span><span>
</span><span></span><span>}</span><span>)</span><span>}</span></code></pre>
<p>Once we get those <code>x</code> and <code>y</code> values, we can make use of them in our animation. Using <code>gsap.to</code> we want to animate the <code>--count</code> custom property for every <code>.flippy-card</code> that's a child of <code>containerRef</code>.</p>
<p>To distribute the delay from where we click, we set the value of <code>delay</code> to use <code>gsap.utils.distribute</code>. The <code>from</code> value of the <code>distribute</code> function takes an Array containing ratios along the x and y axis. To get this, we divide <code>x</code> and <code>y</code> by <code>gridSize</code>. The <code>base</code> value is the initial value. For this, we want <code>0</code> delay on the card we click. The <code>amount</code> is the largest value. We've gone for <code>gridSize / 20</code> but you could experiment with different values. Something based on the <code>gridSize</code> is a good idea though. The <code>grid</code> value tells GreenSock the grid size to use when calculating distribution. Last but not least, the <code>ease</code> defines the ease of the <code>delay</code> distribution.</p>
<pre><code><span>gsap</span><span>.</span><span>to</span><span>(</span><span>containerRef</span><span>.</span><span>current</span><span>.</span><span>querySelectorAll</span><span>(</span><span>'.flippy-card'</span><span>)</span><span>,</span><span> </span><span>{</span><span>
</span><span>  </span><span>'--count'</span><span>:</span><span> count</span><span>.</span><span>current</span><span>,</span><span>
</span><span>  </span><span>delay</span><span>:</span><span> gsap</span><span>.</span><span>utils</span><span>.</span><span>distribute</span><span>(</span><span>{</span><span>
</span><span>    </span><span>from</span><span>:</span><span> </span><span>[</span><span>x </span><span>/</span><span> gridSize</span><span>,</span><span> y </span><span>/</span><span> gridSize</span><span>]</span><span>,</span><span>
</span><span>    </span><span>amount</span><span>:</span><span> gridSize </span><span>/</span><span> </span><span>20</span><span>,</span><span>
</span><span>    </span><span>base</span><span>:</span><span> </span><span>0</span><span>,</span><span>
</span><span>    </span><span>grid</span><span>:</span><span> </span><span>[</span><span>gridSize</span><span>,</span><span> gridSize</span><span>]</span><span>,</span><span>
</span><span>    </span><span>ease</span><span>:</span><span> </span><span>'power1.inOut'</span><span>,</span><span>
</span><span>  </span><span>}</span><span>)</span><span>,</span><span>
</span><span>  </span><span>duration</span><span>:</span><span> </span><span>0.2</span><span>,</span><span>
</span><span>  </span><span>onComplete</span><span>:</span><span> </span><span>(</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>    </span><span>// At this point update the images</span><span>
</span><span>    </span><span>if</span><span> </span><span>(</span><span>onFlip</span><span>)</span><span> </span><span>onFlip</span><span>(</span><span>count</span><span>)</span><span>
</span><span>  </span><span>}</span><span>,</span><span>
</span><span></span><span>}</span><span>)</span></code></pre>
<p>As for the rest of the animation, we are using a flip duration of <code>0.2</code> seconds. And we make use of <code>onComplete</code> to invoke our callback. We pass the flip <code>count</code> to the callback so it can use this to determine snap order. Things like the duration of the flip could get configured by passing in different <code>props</code> if we wished.</p>
<p>Putting it all together gives us this:</p>
<blockquote><p>Check out the <a href="https://codepen.io/dr3apap/YzxBKLP">demo pen</a> by dr3apap (<a href="https://codepen.io/dr3apap">@dr3apap</a>) over on <a href="https://codepen.io/dr3apap/YzxBKLP">CodePen</a>.</p></blockquote>
<p>Those that like to push things a bit might have noticed that we can still "spam" click the snap. And that's because we don't disable <code>FlippySnap</code> until GreenSock has completed. To fix this, we can use an internal ref that we toggle at the start and end of using GreenSock.</p>
<pre><code><span>const</span><span> flipping </span><span>=</span><span> </span><span>useRef</span><span>(</span><span>false</span><span>)</span><span> </span><span>// New ref to track the flipping state</span><span>
</span>
<span></span><span>const</span><span> </span><span>flip</span><span> </span><span>=</span><span> </span><span>e</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>  </span><span>if</span><span> </span><span>(</span><span>disabled </span><span>||</span><span> flipping</span><span>.</span><span>current</span><span>)</span><span> </span><span>return</span><span>
</span><span>  </span><span>const</span><span> x </span><span>=</span><span> </span><span>parseInt</span><span>(</span><span>e</span><span>.</span><span>target</span><span>.</span><span>parentNode</span><span>.</span><span>getAttribute</span><span>(</span><span>'data-snap-x'</span><span>)</span><span>,</span><span> </span><span>10</span><span>)</span><span>
</span><span>  </span><span>const</span><span> y </span><span>=</span><span> </span><span>parseInt</span><span>(</span><span>e</span><span>.</span><span>target</span><span>.</span><span>parentNode</span><span>.</span><span>getAttribute</span><span>(</span><span>'data-snap-y'</span><span>)</span><span>,</span><span> </span><span>10</span><span>)</span><span>
</span><span>  count</span><span>.</span><span>current</span><span> </span><span>=</span><span> count</span><span>.</span><span>current</span><span> </span><span>+</span><span> </span><span>1</span><span>
</span><span>  gsap</span><span>.</span><span>to</span><span>(</span><span>containerRef</span><span>.</span><span>current</span><span>.</span><span>querySelectorAll</span><span>(</span><span>'.flippy-card'</span><span>)</span><span>,</span><span> </span><span>{</span><span>
</span><span>    </span><span>'--count'</span><span>:</span><span> count</span><span>.</span><span>current</span><span>,</span><span>
</span><span>    </span><span>delay</span><span>:</span><span> gsap</span><span>.</span><span>utils</span><span>.</span><span>distribute</span><span>(</span><span>{</span><span>
</span><span>      </span><span>from</span><span>:</span><span> </span><span>[</span><span>x </span><span>/</span><span> gridSize</span><span>,</span><span> y </span><span>/</span><span> gridSize</span><span>]</span><span>,</span><span>
</span><span>      </span><span>amount</span><span>:</span><span> gridSize </span><span>/</span><span> </span><span>20</span><span>,</span><span>
</span><span>      </span><span>base</span><span>:</span><span> </span><span>0</span><span>,</span><span>
</span><span>      </span><span>grid</span><span>:</span><span> </span><span>[</span><span>gridSize</span><span>,</span><span> gridSize</span><span>]</span><span>,</span><span>
</span><span>      </span><span>ease</span><span>:</span><span> </span><span>'power1.inOut'</span><span>,</span><span>
</span><span>    </span><span>}</span><span>)</span><span>,</span><span>
</span><span>    </span><span>duration</span><span>:</span><span> </span><span>0.2</span><span>,</span><span>
</span><span>    </span><span>onStart</span><span>:</span><span> </span><span>(</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>      flipping</span><span>.</span><span>current</span><span> </span><span>=</span><span> </span><span>true</span><span>
</span><span>    </span><span>}</span><span>,</span><span>
</span><span>    </span><span>onComplete</span><span>:</span><span> </span><span>(</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>      </span><span>// At this point update the images</span><span>
</span><span>      flipping</span><span>.</span><span>current</span><span> </span><span>=</span><span> </span><span>false</span><span>
</span><span>      </span><span>if</span><span> </span><span>(</span><span>onFlip</span><span>)</span><span> </span><span>onFlip</span><span>(</span><span>count</span><span>)</span><span>
</span><span>    </span><span>}</span><span>,</span><span>
</span><span>  </span><span>}</span><span>)</span><span>
</span><span></span><span>}</span></code></pre>
<p>And now we can no longer spam click our <code>FlippySnap</code>!</p>
<blockquote><p>Check out the <a href="https://codepen.io/dr3apap/YzxBKLP">demo pen</a> by dr3apap (<a href="https://codepen.io/dr3apap">@dr3apap</a>) over on <a href="https://codepen.io/dr3apap/YzxBKLP">CodePen</a>.</p></blockquote>
<hr />
<p>Now it's time for some extra touches. At the moment, there's no visual sign that we can click our <code>FlippySnap</code>. What if when we hover, the cards raise towards us? We could use <code>onPointerOver</code> and use the "distribute" utility again.</p>
<pre><code><span>const</span><span> </span><span>indicate</span><span> </span><span>=</span><span> </span><span>e</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>  </span><span>const</span><span> x </span><span>=</span><span> </span><span>parseInt</span><span>(</span><span>e</span><span>.</span><span>currentTarget</span><span>.</span><span>getAttribute</span><span>(</span><span>'data-snap-x'</span><span>)</span><span>,</span><span> </span><span>10</span><span>)</span><span>
</span><span>  </span><span>const</span><span> y </span><span>=</span><span> </span><span>parseInt</span><span>(</span><span>e</span><span>.</span><span>currentTarget</span><span>.</span><span>getAttribute</span><span>(</span><span>'data-snap-y'</span><span>)</span><span>,</span><span> </span><span>10</span><span>)</span><span>
</span><span>  gsap</span><span>.</span><span>to</span><span>(</span><span>containerRef</span><span>.</span><span>current</span><span>.</span><span>querySelectorAll</span><span>(</span><span>'.flippy-card'</span><span>)</span><span>,</span><span> </span><span>{</span><span>
</span><span>    </span><span>'--hovered'</span><span>:</span><span> gsap</span><span>.</span><span>utils</span><span>.</span><span>distribute</span><span>(</span><span>{</span><span>
</span><span>      </span><span>from</span><span>:</span><span> </span><span>[</span><span>x </span><span>/</span><span> gridSize</span><span>,</span><span> y </span><span>/</span><span> gridSize</span><span>]</span><span>,</span><span>
</span><span>      </span><span>base</span><span>:</span><span> </span><span>0</span><span>,</span><span>
</span><span>      </span><span>amount</span><span>:</span><span> </span><span>1</span><span>,</span><span>
</span><span>      </span><span>grid</span><span>:</span><span> </span><span>[</span><span>gridSize</span><span>,</span><span> gridSize</span><span>]</span><span>,</span><span>
</span><span>      </span><span>ease</span><span>:</span><span> </span><span>'power1.inOut'</span><span>
</span><span>    </span><span>}</span><span>)</span><span>,</span><span>
</span><span>    </span><span>duration</span><span>:</span><span> </span><span>0.1</span><span>,</span><span>
</span><span>  </span><span>}</span><span>)</span><span>
</span><span></span><span>}</span></code></pre>
<p>Here, we are setting a new custom property on each card named <code>--hovered</code>. This is set to a value from <code>0</code> to <code>1</code>. Then within our CSS, we are going to update our card styles to watch for the value.</p>
<pre><code><span>.flippy-card</span><span> </span><span>{</span><span>
</span><span>  </span><span>transform</span><span>:</span><span> </span><span>translate3d</span><span>(</span><span>0</span><span>,</span><span> </span><span>0</span><span>,</span><span> </span><span>calc</span><span>(</span><span>(</span><span>1</span><span> </span><span>-</span><span> </span><span>(</span><span>var</span><span>(</span><span>--hovered</span><span>,</span><span> </span><span>1</span><span>)</span><span>)</span><span>)</span><span> </span><span>*</span><span> </span><span>5</span><span>vmin</span><span>)</span><span>)</span><span>
</span><span>              </span><span>rotateX</span><span>(</span><span>calc</span><span>(</span><span>var</span><span>(</span><span>--count</span><span>)</span><span> </span><span>*</span><span> </span><span>-180</span><span>deg</span><span>)</span><span>)</span><span>;</span><span>
</span><span></span><span>}</span></code></pre>
<p>Here we are saying a card will move on the z-axis at most <code>5vmin</code>.</p>
<p>We then apply this to each card using the <code>onPointerOver</code> prop.</p>
<pre><code><span>{</span><span>new</span><span> </span><span>Array</span><span>(</span><span>CELL_COUNT</span><span>)</span><span>.</span><span>fill</span><span>(</span><span>)</span><span>.</span><span>map</span><span>(</span><span>(</span><span>cell</span><span>,</span><span> index</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>  </span><span>const</span><span> x </span><span>=</span><span> index </span><span>%</span><span> gridSize
</span><span>  </span><span>const</span><span> y </span><span>=</span><span> </span><span>Math</span><span>.</span><span>floor</span><span>(</span><span>index </span><span>/</span><span> gridSize</span><span>)</span><span>
</span><span>  </span><span>const</span><span> cellStyle </span><span>=</span><span> </span><span>{</span><span>
</span><span>    </span><span>"--x"</span><span>:</span><span> x</span><span>,</span><span>
</span><span>    </span><span>"--y"</span><span>:</span><span> y</span><span>,</span><span>
</span><span>  </span><span>}</span><span>
</span><span>  </span><span>return</span><span> </span><span>(</span><span>
</span><span>    </span><span>&lt;</span><span>span
</span><span>      onPointerOver</span><span>=</span><span>{</span><span>indicate</span><span>}</span><span>
</span><span>      className</span><span>=</span><span>"flippy-card"</span><span>
</span><span>      data</span><span>-</span><span>snap</span><span>-</span><span>x</span><span>=</span><span>{</span><span>x</span><span>}</span><span>
</span><span>      data</span><span>-</span><span>snap</span><span>-</span><span>y</span><span>=</span><span>{</span><span>y</span><span>}</span><span>
</span><span>      style</span><span>=</span><span>{</span><span>cellStyle</span><span>}</span><span>&gt;</span><span>
</span><span>      </span><span>&lt;</span><span>span className</span><span>=</span><span>"flippy-card__front"</span><span>&gt;</span><span>&lt;</span><span>/</span><span>span</span><span>&gt;</span><span>
</span><span>      </span><span>&lt;</span><span>span className</span><span>=</span><span>"flippy-card__rear"</span><span>&gt;</span><span>&lt;</span><span>/</span><span>span</span><span>&gt;</span><span>
</span><span>    </span><span>&lt;</span><span>/</span><span>span</span><span>&gt;</span><span>
</span><span>  </span><span>)</span><span>
</span><span></span><span>}</span><span>)</span><span>}</span></code></pre>
<p>And when our pointer leaves our <code>FlippySnap</code> we want to reset our card positions.</p>
<pre><code><span>const</span><span> </span><span>reset</span><span> </span><span>=</span><span> </span><span>(</span><span>)</span><span> </span><span>=&gt;</span><span> </span><span>{</span><span>
</span><span>  gsap</span><span>.</span><span>to</span><span>(</span><span>containerRef</span><span>.</span><span>current</span><span>.</span><span>querySelectorAll</span><span>(</span><span>'.flippy-card'</span><span>)</span><span>,</span><span> </span><span>{</span><span>
</span><span>    </span><span>'--hovered'</span><span>:</span><span> </span><span>1</span><span>,</span><span>
</span><span>    </span><span>duration</span><span>:</span><span> </span><span>0.1</span><span>,</span><span>
</span><span>  </span><span>}</span><span>)</span><span>
</span><span></span><span>}</span></code></pre>
<p>And we can apply this with the <code>onPointerLeave</code> prop.</p>
<pre><code><span>&lt;</span><span>button</span><span>
</span><span>  </span><span>className</span><span>=</span><span>"</span><span>flippy-snap</span><span>"</span><span>
</span><span>  </span><span>ref</span><span>=</span><span>{</span><span>containerRef</span><span>}</span><span>
</span><span>  </span><span>onPointerLeave</span><span>=</span><span>{</span><span>reset</span><span>}</span><span>
</span><span>  </span><span>style</span><span>=</span><span>{</span><span>buttonStyle</span><span>}</span><span>
</span><span>  </span><span>onClick</span><span>=</span><span>{</span><span>flip</span><span>}</span><span>&gt;</span></code></pre>
<p>Put that all together and we get something like this. Try moving your pointer over it.</p>
<blockquote><p>Check out the <a href="https://codepen.io/dr3apap/YzxBKLP">demo pen</a> by dr3apap (<a href="https://codepen.io/dr3apap">@dr3apap</a>) over on <a href="https://codepen.io/dr3apap/YzxBKLP">CodePen</a>.</p></blockquote>
<p>What next? How about a loading indicator so we know when we our <code>App</code> is grabbing the next image? We can render a loading spinner when our <code>FlippySnap</code> is <code>disabled</code>.</p>
<pre><code><span>{</span><span>disabled </span><span>&amp;&amp;</span><span> </span><span>&lt;</span><span>span className</span><span>=</span><span>'flippy-snap__loader'</span><span>&gt;</span><span>&lt;</span><span>/</span><span>span</span><span>&gt;</span><span>}</span></code></pre>
<p>The styles for which could make a rotating circle.</p>
<pre><code><span>.flippy-snap__loader</span><span> </span><span>{</span><span>
</span><span>  </span><span>border-radius</span><span>:</span><span> </span><span>50</span><span>%</span><span>;</span><span>
</span><span>  </span><span>border</span><span>:</span><span> </span><span>6</span><span>px</span><span> solid </span><span>#fff</span><span>;</span><span>
</span><span>  </span><span>border-left-color</span><span>:</span><span> </span><span>#000</span><span>;</span><span>
</span><span>  </span><span>border-right-color</span><span>:</span><span> </span><span>#000</span><span>;</span><span>
</span><span>  </span><span>position</span><span>:</span><span> absolute</span><span>;</span><span>
</span><span>  </span><span>right</span><span>:</span><span> </span><span>10</span><span>%</span><span>;</span><span>
</span><span>  </span><span>bottom</span><span>:</span><span> </span><span>10</span><span>%</span><span>;</span><span>
</span><span>  </span><span>height</span><span>:</span><span> </span><span>8</span><span>%</span><span>;</span><span>
</span><span>  </span><span>width</span><span>:</span><span> </span><span>8</span><span>%</span><span>;</span><span>
</span><span>  </span><span>transform</span><span>:</span><span> </span><span>translate3d</span><span>(</span><span>0</span><span>,</span><span> </span><span>0</span><span>,</span><span> </span><span>5</span><span>vmin</span><span>)</span><span> </span><span>rotate</span><span>(</span><span>0</span><span>deg</span><span>)</span><span>;</span><span>
</span><span>  </span><span>animation</span><span>:</span><span> spin </span><span>1</span><span>s</span><span> infinite</span><span>;</span><span>
</span><span></span><span>}</span><span>
</span><span></span><span>@keyframes</span><span> spin</span><span> </span><span>{</span><span>
</span><span>  </span><span>to</span><span> </span><span>{</span><span>
</span><span>    </span><span>transform</span><span>:</span><span> </span><span>translate3d</span><span>(</span><span>0</span><span>,</span><span> </span><span>0</span><span>,</span><span> </span><span>5</span><span>vmin</span><span>)</span><span> </span><span>rotate</span><span>(</span><span>360</span><span>deg</span><span>)</span><span>;</span><span>
</span><span>  </span><span>}</span><span>
</span><span></span><span>}</span></code></pre>
<p>And this gives us a loading indicator when grabbing a new image.</p>
<blockquote><p>Check out the <a href="https://codepen.io/dr3apap/YzxBKLP">demo pen</a> by dr3apap (<a href="https://codepen.io/dr3apap">@dr3apap</a>) over on <a href="https://codepen.io/dr3apap/YzxBKLP">CodePen</a>.</p></blockquote>
<h2>That's it!</h2>
<p>That's how we can create a <code>FlippySnap</code> with React and GreenSock. It's fun to make things that we may not create on a day to day basis. Demos like this can pose different challenges and can level up your problem solving game.</p>
<p>I took it a little further and added a slight parallax effect along with some audio. You can also configure the grid size! Big grids affect performance though. It’s also worth noting that this demo works best in Chromium based browsers. Where would you take it next? I'd like to see if I can recreate it with ThreeJS next. That’ll address the performance 😅</p>
<blockquote><p>Check out the <a href="https://codepen.io/dr3apap/JjyxjMX">demo pen</a> by dr3apap (<a href="https://codepen.io/dr3apap">@dr3apap</a>) over on <a href="https://codepen.io/dr3apap/JjyxjMX">CodePen</a>.</p></blockquote>
<p><strong>Stay Awesome! ʕ •ᴥ•ʔ</strong></p>]]></description>
        <pubDate>Tue, 12 Dec 2023 09:15:00 GMT</pubDate>
        <guid isPermaLink="true">www.aadesina.com/posts/testing-for-front-end-category</guid>
        <source url="www.aadesina.com/rss/FrontEnd.xml">Adebola Adesina FrontEnd RSS feed</source>
        <category>FrontEnd</category>
        <catergory>React</catergory>
      </item>
    
    </channel>
  </rss>
