What tools do we have for animating elements on scroll without using JavaScript libraries?
Scroll event
The scroll event has been available in JavaScript since DOM Level 2, which was introduced in the late 1990s and became widely supported by browsers as early as Internet Explorer 5 and Netscape Navigator 6.
It was not tied to a specific version of JavaScript (ECMAScript), but rather to the DOM Events specification. So, the scroll event has been around since the early days of modern web development and is supported in all major browsers.
It is not a good approach due to performance issues in some cases: during scrolling, the function is called frequently, and with a large number of tracked elements, it performs worse than other options. To address performance concerns, it is better to use it in combination with requestAnimationFrame().
If you need to track a couple of elements, it can be used. In cases of animations where a reaction is needed only to scroll up or down, it is a solid choice.
Intersection Observer API
The Intersection Observer API was introduced in 2016 as a modern way to observe the visibility of elements within a viewport or a parent element. It became widely supported across major browsers in the following years.
An API that allows tracking the position of an element relative to the user's viewport using JS: appearance and disappearance from the visible area.
const callback_func = (entries) => {
entries.forEach((entry) => {
// elements processing when it's show/hide from viewport
})
}
const options = {
root: document.querySelector("#scrollArea"),
rootMargin: "0px",
threshold: 1.0,
};
const observer = new IntersectionObserver(callback_func, options);
Thanks to the rootMargin and threshold parameters, we can achieve effects such as gradual appearance / disappearance from the visible area:
rootMargin
is the margin within the element's visibility area.threshold
is the percentage of the element's visibility required to trigger the callback function of the IntersectionObserver.
The API itself does not trigger animations. They need to be defined in the callback function for example.
It can be used when animations are triggered by elements appearing in the visible area: fade
, slide
, etc.
A good approach due to the low number of calls: the code triggers only when the tracked element enters or exits the visible area. It works best by toggling data attributes, allowing simple animations to be handled with CSS, while the JS code performs minimal work and doesn't burden the site performance. Classes can be toggled instead, but this option doesn't appear as clean to me.
animation-timeline
The animation-timeline property was introduced in 2022 as part of the CSS Motion Path and [CSS Animations] specification(https://www.w3.org/TR/css-animations-2/). It is a relatively recent addition to CSS, designed to give developers more control over when and how animations occur in relation to other animations or the overall page layout.
This property allows developers to specify a custom timeline for an animation, providing a way to control multiple animations or keyframes simultaneously. This was especially useful for scenarios like coordinating complex animations or creating animations that sync with user interactions or other media timelines (such as videos).
As of now, support for animation-timeline is still somewhat limited in browsers, but it is gradually becoming available in the latest browser versions, especially for cutting-edge web designs.
Among other things, it allows animating objects on scroll.
Example from the docs:
#square {
background-color: deeppink;
width: 100px;
height: 100px;
margin-top: 100px;
animation-name: rotateAnimation;
animation-duration: 1ms; /* Firefox requires this to apply the animation */
animation-direction: alternate;
animation-timeline: --squareTimeline;
position: absolute;
bottom: 0;
}
@keyframes rotateAnimation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
Possible issues:
- It doesn't work in all browsers;
- It is absolutely tied to scrolling position. It requires precise knowledge of the element's position on the page for the animation to trigger when it appears on the screen.
- If elements are tied to different scroll positions, a separate animation needs to be defined for each.
The method works well as a replacement for a JS handler in cases where it's used in the user's browser, such as when there is a counter tracking article views or something else showing the "depth" of page scroll.
Due to limited support, it requires a polyfill in JS.
Answers to possible questions
Why can't we simply add animations when an element appears in css? This is possible but complex from a technical standpoint. CSS is a tool for styling elements on a page, not for processing them. As a result, it has limited functionality and static behavior: elements are rendered in the DOM tree, the browser receives the stylesheet, and applies it. Dynamic operations are handled by the js part. JS and CSS have different responsibilities, so expecting all the features for element handling to be in CSS is unrealistic. Logically speaking, appearing on screen is no different from existing pseudo-classes like
:first-child
. The complexity lies in dynamically processing the visibility zone, which goes beyond the current capabilities of CSS.Why are there functions and pseudo-classes in CSS now that used to be possible only with JS? Why isn't it the case with scroll animations? CSS has introduced math functions
calc()
,min()
,max()
, and others, as well as the new pseudo-class:has
. Other features are being added as well, expanding the boundaries of CSS. However, all of this remains within the realm of static behavior: styles are applied and work according to predefined rules, regardless of what the user does (except for simple interactions like:hover
).Is there a specification or plans to add styling for an element when it appears? As far as I know, no. It may not happen through CSS. There are long-standing issues even with static styling (such as limited support for gradients or styling
input
elements), so CSS still has room for growth.