How many paths?
With the unfolding of the pleasant season I spotted a series of floating features at the entrance of a few shops, on the windows of a few houses. Pinwheels, wind chimes, and a delightful cardboard cut-out spiraling with the gentlest breeze. A cutout similar to the following vector graphic.
The visual itself is already effective to describe the static piece of craft, but with SMIL animation we can go further and emulate the approaching wind. With JavaScript, then, we can go deeper and study SVG syntax in excessive detail.
Animation
Focus on the bottom section, on the puffy tail bound to rotate with the most feeble gust.
There’s just one element behind the visual. One <path> with a handful of curves.
<path
d="
M 0 0
c -4 5 -3 12 0 15
c 5 5 10 15 -4 15
c 2 -5 5 -10 0 -15
c -4 -5 -4 -10 4 -15
"
/> We want the shape to rotate around the y axis, but as you might know, there is no perspective in SVG.
You can pretend to rotate the path by changing the horizontal scale.
<path transform="scale(0.5 1)" d="..." /> And with CSS you can even animate the value between 1 and -1.
path {
animation: float 2s infinite;
transform: scale(1, 1);
}
@keyframes float {
50% {
transform: scale(-1, 1);
}
} But the result makes for a rather lackluster workaround.
As the scale reaches zero the path disappears and then grows back on the opposite side. What we want, however, is to maintain the visual, to show the path through the entire sequence.
Enters SMIL animation.
If we come up with the d attribute for the start and end state, making sure to use the same number of points.
"M 0 0 c -4 5 -3 12 0 15 c 5 5 10 15 -4 15 c 2 -5 5 -10 0 -15 c -4 -5 -4 -10 4 -15"
"M 0 0 c 4 5 3 12 0 15 c -5 5 -10 15 4 15 c -2 -5 -5 -10 0 -15 c 4 -5 4 -10 -4 -15" The <animate> element interpolates between the two curves in a much more impressive manner.
For the remarkable result you need to leave the path open and tuck the <animate> element right within.
<path d="M 0 0 c -4 5 -3 12 0 15 ...">
<animate attributeName="d" />
</path> Between the attributes, then, specify the duration and the values, leaning on the two strings.
<animate
attributeName="d"
dur="2s"
values="
M 0 0 c -4 5 -3 12 0 15...;
M 0 0 c 4 5 3 12 0 15;
M 0 0 c -4 5 -3 12 0 15...;
"
/> There are actually three strings so that the animation moves temporarily to the second value and then goes back to the initial instruction. Seamless. And job essentially done.
You can experiment with different timing functions through the keySplines and calcMode attributes. You can animate the entire silhouette to softly move as well. I’ll let you explore these routes on your own, however, and consider a different road.
Lines
Say you are delighted with the SMIL animation, but would like to know more about the d attribute. Say you want to display one specific version of the tail in between the two, interpolated values, or you are now intrigued by the now puzzling question: just how many versions are there?
The answer is hidden in the <path> element, but you won’t find it by inspecting the DOM tree. Run the animation and the node keeps displaying the hard-coded string.
<path d="M 0 0 c -4 5 -3 12 0 15 ...">
<!-- ...animate -->
</path> This is perhaps expected. And certainly more expected than finding a solution with CSS.
There is no d property.
/* no such thing! */
path {
d: 'M 0 0 h 20';
} But the value for the d attribute, the real value, is exposed with CSS properties through the getComputedStyle method.
const path = document.querySelector('path');
getComputedStyle(path)['d'];
/*
'path("M 0 0 C -4 5 -3 12 0 15 C 5 20 10 30 -4 30 C -2 25 1 20 -4 15 C -8 10 -8 5 0 0")'
*/ With this in mind, what you need to do is to let the animation run. And, repeat the instruction in the developer tools. Without delay.
Kidding aside, the suggestion does help to validate the concept — here’s one of the values I found while testing the code.
'path("M 0 0 C 3.71653 5 2.7874 12 0 15 C -4.64566 20 -9.29132 30 3.71653 30 C 1.85826 25 -0.929132 20 3.71653 15 C 7.43306 10 7.43306 5 0 0")' To find all the possible paths, however, focus on the <animate> element.
const animate = path.querySelector('animate'); As the animation takes place, you are able to listen to different events. When the animation starts.
animate.addEventListener('beginEvent', () => {
// now it begins...
}); When the same ends.
animate.addEventListener('endEvent', () => {
// ...sweet closure
}); What you then need is to save the d attribute between the two instants.
Personally, I resolved to use requestAnimationFrame, calling a helper function over and over.
let animation = null;
const paths = [];
const getPath = () => {
paths.push(getComputedStyle(path)['d']);
animation = requestAnimationFrame(getPath);
}; Start the recording process when the animation begins.
animate.addEventListener('beginEvent', () => {
getPath();
}); And stop the looping directive when no longer needed.
animate.addEventListener('endEvent', () => {
cancelAnimationFrame(animation);
}); Direct the <animate> over the span of 1 second, and you’ll finally find an array filled with strings. Roughly 60 of them.
You can take my word for it. Or, consider the initial visual one last time. Now with a touch of interaction.
Inspect the DOM and you will see the attribute does change. After all, I did register all possible paths.