Fade In Headings with GSAP in WordPress

A short fade-in when the page loads can make a WordPress hero heading feel less abrupt. The old version of this snippet faded in every h1 with GSAP, but it hid all of them with CSS first. When GSAP did not load (a blocked CDN, a script error, a slow connection), the headings stayed invisible.

This version only animates elements you mark with the class dpc-fade-in, one after another. It loads GSAP itself from a pinned CDN URL, keeps the content visible without JavaScript, and skips the animation for visitors who turned on reduced motion.

The snippet

<?php
/**
 * Fade in elements with the class "dpc-fade-in" one after another with GSAP.
 */

// ===== Config =====
const DPC_GSAP_VERSION = '3.12.5';
// ===== End of config =====

add_action( 'wp_enqueue_scripts', 'dpc_fade_in_assets' );

function dpc_fade_in_assets() {
	wp_enqueue_script(
		'dpc-gsap',
		'https://cdnjs.cloudflare.com/ajax/libs/gsap/' . DPC_GSAP_VERSION . '/gsap.min.js',
		array(),
		null, // The version is already in the URL.
		array( 'in_footer' => true )
	);

	// Runs after GSAP. If GSAP did not load, it shows the content instead of animating it.
	wp_add_inline_script( 'dpc-gsap', <<<'JS'
(function (root) {
	var items = document.querySelectorAll('.dpc-fade-in');
	if (!root.classList.contains('dpc-fade-pending') || !items.length || typeof window.gsap === 'undefined') {
		root.classList.remove('dpc-fade-pending');
		return;
	}
	gsap.set(items, { autoAlpha: 0 });
	root.classList.remove('dpc-fade-pending');
	gsap.to(items, { autoAlpha: 1, duration: 0.8, stagger: 0.5, ease: 'power1.out', clearProps: 'opacity,visibility' });
})(document.documentElement);
JS
	);

	// Hide the elements only while the animation is pending, never without JavaScript.
	wp_register_style( 'dpc-fade-in', false, array(), '1.0' );
	wp_enqueue_style( 'dpc-fade-in' );
	wp_add_inline_style( 'dpc-fade-in', '.dpc-fade-pending .dpc-fade-in { visibility: hidden; }' );
}

add_action( 'wp_head', 'dpc_fade_in_head_script', 1 );

function dpc_fade_in_head_script() {
	wp_print_inline_script_tag( <<<'JS'
(function (root) {
	if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
		return;
	}
	root.classList.add('dpc-fade-pending');
	// Safety net: show the content after 3 seconds, whatever happens to GSAP.
	setTimeout(function () {
		root.classList.remove('dpc-fade-pending');
	}, 3000);
})(document.documentElement);
JS
	);
}

Then add the class dpc-fade-in to the elements to animate: in the block editor under Advanced → Additional CSS class(es), or in your builder’s class field.

Add it with Scripts Organizer

In Scripts Organizer go to Code Blocks → Add New and give the block a name. Set Trigger location to Everywhere, tick PHP under Script location, paste the code into the PHP editor and publish. Leave Action/Hook empty.

Everything is in one PHP block on purpose: the CSS and both scripts are added through WordPress’s own enqueue functions, in the right order. The code only adds hooks; wp_enqueue_scripts runs on the front end only.

The same code also works unchanged in a small plugin or an mu-plugin.

How it works

  • A tiny script at the top of <head> adds the class dpc-fade-pending to <html>. The CSS hides .dpc-fade-in elements only under that class, so without JavaScript nothing is ever hidden.
  • GSAP loads in the footer from cdnjs with an exact version in the URL. After it, an inline script sets the elements to autoAlpha: 0, removes the pending class and fades them in with a 0.5 second stagger. clearProps removes GSAP’s inline styles at the end.
  • If GSAP is missing when the inline script runs, it removes the pending class at once. A three-second timer in the head script does the same as a last resort.
  • With reduce motion turned on, the head script does nothing and the content simply shows.
  • The older version used gsap.from("h1") on the window load event with h1 { visibility: hidden; }, and did not load GSAP at all.

Customise it

  • Other GSAP version: change DPC_GSAP_VERSION. Pin an exact version; never load a latest URL.
  • Speed and order: change duration, stagger and ease in gsap.to(). Add y: 20 to gsap.set() and y: 0 to gsap.to() for a slide-up, and add transform to clearProps.
  • Only on some pages: return early in dpc_fade_in_assets() and dpc_fade_in_head_script(), for example if ( ! is_front_page() ) { return; }.
  • Animations on scroll instead of on load: see Add and Remove a Class on Scroll in WordPress.

Gotchas

  • The page loads GSAP from cdnjs. If you already load GSAP from your theme or another plugin, delete the wp_enqueue_script() call and attach the inline script to that handle instead, so GSAP is not loaded twice.
  • Loading from a CDN sends the visitor’s IP address to Cloudflare. If that matters for your privacy policy, host gsap.min.js yourself and change the URL.
  • Above-the-fold elements are hidden until the footer scripts run. Keep the class on a few elements, not on the main content.

In the Scripts Organizer library Import it with one click from Scripts Organizer.

// newsletter

Three tested WordPress snippets a week

Blocks, security, performance, Tailwind, PHP and React. Copy, paste, ship. You’ll also get DPlugins product updates. No spam, and you can unsubscribe anytime.

// newsletter

Three tested WordPress snippets a week

Blocks, security, performance, Tailwind, PHP and React. Copy, paste, ship. You'll also get DPlugins product updates. No spam, and you can unsubscribe anytime.