Animating SVG with plain JavaScript and CSS

| In Tech
| 5 minute read |by Constantin Gonzalez
A dreamy, ethereal landscape with soft pink and purple clouds in a pastel sky. Flowing sine waves made of code characters weave through the scene in translucent ribbons of light, creating gentle wave patterns. Several colorful butterflies - including blue, purple, and pink ones - flutter gracefully across the composition. The overall mood is serene and fantastical, blending programming elements with natural beauty.

Refactoring my banner into SVG was only the beginning: the next step was animation.

I continued with the “AI as a teacher” model and asked Claude to explain to me concepts like IIFE, how the browser’s DOM processes SVG elements, which SVG properties are GPU-accelerated and other CSS performance concepts, how to integrate SVG with JavaScript and CSS, JavaScript timing patterns, and some more.

You can see the result on the main home page of this blog. Hope you like it!

Why animate your banner?

Because it’s fun! And I wanted to learn a bit more JavaScript (I’m more of a Python person). And this is a tech-focused blog, so why not add a bit more tech to it?

The world is full of generic Wordpress blogs. I like Wordpress because it’s putting blogging and site ownership into people’s hands.

But I also like to build things, including this blog, from the ground up. Having a bit of an individual, even animated thing here and there that is not available as a generic Wordpress plugin is a great way to stand out from the crowd.

Why not on all pages?

I chose to limit the animated version of the banner to the main home page (the other pages get the same code, but it just skips to the end state) to avoid unnecessary distraction and also as a mini-reward for those readers who like to check out my home page for new articles, instead of clicking on some social media update or coming in through some search hit.

Why not (insert your favourite web animation engine here)?

The whole animation code is just 12.5K, unminified. So why spend many MB on some animation framework only to use a small percentage of it?

Also: control. By writing everything by hand, I can control exactly how the animation plays out. This is also a reason why I coded everything by hand and didn’t let an AI do it—I like control.

Finally, it’s much more fun to create a little magic all by yourself. It may be a very simple animation, but there’s some love that went into building it. That’s a level of fun you can’t get with the most powerful framework.

How does it work?

Overall animation structure

The SVG is composed of 4 layers: the background, the sine wave pattern, the avatar image on the right, and the text. Each layer has its own timing in the animation sequence:

const animTimeline = {
  background: {
    start: 0.0,
    stop: 0.1,
  },
  avatar: {
    start: 0.1,
    stop: 0.2,
  },
  sineWaves: {
    start: 0.2,
    stop: 0.9,
  },
  text: {
    start: 0.9,
    stop: 1.0,
  },
};

Layer setup and animation

Each layer is initially rendered invisible (using opacity=0 or visibility="hidden"). The code in /js/header_image.js executes when the DOM is ready and either shows everything at once (static mode) or runs through the animation sequence.

Each layer has 3 JavaScript functions: setup, animation, and finish. The animation functions receive the current animation “time” as a value between 0.0 and 1.0, and use the timeline above to determine when they should be active.

The sine wave “oscilloscope” effect

The sine wave pattern is the most complex part. It’s created using actual sine waves modulated by other sine waves (like an FM synth) to make them more interesting, rendered with varying amplitudes and mirrored for the multi-wave effect.

To create the “oscilloscope” effect, I pre-render the entire sine wave layer 20 times with different amplitude modulations (0% to 100%), storing each as a separate SVG group with visibility="hidden". The animation then rapidly switches between these groups by making one visible while hiding the others. The switching happens by adding a small random offset to make the effect smoother.

Simple effects for other layers

The background and avatar image are simply faded in by changing their opacity value. For the text reveal effect, I use an SVG mask element that grows its width parameter until it fully reveals the text.

Performance considerations

The main animation loop uses setTimeout() rather than requestAnimationFrame() or watching timers, etc., to be a good CPU citizen. It runs at 30 fps, which is plenty for this type of playful banner animation. There’s also logic to control the sine wave animation frequency independently of the overall frame rate.

Accessibility support

While writing this article, Claude helped me realize I was missing an important accessibility feature. Users who have prefers-reduced-motion enabled in their system should skip the animation entirely. I added this check to the animation initialization:

// Check for prefers-reduced-motion and override animation if user prefers reduced motion
const prefersReducedMotion =
  window.matchMedia &&
  window.matchMedia("(prefers-reduced-motion: reduce)").matches;

const config = {
  // ... other config
  animated: animated && !prefersReducedMotion,
};

Now users who prefer reduced motion will see the banner appear instantly in its final state, without any potentially distracting animation.

What next?

Who knows? I’m looking for a Bitmoji-style dog picture that looks like our dog Elvis, so I can add him to the avatar, and perhaps I might add some more, but subtle effects in the future.

I’m not a big fan of JavaScript, and the whole async/promise thing tends to give me headaches. Perhaps I’ll dig a bit deeper into Rust and do a WebAssembly banner in the future?

What do you think? Click on either the Bluesky or LinkedIn post in the “Also posted on” box at the end of this article and add your comments there!