Alpine.js represents an incredibly potent suite of utilities in a small and cohesive package. One of my favorite directives in Alpine has been the x-transition attribute. The x-transition attribute makes it really easy to add tasteful transitions to your markup.

I encountered a challenge when I tried to animate a list of articles. The desired interaction was that as the user scrolls the page; each article should animate in independently. The interaction that I was getting was that the entire group animated in all at once.

Here is the problem code. (I’ll show the solution later)

<div x-data="{ shown: false }" x-intersect="shown = true">
    <article x-show="shown" x-transition:enter.duration.500ms>
        <!--- YOUR ARTICLE MARKUP HERE --->
    </article>
</div>

You can see how easy it is to add the transition using Alpine.

  1. We start by adding some basic state in x-data this will keep visibility state of each individual div containing an article when placed in a loop
  2. We use the x-intersect plugin which allows us to easily react when an element enters the viewport. In this instance when the div around the article container enters the viewport.
  3. We place the x-show to declare the conditions when the article should appear
  4. Lastly we add our transition with its settings.

But we have made one crucial mistake. In its current configuration if we loop the code above 10 times showing 10 articles they will all 10 animate in at the same time. Thats not what we want!

In order to fix my mistake I needed to take a closer look at how the x-show directive works. When x-show is applied behind the scenes it is toggling the css of display:none. Elements that are set to display: none do not take up height or width in the window by default.

So in this case as soon as one article container is in the viewport – all of the article containers are actually visible in the viewport because none of them are taking up any space.

The fix for this is to set the div containing the article to a height when the article is not visible. You should set the height to be similar to the height of the div to what the height will be when shown is equal to true.

You can see one way to do that below using x-bind

<div x-data="{ shown: false }" x-intersect="shown = true" x-bind:style="!shown && { height: '232px' }">
    <article x-show="shown" x-transition:enter.duration.500ms>
        <!--- YOUR ARTICLE MARKUP HERE --->
    </article>
</div>

Now when you scroll each article should have claimed its space in the window even if its not visible. The result is that each article should animate in one by one.