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:

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.

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.

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).