Expressive scroll animations

— An article about: - Animations - JS

The introduction of this venture in animations with JavaScript functions created a source of progress to handle scroll animations.
Its features were a bit basic though, limiting to “over N% of the viewport height once past D%”.

I mean, it works but leaves it all to the developer to figure out those percentages, which might vary from screen size to screen size as the responsive design puts the element in a different position… Not to mention the next change of design, introducing a new element higher up in the page and shifting the animated element down.

Let’s see if we can create something more expressive and resilient to design changes. When animating elements on scroll, percents of the viewport are not really the things we reason about first. It’s more natural to go for “start when the distance from the top of the element to the bottom of the viewport reaches S% of the ~screen~viewport height and until it reaches E%”. So let’s shoot for that.

The highlighted parts of the sentence are each one concept we’re going to translate into JavaScript for this to work. There are two kinds of things there:

  1. Values, like the top of the element, the bottom of the viewport or the viewport height. But those vary with time: the window can be resized, the user can scroll… So similarly to the sources of progress we have for the animations, we’ll need sources of values here.
  2. Computation over those values, like getting the distance between two points, or getting a percentage. Like a simple - or / operator, except they’ll have to work with our sources of values we said we needed just above.

Bottom of viewport and viewport height

Let’s start with the simplest values here, which are actually the same value if you think about it. From the top of the viewport (which we’ll use as a common origin for our coordinates), they are the same value. And we don’t even need any computation to get it, the DOM provides it straight away: document.body.clientHeight.

The main thing we need to handle is making sure we can observe the changes of this value when the screen is resized.

Top of el

Now we’ve got our information about the viewport, let’s get those about the element. That’s the one that’ll actually trigger changes on scroll, as the position of the element relative to the top of the viewport will change then.
Here again, the DOM is pretty handdy providing that, with el.getClientBoundingRect().top.

First step out of the way, we can measure some dimensions \o/

And with that, we’re ready to move on to computing the distance and percentage.


As the values above change, so will the distance and percentage. This means they also need to be sources of values. Sources of value that will:

  1. combine the value from different sources
  2. compute a new value from there

Combine, first. When observed, it’ll need to observe the sources it combines and just provide their values in an array.

From there we can create the final sources, consuming this array for computation, but only after all data is there:

Combining all this, we can now fade the circle out on scroll \o/.

Tying it all together

There we go! We have everything we need and can now write a more understandable: percentOf(viewportHeight, distance(topOf(el), bottomOfViewport)) to use as our source of progress. As it is, it would animate as the element goes through the whole viewport. We just need one last wrapping source that will scale the percentage into a neat [0..1] range according to our wishes. Let’s call it between(range, source).

Notice how the left circle only animates between the selected range.

And voila! Combining functions, we have a pretty expressive way to define scroll based animations.

As with the first article, this is just a base to start with, that I got from implementing scroll based animations on this website. There’d be plenty more to explore around those scroll sources like :
providing some based on the intersectionRatio from IntersectionObservers,
handling conditions different landmarks on the element & viewport (for example: start when the top of the element crosses the bottom of the viewport and end when its bottom crosses the top of the viewport…)

And of course around the areas for making animations with functions. I’ve started to gather some code on the tinymation project on Github. If you have thoughts on the topic, maybe specific use case, I’d be really happy to head about them. Feel free to look at the code and add issues (or comment on the existing ones).

PS. If you enjoyed combining this concept of sources of values that vary over time, you might want to have a look around Observables and reactive libraries like RxJS, xstream