Lately I’ve been interested in creating motion typography for the web. Making shattered text turned out to be a combination of SVG, JavaScript and CSS, which I thought might make a useful tutorial article.
Making The Text
Neither HTML nor CSS has the ability to fragment letterforms: that’s currently the sole province of SVG. To make that happen, I created text in Adobe Illustrator, broke the letters into outlines, and split the result using the Knife tool, working from the inside out. This created “breakpoints” in the text, while maintaining the appearance of wholeness.
The result, exported as an SVG and given a quick code cleanup, is a series of simple paths:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 475.7 162.3" id="heavy">
<path d="M72, 134.1v0.9h30.5v-8.8C92, 127.2, 81.9, 130.4, 72, 134.1z"/>
<path d="M102.6, 113c-10.1, 1.5-20.3, 2.2-30.5, 2.7v18.4c9.9-3.7, 20-6.9, 30.5-7.8V113z" />
…
This SVG code is placed directly in the body of an HTML document.
By default SVG paths are filled with black, but I wanted the reverse. I also needed to add a stroke
definition, so that the SVG pieces looked seamless. The following CSS will achieve that:
body { background: #000; }
svg#heavy path { fill: white; stroke: white; }
With the fractures in place, I needed to consider how to make the pieces fly apart.
Motion Design
Animating each of the letter fragments by hand would be incredibly tedious; instead, I decided to write the motion as a script. Before starting to code, I had to design what the motion would look like, and then translate it into mathematics.
From the separation lines I used to divide the letters, I knew that the word “HEAVY” would explode outwards from its center. This means that every piece would travel in a radial line from the center of the text. If I could locate each piece, then I could determine its distance (x and y) from the center. Moving each piece could then be a simple matter of taking a portion of x and y and using a CSS transform to translate it further in the same direction.
Scripting Motion With JavaScript
A script at the end of the document gains the information we need we need to create the motion. We can’t use the standard offsetLeft
to locate SVG elements; instead, we have to use getBBox()
:
var heavy = document.getElementById("heavy"),
dim = heavy.getBBox(),
heavyCenterX = dim.width/2,
heavyCenterY = dim.height/2,
force = 8;
heavyPieces = document.querySelectorAll("svg#heavy path");
Next, we’ll loop though the paths:
for (var i=0; i < heavyPieces.length; i++) {
var piece = heavyPieces[i];
piece.id = "fragment"+i;
var bbox = piece.getBBox();
var pieceCenterX = bbox.x + (bbox.width/2);
var pieceCenterY = bbox.y + (bbox.height/2);
var distanceX = Math.abs(heavyCenterX - pieceCenterX);
var distanceY = Math.abs(heavyCenterY - pieceCenterY);
…
}
Each fragment is given an id
, with a calculated center and a distance from the center of the text.
Next, we move the pieces. If the fragment is in the top right corner, it moves up and to the right; if it’s in the lower left corner, it moves to the left and down. At the same time, we want to ensure that each fragment moves proportional to its position: the inner fragments by just a fraction, and the outer ones significantly more. I’ll use force
to reduce the distances proportionally:
if (pieceCenterX > heavyCenterX) {
var moveX = distanceX/force+"px";
} else {
var moveX = "-"+distanceX/force+"px";
}
if (pieceCenterY > heavyCenterY) {
var moveY = distanceY/force+"px";
} else {
var moveY = "-"+distanceY/force+"px";
}
It will be a little boring if the fragments only move in straight lines away from the center; I’ll also add a little random spin:
var force = 8,
min = -2.5,
max = 2.5,
randomRot = Math.floor(Math.random() * (max - min + 1)) + min;
I want this action to take place when the user hovers over the text. Putting all of this together in a transform applied with JavaScript, and ignoring vendor prefixes for the sake of simplicity:
document.styleSheets[0].insertRule("svg:hover #fragment"+i+" { transform: translate("+moveX+","+moveY+") rotate("+randomRot+"deg) }",1);
This is good, but the text doesn’t actually animate yet: a hover over the SVG will simply place the fragments in their final position.
Creating Action Movie Motion
I wanted a very specific kind of motion for the explosion: a “move quick, then slow motion” movement familiar from modern action movies. After a little fiddling in Ceaser, I came up with a motion graph that looks like Figure 1.
Time is measured horizontally in this graph, with “amount of change” vertically: so what you see is a very quick “explosion” phase, followed by a long, drawn-out movement driven by the initial impetus.
As a CSS Bezier curve timing function, it looks something like this:
svg#heavy path {
fill: white;
stroke: white;
transition: 12s 1.6s cubic-bezier(0, 1, 0, 1);
}
Note the relatively long duration (12 seconds) and the brief pause beforehand (1.6 seconds). The pause is present because I needed some motion to initially break up the letters, which is the next step.
Under Pressure
Without a cue – the sound of a gunshot or a visual impact – there’s no reason for the letters to fly apart in the first place. I decided to animate the word “HEAVY” as a whole, as it was as struggling to contain internal pressure.
While it would be possible to write this in JavaScript too, I decided to simply code it a CSS keyframe animation:
@keyframes shake {
0% { transform: translate(3px,5px); }
5% { transform: translate(8px,-5px); }
10% { transform: translate(-3px,2px); }
…
}
The motion doesn’t have to be much, but it does need to move relatively quickly:
svg:hover { animation: shake 1s linear; }
With the timing delay on the explosion, the animation and transitions work together to give the impression of a seamless sequence.
Adding Touch Support
To gain support on iOS and other devices I created a quick hack to initiate the same behaviour on touch:
var paths = document.getElementsByTagName('path')[0];
heavy.onclick = function() {
paths.onhover.call(paths);
};
Conclusion
While the result works well, there are a few improvements that could be made… the most significant being auto-fragmenting the text using JavaScript, rather than drawing the divisions by hand. This is a challenge I will leave for a future article.
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/LdGFw