Animate SVG with D3.js

Animation always makes things look better, smooth and nice animations can catch the attention of the user for your application or games. It’s really important to don’t over-exaggerate animations for many reasons like performance issues or too many distractions.

There are so many ways to create animations in those days, 3D using the power of WebGL, and 2D using canvas or moving elements around the DOM.

I have tried different frameworks to animate like Jointjs, Pixijs, Threejs, Popmotion, react-spring, Animate.css, AnimatorJs, and D3js.

Sometimes it is harder to pick one to accomplish something, all of them are good animation frameworks some of them have better performance than others, all it going to depends on what is the task or project that we need to get done.

During this time I had to create a visual representation of the machine brain AI to highlight different parts of the Machine Learning activated during a conversation using NLP (natural language processing), and the user should be able to click on this active memory to show more details.

To accomplish the same thing I created before a 3d brain visualization using the power of WebGL, it looked awesome but because of the miles of particles, it became too heavy and expensive on the client side, and this time I need to implement something lightweight.

After I ran several tests with different frameworks like Pixijs and use different techniques I ended up finding a nice vector image that will allow me to export it as SVG. My first thought was to access each element of the SVG image and animate it. I decided to use D3js because it is pretty straightforward like jQuery to access and manipulate elements.

//Simply doing something like
 const svg = d3.select('svg');

Then using the power of Illustrator I add names to some of the layers that I need to select and access to manipulate.

Exporting SVG File

SVG File-> Export -> Export As : Format SVG and click Show Code.
Now is time to use D3 to animate.

In order to access the Android arms, we can select one or select both at the same time.

const svg = d3.select('svg'); 
const arms = svg.selectAll('[id*=arm]');
Layers on Illustrator

In this example we make our android move its arms.

See the Pen Arm-animation by Victor (@victors1681) on CodePen.

There are several ways to create transitions using D3

  • Applying Styling
  • Using attrTween

Animating Opacity

This is another example of animating one eye of our android creating a fadeOut – fadeIn effect.

const svg = d3.select("svg");
const eyeRight = svg.select("[id=eye-right]");
eyeRight
.transition()
.duration(500)
.style("opacity", 0)
.transition()
.duration(500)
.style("opacity", 1);

Advanced Animation

We can also create a complex animation using tween, to create a transition from one point to another and also combine and animate multiples one at the same time.

One of the challenges that I faced was to create a smooth fadeIn-fadeOut flashing animation and also follow the path and direction, as you can see in the image below there are some blue and red flashing following the path, to accomplish that I had to use some math in order to calculate the angle and make the flash to fade in and out.


Path and flash element Illustrator layers

We have one element called `flash-segment-line` and four red paths, the idea is to show the flashes in each path.

  • After we select the flash and the paths I’m creating dynamically 3 more Flashes that way each path will have its own flash.
  • Select all paths.
  • Then per each path, we select one flash, after selecting the flash we need to get the bounding box (left, bottom, right, top) using one of the helper functions called `getElementBBox`
  • Because I don’t want that effect to happen simultaneously, I’m using `d3.random()` for the duration and the delay.
  • Then using transform to translate the element and the tween function called translateAlongWithDirection` when the magic is happening.
const redFlash = () => {
    const flashes = svg.selectAll('[id*=flash-segment-line]').nodes();
    const flashPath = svg.selectAll('[id*=segment-line-]');

    if (flashes.length === 1) {
        //create more flash
        for (let x = 1; x < flashPath.nodes().length; x++) {
            d3.select(flashes[0].parentNode.insertBefore(flashes[0].cloneNode(true), flashes[0].nextSibling));
        }
    }

    flashes = svg.selectAll('[id*=flash-segment-line]').nodes();
    flashPath.nodes().forEach((node, i) => {
        const currentPos = getElementBBox(flashes[i]);

        const startingPoint = 0;
        const random = d3.randomUniform(6000, 10000);
        const randomDelay = d3.randomUniform(200, 900);

        d3.select(flashes[i])
            .transition()
            .duration(random)
            .delay(function(d, i) {
                return i * randomDelay();
            })
            .attrTween('transform', translateAlongWithDirection(node, currentPos))
            .on('end', redFlash);
    });
};

Translation along the Path

This technique is very useful in any animation framework like Threejs, or pure SVG or CSS.

This is how we can also use just SVG not CSS or Javascript we can animate it.

See the Pen SVG animate along path by Victor (@victors1681) on CodePen.

Going back to our helper function translateAlongWithDirection.

function translateAlongWithDirection(path, { centerW, centerH }) {
    const anglePos = getElementBBox(path);
 
    const l = path.getTotalLength() / 2;
    return function(d, i, a) {
        const shineElement = d3.select(this);
        return function(t) {
            const fadeInOut = Math.sin(Math.PI * t);
            shineElement.style('opacity', fadeInOut);
            const p1 = path.getPointAtLength(t * l);
            const p2 = path.getPointAtLength(t * l + 1);
            const angle = (Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180) / Math.PI;

            return 'translate(' + (p1.x - centerW) + ',' + (p1.y - centerH) + ') rotate(' + angle + ', ' + centerW + ',' + centerH + ')';
        };
    };
}

FadeIn FadeOut

const fadeInOut = Math.sin(Math.PI * t);

When (t) is the current time it takes the animation to be completed, for instance, if our animation takes 1,000 milliseconds (t) will be a normalized value between 0 to 1.

I’m looking to start the animation with opacity 0 and in the middle of my animation I want the opacity to be 1 and in the end, I want to be cero again, so what I need is a sinusoidal wave.

sinusoidal Waveform

Because (t) is the time and (l) is the length of the path, I just need a half to avoid the flash going back and forward, then we need two-point from the path and the time to calculate the angle that we need to apply to our Flash (rectangle element).

 const angle = (Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180) / Math.PI;

There are some of the techniques that I want to highlight for this demonstration, you can follow the code below is not the final result but is close, to the last result I had toma make more changes like changing the color to alight according to our application and implement some actions like mouse hover and click events.

Something that I need to dig deeper is the performance, D3 is easy and great but transitions kill the performance and me the browser slower.

After more changes, I integrated this interactive application into our new user interface.

Leave a Comment