2026年11月12日
2026年11月12日
WordPress Scroll Animations with Intersection Observer and a Child Theme
This article is a localized edition of the Japanese original. Read the Japanese original
A "scroll animation," where elements rise gently into view as you scroll down to them, doesn't need a plugin: you can build one by adding a single JavaScript file and a single CSS file to a child theme. This article walks through an implementation using the browser's built-in Intersection Observer, along with the precautions that keep it from hurting display or usability.
First, a common misconception. Scroll animations are a visual effect; they do not make pages load faster. Depending on how they're built, they can hide the headings and images on the first screen, making the page look slower and worsening LCP (the time until the largest content is displayed) in Core Web Vitals. The code in this article never hides elements that are already on screen when the page opens.
How it works
- Add the class
scroll-animationto the elements you want to animate. - JavaScript watches those elements with Intersection Observer. Only elements that are off-screen when the page opens get the
is-waitingclass, which hides them. - When an element scrolls into view,
is-waitingis removed and a CSS transition reveals it.
Intersection Observer is a mechanism in which the browser notifies you when an element enters the screen (the viewport). Unlike computing positions on every scroll event, it doesn't run code on each scroll. It also sends one notification right after observation starts, so elements already on screen when the page opens are detected correctly.
If the JavaScript fails to load, or the browser doesn't support Intersection Observer, elements simply stay visible from the start. Elements are hidden only when the JavaScript runs, so content never becomes invisible.
Step 1: Add the files to your child theme
If you edit a theme directly, theme updates wipe out your changes. Set up a child theme (see How to create a WordPress child theme) and place the files like this:
your-child-theme/
├── functions.php
├── css/
│ └── scroll-animation.css
└── js/
└── scroll-animation.js
Step 2: Load them from functions.php
Add the following code to the child theme's functions.php. Because it points to files in the child theme, it uses get_stylesheet_directory_uri() rather than get_template_directory_uri() (the parent theme).
<?php
// Load the CSS and JavaScript for scroll animations
add_action( 'wp_enqueue_scripts', function () {
$dir = get_stylesheet_directory();
$uri = get_stylesheet_directory_uri();
wp_enqueue_style(
'scroll-animation',
$uri . '/css/scroll-animation.css',
array(),
filemtime( $dir . '/css/scroll-animation.css' )
);
wp_enqueue_script(
'scroll-animation',
$uri . '/js/scroll-animation.js',
array(),
filemtime( $dir . '/js/scroll-animation.js' ),
array(
'strategy' => 'defer',
'in_footer' => true,
)
);
} );
- Using
filemtime()(the file's modification time) as the version number makes browser caches refresh automatically when you edit the files. - Setting
strategytodeferin the fifth argument keeps the script from blocking page rendering. This array form is available in WordPress 6.3 and later.
If your functions.php already starts with <?php, add only the lines from the second line on. For more on how loading works, see also Loading CSS and JavaScript with wp_enqueue_scripts.
Step 3: Write the JavaScript
Save the following as js/scroll-animation.js.
// Hide only off-screen .scroll-animation elements and reveal them when they scroll into view.
( function () {
var targets = document.querySelectorAll( '.scroll-animation' );
if ( ! targets.length || ! ( 'IntersectionObserver' in window ) ) {
return; // Do nothing in unsupported browsers (elements stay visible from the start)
}
if ( window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches ) {
return; // Don't animate for users who prefer reduced motion
}
var checked = new WeakSet();
var observer = new IntersectionObserver( function ( entries ) {
entries.forEach( function ( entry ) {
var el = entry.target;
if ( entry.isIntersecting ) {
// Show elements that entered (or started in) the viewport and stop observing them
el.classList.remove( 'is-waiting' );
observer.unobserve( el );
} else if ( ! checked.has( el ) ) {
// Hide only the elements that were off-screen at the first check
el.classList.add( 'is-waiting' );
}
checked.add( el );
} );
} );
targets.forEach( function ( el ) {
observer.observe( el );
} );
} )();
Key points:
- The first check: In the first notification that arrives right after
observe(), elements on screen stay visible and only off-screen elements are hidden. Elements that are only partly visible also count as on screen and are not hidden. - Stop observing once shown:
unobserve()removes the observer, so scrolling back up doesn't replay the animation. - Reduced motion settings: For users who have enabled settings such as "Reduce motion" in their OS or browser, the script checks
prefers-reduced-motionand doesn't animate.
Step 4: Write the CSS
Save the following as css/scroll-animation.css.
.scroll-animation {
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
.scroll-animation.is-waiting {
opacity: 0;
transform: translateY(24px);
}
@media (prefers-reduced-motion: reduce) {
.scroll-animation {
transition: none;
}
}
/* When printing, also show elements that haven't been revealed yet */
@media print {
.scroll-animation.is-waiting {
opacity: 1;
transform: none;
}
}
Only opacity (transparency) and transform (position) are animated. Browsers handle these two cheaply, and they don't move surrounding elements, so they don't cause layout shifts (CLS). Avoid animations that change margin or top, which also move the elements around them.
Step 5: Add the class to elements
In the block editor, select the block you want to animate, open "Advanced" in the "Block" tab of the settings sidebar on the right, and enter scroll-animation in "Additional CSS class(es)". Add it to a Group block and the headings and paragraphs inside are revealed together.
When writing markup in theme templates or a Custom HTML block, add the class like this:
<section class="feature scroll-animation">
<h2>Heading</h2>
<p>Body text</p>
</section>
Open the page and scroll down; if the elements rise into view, you're done. If you use a caching plugin, clear the cache before checking.
Precautions
- Don't apply it to first-screen elements: With this article's code, headings right below the header and hero images aren't hidden, but other implementations (such as CSS alone setting
opacity: 0from the start) make them appear late. The effect is only needed for elements that become visible after scrolling. - Don't overdo it: Movement on every paragraph makes pages harder to read. Apply it per group, such as per section or card.
- Make sure nothing stays hidden: Check that content is visible when jumping mid-page via an in-page link and in print preview (the CSS above shows everything when printing).
- Overlap with plugins: Page builders and animation plugins offer similar features. Setting both on the same element causes odd movement, so use only one.
Combining CSS animations with Intersection Observer is also covered in Revealing elements with CSS animations and Intersection Observer.
Summary
- Scroll animations are an effect, not a way to speed up loading. Don't hide elements on the first screen.
- Put the JavaScript and CSS in a child theme and load them from
functions.phpwithget_stylesheet_directory_uri()anddefer. - Use Intersection Observer to hide only elements that were off-screen at the first check, and reveal them when they enter the viewport.
- Animate only
opacityandtransform, and respect users who want less motion withprefers-reduced-motion. - In the block editor, add
scroll-animationunder "Advanced" in "Additional CSS class(es)".
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)