Skip to content

Commit

Permalink
Add post about animating a pirate treasure map path
Browse files Browse the repository at this point in the history
  • Loading branch information
guilhermesimoes committed Sep 11, 2023
1 parent eac5afa commit ffe1235
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 2 deletions.
5 changes: 3 additions & 2 deletions _posts/2016-01-21-youtube-new-morphing-play-pause-svg-icon.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ From my analysis it looks like YouTube is using [SMIL animations]. I could not g

1. Define the icon `path` elements inside a `defs` element so that they are not drawn.

2. Draw one icon by definining a `use` element whose `xlink:href` attribute points to one of the `path`s defined in the previous step. Simply [changing this attribute to point to the other icon is enough to swap them out], but this switch is not animated. To do that,
2. Draw one icon by defining a `use` element whose `xlink:href` attribute points to one of the `path`s defined in the previous step. Simply [changing this attribute to point to the other icon is enough to swap them out], but this switch is not animated. To do that,

3. Replace the `use` with the actual `path` when the page is loaded.
3. Replace the [`use`] with the actual `path` when the page is loaded.

4. Use [`d3-interpolate-path` to morph] one `path` into the other when the button is clicked. Other SVG libraries like D3, Snap.svg or Raphaël can also be used for this effect.

Expand All @@ -27,4 +27,5 @@ Finally, it's important to point out that the number and order of the points of
[SMIL animations]: https://css-tricks.com/guide-svg-animations-smil/
[that they are deprecated and will be removed]: https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/5o0yiO440LM%5B1-25%5D
[changing this attribute to point to the other icon is enough to swap them out]: https://codepen.io/chriscoyier/pen/BaxRaM
[`use`]: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use
[`d3-interpolate-path` to morph]: https://github.com/pbeshai/d3-interpolate-path
36 changes: 36 additions & 0 deletions _posts/2023-09-10-animating-a-pirate-treasure-map-path.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
layout: block
title: "Animating a pirate treasure map path"
subtitle: “X” marks the spot.
date: 2023-09-10 16:36:40 +0100
image:
path: /assets/images/treasure-map.png
block:
path: animating-a-pirate-treasure-map-path.html
---
Animating an SVG line is nothing to write home about. It has already been described in great detail [many], [multiple], [different][different] [times][times]. We make the line use dashes, configure it so that there is a single dash that spans the entire line, and then we animate the offset of that dash. Done.

But what if we want to animate a line that itself is dashed? Like a dashed line in a treasure map?

As it turns out, we can animate it in pretty much the same way! But instead of animating the dashed line, we mask that dashed line with an identical line, and then we animate _that_ line. So, step by step:

1. Define the line `path` inside a `defs` element so that it's not drawn.

2. Draw the line by defining a [`use`] element whose `xlink:href` attribute points to the `path` defined in the previous step.

3. Create a mask with an identical line by using another `use` element. The mask must be white ([like in Photoshop]) and have the same `stroke` properties ([`stroke-width`], [`stroke-linecap`], [`stroke-miterlimit`], etc) as the drawn line so that it can cover it perfectly.

4. Make the mask line use dashes, make it dashed with a single dash that spans its total length, and then animate the dash offset. The usual stuff.

Finally, we can change the transition timing function to achieve different results. Using the [steps transition function] with the same amount of steps as the number of line dashes results in a nice effect.

[many]: https://jakearchibald.com/2013/animated-line-drawing-svg/
[multiple]: https://css-tricks.com/svg-line-animation-works/
[different]: https://product.voxmedia.com/2013/11/25/5426880/polygon-feature-design-svg-animations-for-fun-and-profit
[times]: https://tympanus.net/codrops/2013/12/30/svg-drawing-animation/
[`use`]: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use
[like in Photoshop]: https://lab.iamvdo.me/css-svg-masks/#masking
[`stroke-width`]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-width
[`stroke-linecap`]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linecap
[`stroke-miterlimit`]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit
[steps transition function]: https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function#values
100 changes: 100 additions & 0 deletions assets/blocks/animating-a-pirate-treasure-map-path.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<!DOCTYPE html>
<meta charset="utf-8">
<style>
html, body {
height: 100%;
margin: 0;
}

svg {
width: 100%;
height: 100%;
display: block;
}

.controls {
position: absolute;
top: 1em;
left: 1em;
}

#line {
fill: none;
stroke-width: 4;
}

#animated-line {
stroke: currentColor;
}

#animated-line-mask {
stroke: white;
}
</style>
<body>

<form class="controls">
<label><button type="button" value="continuous-line">Continuous line</button></label>
<label><button type="button" value="continuous-dashes">Continuous dashes</button></label>
<label><button type="button" value="dash-by-dash">Dash by dash</button></label>
</form>
<svg viewBox="0 0 560 98">
<defs>
<path id="line" d="M108 10S-8 30 21 67c41 44 66-71 161-2 95 70 181-58 122-57-58 0-34 76 60 76 95 0 54-72 113-71s69 70 69 70" />
<mask id="animated-line-mask">
<use xlink:href="#line" />
</mask>
</defs>
<use xlink:href="#line" id="animated-line" mask="url(#animated-line-mask)" />
</svg>

<script type="text/javascript">
"use strict";

var line = document.querySelector("#line");
var animatedLine = document.querySelector("#animated-line");
var animatedLineMask = document.querySelector("#animated-line-mask use");
var lineLength = line.getTotalLength();
var numDashes = 14;

function animate(event) {
var button = event.target;

reset();

if (button.value === "continuous-line") {
animatedLine.style.strokeDashoffset = lineLength;
animatedLine.style.strokeDasharray = lineLength;
setTimeout(() => {
animatedLine.style["transition-duration"] = "3s";
animatedLine.style["transition-property"] = "stroke-dashoffset";
animatedLine.style.strokeDashoffset = 0;
}, 10);
} else { // "continuous-dashes" and "dash-by-dash"
animatedLine.style.strokeDasharray = lineLength / (numDashes * 2 - 1);

animatedLineMask.style.strokeDashoffset = lineLength;
animatedLineMask.style.strokeDasharray = lineLength;
setTimeout(() => {
animatedLineMask.style["transition-duration"] = "3s";
animatedLineMask.style["transition-property"] = "stroke-dashoffset";

if (button.value === "dash-by-dash") {
animatedLineMask.style["transition-timing-function"] = `steps(${numDashes}, jump-start)`;
}

animatedLineMask.style.strokeDashoffset = 0;
}, 10);
}
}

function reset() {
animatedLine.removeAttribute('style');
animatedLineMask.removeAttribute('style');
animatedLine.style["transition-property"] = "none";
animatedLineMask.style["transition-property"] = "none";
}

document.querySelector(".controls").addEventListener("click", animate);
</script>
</body>
Binary file added assets/images/treasure-map.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit ffe1235

Please sign in to comment.