<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>SVG on Smashing Magazine — For Web Designers And Developers</title><link>https://www.smashingmagazine.com/category/svg/index.xml</link><description>Recent content in SVG on Smashing Magazine — For Web Designers And Developers</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Thu, 25 Dec 2025 10:03:08 +0000</lastBuildDate><item><author>Andy Clarke</author><title>Smashing Animations Part 7: Recreating Toon Text With CSS And SVG</title><link>https://www.smashingmagazine.com/2025/12/smashing-animations-part-7-recreating-toon-text-css-svg/</link><pubDate>Wed, 17 Dec 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/12/smashing-animations-part-7-recreating-toon-text-css-svg/</guid><description>In this article, pioneering author and web designer &lt;a href="https://stuffandnonsense.co.uk">Andy Clarke&lt;/a> shows his techniques for creating &lt;a href="https://stuffandnonsense.co.uk/toon-text/index.html">Toon Text titles&lt;/a> using modern CSS and SVG.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/12/smashing-animations-part-7-recreating-toon-text-css-svg/" />
              <title>Smashing Animations Part 7: Recreating Toon Text With CSS And SVG</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Smashing Animations Part 7: Recreating Toon Text With CSS And SVG</h1>
                  
                    
                    <address>Andy Clarke</address>
                  
                  <time datetime="2025-12-17T10:00:00&#43;00:00" class="op-published">2025-12-17T10:00:00+00:00</time>
                  <time datetime="2025-12-17T10:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>After finishing a project that required me to learn everything I could about CSS and SVG animations, I started writing this series about Smashing Animations and “<a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/">How Classic Cartoons Inspire Modern CSS</a>.” To round off this year, I want to show you how to use modern CSS to create that element that makes Toon Titles so impactful: their typography.</p>

<h2 id="title-artwork-design">Title Artwork Design</h2>

<p>In the silent era of the 1920s and early ’30s, the typography of a film’s title card created a mood, set the scene, and reminded an audience of the type of film they’d paid to see.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/1-typographic-title-cards.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="156"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/1-typographic-title-cards.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/1-typographic-title-cards.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/1-typographic-title-cards.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/1-typographic-title-cards.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/1-typographic-title-cards.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/1-typographic-title-cards.png"
			
			sizes="100vw"
			alt="Typographic title cards from the early years of cinema"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Typographic title cards from the early years of cinema. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/1-typographic-title-cards.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Cartoon title cards were also branding, mood, and scene-setting, all rolled into one. In the early years, when major studio budgets were bigger, these title cards were often illustrative and painterly.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/2-tom-jerry-title-cards.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="300"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/2-tom-jerry-title-cards.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/2-tom-jerry-title-cards.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/2-tom-jerry-title-cards.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/2-tom-jerry-title-cards.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/2-tom-jerry-title-cards.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/2-tom-jerry-title-cards.png"
			
			sizes="100vw"
			alt="Top: William Hanna and Joseph Barbera’s 1940s Tom &amp; Jerry title cards. Bottom: Colour versions released in 1957. © Warner Bros. Entertainment Inc."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Top: William Hanna and Joseph Barbera’s 1940s Tom & Jerry title cards. Bottom: Colour versions released in 1957. © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/2-tom-jerry-title-cards.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>But when television boomed during the 1950s, budgets dropped, and cards designed by artists like Lawrence “Art” Goble adopted a new visual language, becoming more graphic, stylised, and less intricate.</p>

<p><strong>Note:</strong> <em>Lawrence “Art” Goble is one of the often overlooked heroes of mid-century American animation. He primarily worked for Hanna-Barbera during its most influential years of the 1950s and 1960s.</em></p>

<p>Goble wasn’t a character animator. His role was to create atmosphere, so he designed environments for <em>The Flintstones</em>, <em>Huckleberry Hound</em>, <em>Quick Draw McGraw</em>, and <em>Yogi Bear</em>, as well as the opening title cards that set the tone. His title cards, featuring paintings with a logo overlaid, helped define the iconic look of Hanna-Barbera.</p>

<p>Goble’s artwork for characters such as Quick Draw McGraw and Yogi Bear was effective on smaller TV screens. Rather than reproducing a still from the cartoon, he focused on presenting a single, strong idea &mdash; often in silhouette &mdash; that captured its essence. In “The Buzzin’ Bear,” Yogi buzzes by in a helicopter. He bounces away, pic-a-nic basket in hand, in “Bear on a Picnic,” and for his “Prize Fight Fright,” Yogi boxes the title text.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/3-title-cards-yogi-bear.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="300"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/3-title-cards-yogi-bear.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/3-title-cards-yogi-bear.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/3-title-cards-yogi-bear.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/3-title-cards-yogi-bear.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/3-title-cards-yogi-bear.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/3-title-cards-yogi-bear.png"
			
			sizes="100vw"
			alt="Title cards for Hanna-Barbera’s Yogi Bear."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Title cards for Hanna-Barbera’s Yogi Bear. © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/3-title-cards-yogi-bear.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>With little or no motion to rely on, Goble’s single frames had to create a mood, set the scene, and describe a story. They did this using flat colours, graphic shapes, and typography that was frequently integrated into the artwork.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/4-title-cards-quick-draw-mcgraw.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="225"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/4-title-cards-quick-draw-mcgraw.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/4-title-cards-quick-draw-mcgraw.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/4-title-cards-quick-draw-mcgraw.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/4-title-cards-quick-draw-mcgraw.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/4-title-cards-quick-draw-mcgraw.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/4-title-cards-quick-draw-mcgraw.png"
			
			sizes="100vw"
			alt="Title cards for Hanna-Barbera’s Quick Draw McGraw."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Title cards for Hanna-Barbera’s Quick Draw McGraw. © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/4-title-cards-quick-draw-mcgraw.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>As designers who work on the web, toon titles can teach us plenty about how to convey a brand’s personality, make a first impression, and set expectations for someone’s experience using a product or website. We can learn from the artists’ techniques to create effective banners, landing-page headers, and even good ol’ fashioned splash screens.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="toon-title-typography">Toon Title Typography</h2>

<p>Cartoon title cards show how merging type with imagery delivers the punch a header or hero needs. With a handful of <code>text-shadow</code>, <code>text-stroke</code>, and <code>transform</code> tricks, modern CSS lets you tap into that same energy.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/5-title-cards-augie-doggie.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="455"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/5-title-cards-augie-doggie.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/5-title-cards-augie-doggie.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/5-title-cards-augie-doggie.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/5-title-cards-augie-doggie.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/5-title-cards-augie-doggie.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/5-title-cards-augie-doggie.png"
			
			sizes="100vw"
			alt="Title cards for Hanna-Barbera’s Augie Doggie."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Title cards for Hanna-Barbera’s Augie Doggie. © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/5-title-cards-augie-doggie.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="the-toon-text-title-generator">The Toon Text Title Generator</h2>

<p>Partway through writing this article, I realised it would be useful to have a tool for generating text styled like the cartoon titles I love so much. <a href="https://stuffandnonsense.co.uk/toon-text/tool.html">So I made one.</a></p>

<p>My Toon Text Title Generator lets you experiment with colours, strokes, and multiple text shadows. You can adjust paint order, apply letter spacing, preview your text in a selection of sample fonts, and then copy the generated CSS straight to your clipboard to use in a project.</p>

<h2 id="toon-title-css">Toon Title CSS</h2>

<p>You can simply copy-paste the CSS that the Toon Text Title Generator provides you. But let’s look closer at what it does.</p>

<h3 id="text-shadow">Text shadow</h3>

<p>Look at the type in this title from Augie Doggie’s episode “Yuk-Yuk Duck,” with its pale yellow letters and dark, hard, offset shadow that lifts it off the background and creates the illusion of depth.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/6-toon-text-collection.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="317"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/6-toon-text-collection.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/6-toon-text-collection.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/6-toon-text-collection.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/6-toon-text-collection.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/6-toon-text-collection.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/6-toon-text-collection.png"
			
			sizes="100vw"
			alt="Example from Andy&#39;s Toon Text collection."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      See this example in my Toon Text collection. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/6-toon-text-collection.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You probably already know that <code>text-shadow</code> accepts four values: (1) horizontal and (2) vertical offsets, (3) blur, and (4) a colour which can be solid or semi-transparent. Those offset values can be positive or negative, so I can replicate “Yuk-Yuk Duck” using a hard shadow pulled down and to the right:</p>

<pre><code class="language-css">color: &#35;f7f76d;
text-shadow: 5px 5px 0 &#35;1e1904;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/7-toon-text-collection.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="317"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/7-toon-text-collection.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/7-toon-text-collection.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/7-toon-text-collection.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/7-toon-text-collection.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/7-toon-text-collection.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/7-toon-text-collection.png"
			
			sizes="100vw"
			alt="Example from Andy&#39;s Toon Text collection."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      See this example in my Toon Text collection. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/7-toon-text-collection.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>On the other hand, this “Pint Giant” title has a different feel with its negative semi-soft shadow:</p>

<pre><code class="language-css">color: &#35;c2a872;
text-shadow:
  -7px 5px 0 &#35;b100e,
  0 -5px 10px &#35;546c6f;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/8-toon-text-collection.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="317"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/8-toon-text-collection.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/8-toon-text-collection.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/8-toon-text-collection.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/8-toon-text-collection.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/8-toon-text-collection.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/8-toon-text-collection.png"
			
			sizes="100vw"
			alt="Example from Andy&#39;s Toon Text collection."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      See this example in my Toon Text collection. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/8-toon-text-collection.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>To add extra depth and create more interesting effects, I can layer multiple shadows. For “Let’s Duck Out,” I combine four shadows: the first a solid shadow with a negative horizontal offset to lift the text off the background, followed by progressively softer shadows to create a blur around it:</p>

<pre><code class="language-css">color: &#35;6F4D80;
text-shadow:
  -5px 5px 0 &#35;260e1e, /&#42; Shadow 1 &#42;/
  0 0 15px &#35;e9ce96,   /&#42; Shadow 2 &#42;/
  0 0 30px &#35;e9ce96,   /&#42; Shadow 3 &#42;/
  0 0 30px &#35;e9ce96;   /&#42; Shadow 4 &#42;/
</code></pre>

<p>These shadows show that using <code>text-shadow</code> isn’t just about creating lighting effects, as they can also be decorative and add personality.</p>

<h3 id="text-stroke">Text Stroke</h3>

<p>Many cartoon title cards feature letters with a bold outline that makes them stand out from the background. I can recreate this effect using <code>text-stroke</code>. For a long time, this property was only available via a <code>-webkit-</code> prefix, but that also means it’s now supported across modern browsers.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/9-toon-text-collection.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="317"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/9-toon-text-collection.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/9-toon-text-collection.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/9-toon-text-collection.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/9-toon-text-collection.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/9-toon-text-collection.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/9-toon-text-collection.png"
			
			sizes="100vw"
			alt="Example from Andy&#39;s Toon Text collection."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      See this example in my Toon Text collection. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/9-toon-text-collection.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><code>text-stroke</code> is a shorthand for two properties. The first, <code>text-stroke-width</code>, draws a contour around individual letters, while the second, <code>text-stroke-color</code>, controls its colour. For “Whatever Goes Pup,” I added a <code>4px</code> blue stroke to the yellow text:</p>

<pre><code class="language-css">color: &#35;eff0cd;
-webkit-text-stroke: 4px &#35;7890b5;
text-stroke: 4px &#35;7890b5;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/10-toon-text-collection.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="317"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/10-toon-text-collection.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/10-toon-text-collection.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/10-toon-text-collection.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/10-toon-text-collection.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/10-toon-text-collection.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/10-toon-text-collection.png"
			
			sizes="100vw"
			alt="Example from Andy&#39;s Toon Text collection."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      See this example in my Toon Text collection. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/10-toon-text-collection.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Strokes can be especially useful when they’re combined with shadows, so for “Growing, Growing, Gone,” I added a thin <code>3px</code> stroke to a barely blurred <code>1px</code> shadow to create this three-dimensional text effect:</p>

<pre><code class="language-css">color: &#35;fbb999;
text-shadow: 3px 5px 1px &#35;5160b1;
-webkit-text-stroke: 3px &#35;984336;
text-stroke: 3px &#35;984336;
</code></pre>

<h3 id="paint-order">Paint Order</h3>

<p>Using <code>text-stroke</code> doesn’t always produce the expected result, especially with thinner letters and thicker strokes, because by default the browser draws a stroke over the fill. Sadly, CSS still does not permit me to adjust stroke placement as I often do in Sketch. However, the <code>paint-order</code> property has values that allow me to place the stroke behind, rather than in front of, the fill.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/11-paint-order.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="317"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/11-paint-order.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/11-paint-order.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/11-paint-order.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/11-paint-order.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/11-paint-order.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/11-paint-order.png"
			
			sizes="100vw"
			alt="Left: paint-order: stroke; Right: paint-order: fill."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Left: <code>paint-order: stroke</code>. Right: <code>paint-order: fill</code>. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/11-paint-order.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><code>paint-order: stroke</code> paints the stroke first, then the fill, whereas <code>paint-order: fill</code> does the opposite:</p>

<pre><code class="language-css">color: &#35;fbb999;
paint-order: fill;
text-shadow: 3px 5px 1px &#35;5160b1;
text-stroke-color:&#35;984336;
text-stroke-width: 3px;
</code></pre>

<p>An effective stroke keeps letters readable, adds weight, and &mdash; when combined with shadows and paint order &mdash; gives flat text real presence.</p>

<div class="partners__lead-place"></div>

<h2 id="backgrounds-inside-text">Backgrounds Inside Text</h2>

<p>Many cartoon title cards go beyond flat colour by adding texture, gradients, or illustrated detail to the lettering. Sometimes that’s a texture, other times it might be a gradient with a subtle tonal shift. On the web, I can recreate this effect by using a background image or gradient behind the text, and then clipping it to the shape of the letters. This relies on two properties working together: <code>background-clip: text</code> and <code>text-fill-color: transparent</code>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/12-toon-text-collection.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/12-toon-text-collection.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/12-toon-text-collection.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/12-toon-text-collection.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/12-toon-text-collection.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/12-toon-text-collection.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/12-toon-text-collection.png"
			
			sizes="100vw"
			alt="Example from Andy&#39;s Toon Text collection."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      See this example in my Toon Text collection. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/12-toon-text-collection.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>First, I apply a background behind the text. This can be a bitmap or vector image or a CSS gradient. For this example from the Quick Draw McGraw episode “Baba Bait,” the title text includes a subtle top–bottom gradient from dark to light:</p>

<pre><code class="language-css">background: linear-gradient(0deg, &#35;667b6a, &#35;1d271a);
</code></pre>

<p>Next, I clip that background to the glyphs and make the text transparent so the background shows through:</p>

<pre><code class="language-css">-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
</code></pre>

<p>With just those two lines, the background is no longer painted behind the text; instead, it’s painted within it. This technique works especially well when combined with strokes and shadows. A clipped gradient provides the lettering with colour and texture, a stroke keeps its edges sharp, and a shadow elevates it from the background. Together, they recreate the layered look of hand-painted title cards using nothing more than a little CSS. As always, test clipped text carefully, as browser quirks can sometimes affect shadows and rendering.</p>

<h3 id="splitting-text-into-individual-characters">Splitting Text Into Individual Characters</h3>

<p>Sometimes I don’t want to style a whole word or heading. I want to style individual letters &mdash; to nudge a character into place, give one glyph extra weight, or animate a few letters independently.</p>

<p>In plain HTML and CSS, there’s only one reliable way to do that: wrap each character in its own <code>span</code> element. I could do that manually, but that would be fragile, hard to maintain, and would quickly fall apart when copy changes. Instead, when I need per-letter control, I use a text-splitting library like <a href="https://www.spltjs.com">splt.js</a> (although other solutions are available). This takes a text node and automatically wraps words or characters, giving me extra hooks to animate and style without messing up my markup.</p>

<p>It’s an approach that keeps my HTML readable and semantic, while giving me the fine-grained control I need to recreate the uneven, characterful typography you see in classic cartoon title cards. However, this approach comes with accessibility caveats, as most screen readers read text nodes in order. So this:</p>

<pre><code class="language-html">&lt;h2&gt;Hum Sweet Hum&lt;/h2&gt;
</code></pre>

<p>…reads as you’d expect:</p>

<blockquote>Hum Sweet Hum</blockquote>

<p>But this:</p>

<pre><code class="language-html">&lt;h2&gt;
&lt;span&gt;H&lt;/span&gt;
&lt;span&gt;u&lt;/span&gt;
&lt;span&gt;m&lt;/span&gt;
&lt;!-- etc. --&gt;
&lt;/h2&gt;
</code></pre>

<p>…can be interpreted differently depending on the browser and screen reader. Some will concatenate the letters and read the words correctly. Others may pause between letters, which in a worst-case scenario might sound like:</p>

<blockquote>“H…” “U…” “M…”</blockquote>

<p>Sadly, some splitting solutions don’t deliver an always accessible result, so I’ve written my own text splitter, <a href="https://stuffandnonsense.co.uk/toon-text/splinter.html#section-install">splinter.js</a>, which is currently in beta.</p>

<h3 id="transforming-individual-letters">Transforming Individual Letters</h3>

<p>To activate my Toon Text Splitter, I add a <code>data-</code> attribute to the element I want to split:</p>

<pre><code class="language-html">&lt;h2 data-split="toon"&gt;Hum Sweet Hum&lt;/h2&gt;
</code></pre>

<p>First, my script separates each word into individual letters and wraps them in a <code>span</code> element with class and ARIA attributes applied:</p>

<pre><code class="language-html">&lt;span class="toon-char" aria-hidden="true"&gt;H&lt;/span&gt;
&lt;span class="toon-char" aria-hidden="true"&gt;u&lt;/span&gt;
&lt;span class="toon-char" aria-hidden="true"&gt;m&lt;/span&gt;
</code></pre>

<p>The script then takes the initial content of the split element and adds it as an aria attribute to help maintain accessibility:</p>

<div class="break-out">
<pre><code class="language-html">&lt;h2 data-split="toon" aria-label="Hum Sweet Hum"&gt;
  &lt;span class="toon-char" aria-hidden="true"&gt;H&lt;/span&gt;
  &lt;span class="toon-char" aria-hidden="true"&gt;u&lt;/span&gt;
  &lt;span class="toon-char" aria-hidden="true"&gt;m&lt;/span&gt;
&lt;/h2&gt;
</code></pre>
</div>

<p>With those class attributes applied, I can then style individual characters as I choose.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/13-toon-text-collection.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="317"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/13-toon-text-collection.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/13-toon-text-collection.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/13-toon-text-collection.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/13-toon-text-collection.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/13-toon-text-collection.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/13-toon-text-collection.png"
			
			sizes="100vw"
			alt="Example from Andy&#39;s Toon Text collection."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      See this example in my Toon Text collection. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/13-toon-text-collection.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>For example, for “Hum Sweet Hum,” I want to replicate how its letters shift away from the baseline. After using my Toon Text Splitter, I applied four different <code>translate</code> values using several <code>:nth-child</code> selectors to create a semi-random look:</p>

<pre><code class="language-css">/&#42; 4th, 8th, 12th... &#42;/
.toon-char:nth-child(4n) { translate: 0 -8px; }
/&#42; 1st, 5th, 9th... &#42;/
.toon-char:nth-child(4n+1) { translate: 0 -4px; }
/&#42; 2nd, 6th, 10th... &#42;/
.toon-char:nth-child(4n+2) { translate: 0 4px; }
/&#42; 3rd, 7th, 11th... &#42;/
.toon-char:nth-child(4n+3) { translate: 0 8px; }
</code></pre>

<p>But <code>translate</code> is only one property I can use to <code>transform</code> my toon text.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/14-toon-text-collection.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="317"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/14-toon-text-collection.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/14-toon-text-collection.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/14-toon-text-collection.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/14-toon-text-collection.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/14-toon-text-collection.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/14-toon-text-collection.png"
			
			sizes="100vw"
			alt="Example from Andy&#39;s Toon Text collection."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      See this example in my Toon Text collection. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/14-toon-text-collection.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>I could also rotate those individual characters for an even more chaotic look:</p>

<pre><code class="language-css">/&#42; 4th, 8th, 12th... &#42;/
.toon-line .toon-char:nth-child(4n) { rotate: -4deg; }
/&#42; 1st, 5th, 9th... &#42;/
.toon-char:nth-child(4n+1) { rotate: -8deg; }
/&#42; 2nd, 6th, 10th... &#42;/
.toon-char:nth-child(4n+2) { rotate: 4deg; }
/&#42; 3rd, 7th, 11th... &#42;/
.toon-char:nth-child(4n+3) { rotate: 8deg; }
</code></pre>

<p>But <code>translate</code> is only one property I can use to <code>transform</code> my toon text. I could also <code>rotate</code> those individual characters for an even more chaotic look:</p>

<pre><code class="language-css">/&#42; 4th, 8th, 12th... &#42;/
.toon-line .toon-char:nth-child(4n) {
rotate: -4deg; }

/&#42; 1st, 5th, 9th... &#42;/
.toon-char:nth-child(4n+1) {
rotate: -8deg; }

/&#42; 2nd, 6th, 10th... &#42;/
.toon-char:nth-child(4n+2) {
rotate: 4deg; }

/&#42; 3rd, 7th, 11th... &#42;/
.toon-char:nth-child(4n+3) {
rotate: 8deg; }
</code></pre>

<p>And, of course, I could add animations to jiggle those characters and bring my toon text style titles to life. First, I created a keyframe animation that rotates the characters:</p>

<div class="break-out">
<pre><code class="language-css">@keyframes jiggle {
0%, 100% { transform: rotate(var(--base-rotate, 0deg)); }
25% { transform: rotate(calc(var(--base-rotate, 0deg) + 3deg)); }
50% { transform: rotate(calc(var(--base-rotate, 0deg) - 2deg)); }
75% { transform: rotate(calc(var(--base-rotate, 0deg) + 1deg)); }
}
</code></pre>
</div>

<p>Before applying it to the <code>span</code> elements created by my Toon Text Splitter:</p>

<pre><code class="language-css">.toon-char {
animation: jiggle 3s infinite ease-in-out;
transform-origin: center bottom; }
</code></pre> 

<p>And finally, setting the rotation amount and a delay before each character begins to jiggle:</p>

<pre><code class="language-css">.toon-char:nth-child(4n) { --base-rotate: -2deg; }
.toon-char:nth-child(4n+1) { --base-rotate: -4deg; }
.toon-char:nth-child(4n+2) { --base-rotate: 2deg; }
.toon-char:nth-child(4n+3) { --base-rotate: 4deg; }

.toon-char:nth-child(4n) { animation-delay: 0.1s; }
.toon-char:nth-child(4n+1) { animation-delay: 0.3s; }
.toon-char:nth-child(4n+2) { animation-delay: 0.5s; }
.toon-char:nth-child(4n+3) { animation-delay: 0.7s; }
</code></pre> 














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/15-toon-text-collection.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="317"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/15-toon-text-collection.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/15-toon-text-collection.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/15-toon-text-collection.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/15-toon-text-collection.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/15-toon-text-collection.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/15-toon-text-collection.png"
			
			sizes="100vw"
			alt="Example from Andy&#39;s Toon Text collection."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      See this example in my Toon Text collection. (<a href='https://files.smashing.media/articles/smashing-animations-part-7-recreating-toon-text-css-svg/15-toon-text-collection.png'>Large preview</a>)
    </figcaption>
  
</figure>

<div class="partners__lead-place"></div>

<h2 id="one-frame-to-make-an-impression">One Frame To Make An Impression</h2>

<p>Cartoon title artists had one frame to make an impression, and their typography was as important as the artwork they painted. The same is true on the web.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aA%20well-designed%20header%20or%20hero%20area%20needs%20clarity,%20character,%20and%20confidence%20%e2%80%94%20not%20simply%20a%20faded%20full-width%20background%20image.%0a&url=https://smashingmagazine.com%2f2025%2f12%2fsmashing-animations-part-7-recreating-toon-text-css-svg%2f">
      
A well-designed header or hero area needs clarity, character, and confidence — not simply a faded full-width background image.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<p>With a few carefully chosen CSS properties &mdash; shadows, strokes, clipped backgrounds, and some restrained animation &mdash; we can recreate that same impact. I love toon text not because I’m nostalgic, but because its design is intentional. Make deliberate choices, and let a little toon text typography add punch to your designs.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Andy Clarke</author><title>Smashing Animations Part 6: Magnificent SVGs With `&lt;use>` And CSS Custom Properties</title><link>https://www.smashingmagazine.com/2025/11/smashing-animations-part-6-svgs-css-custom-properties/</link><pubDate>Fri, 07 Nov 2025 15:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/11/smashing-animations-part-6-svgs-css-custom-properties/</guid><description>SVG is one of those web technologies that’s both elegant and, at times, infuriating. In this article, pioneering author and web designer &lt;a href="https://stuffandnonsense.co.uk">Andy Clarke&lt;/a> explains his technique for animating SVG elements that are hidden in the Shadow DOM.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/11/smashing-animations-part-6-svgs-css-custom-properties/" />
              <title>Smashing Animations Part 6: Magnificent SVGs With `&lt;use&gt;` And CSS Custom Properties</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Smashing Animations Part 6: Magnificent SVGs With `&lt;use&gt;` And CSS Custom Properties</h1>
                  
                    
                    <address>Andy Clarke</address>
                  
                  <time datetime="2025-11-07T15:00:00&#43;00:00" class="op-published">2025-11-07T15:00:00+00:00</time>
                  <time datetime="2025-11-07T15:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>I explained recently how I use <code>&lt;symbol&gt;</code>, <code>&lt;use&gt;</code>, and CSS Media Queries to develop what I call <a href="https://www.smashingmagazine.com/2025/10/smashing-animations-part-5-building-adaptive-svgs/">adaptive SVGs</a>. Symbols let us define an element once and then <em>use</em> it again and again, making SVG animations easier to maintain, more efficient, and lightweight.</p>

<p>Since I wrote that explanation, I’ve designed and implemented new <a href="https://stuffandnonsense.co.uk/blog/say-hello-to-my-magnificent-7">Magnificent 7</a> animated graphics across <a href="https://stuffandnonsense.co.uk/">my website</a>. They play on the web design pioneer theme, featuring seven magnificent Old West characters.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://stuffandnonsense.co.uk/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/1-graphics-old-west-characters.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/1-graphics-old-west-characters.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/1-graphics-old-west-characters.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/1-graphics-old-west-characters.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/1-graphics-old-west-characters.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/1-graphics-old-west-characters.png"
			
			sizes="100vw"
			alt="Graphics featuring seven magnificent Old West characters"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      View this animated SVG on <a href='https://stuffandnonsense.co.uk/'>my website</a>. (<a href='https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/1-graphics-old-west-characters.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><code>&lt;symbol&gt;</code> and <code>&lt;use&gt;</code> let me define a character design and reuse it across multiple SVGs and pages. First, I created my characters and put each into a <code>&lt;symbol&gt;</code> inside a hidden library SVG:</p>

<div class="break-out">
<pre><code class="language-svg">&lt;!-- Symbols library --&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" style="display:none;"&gt;
 &lt;symbol id="outlaw-1"&gt;[...]&lt;/symbol&gt;
 &lt;symbol id="outlaw-2"&gt;[...]&lt;/symbol&gt;
 &lt;symbol id="outlaw-3"&gt;[...]&lt;/symbol&gt;
 &lt;!-- etc. --&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>Then, I referenced those symbols in two other SVGs, one for large and the other for small screens:</p>

<pre><code class="language-svg">&lt;!-- Large screens --&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" id="svg-large"&gt;
 &lt;use href="outlaw-1" /&gt;
 &lt;!-- ... --&gt;
&lt;/svg&gt;

&lt;!-- Small screens --&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" id="svg-small"&gt;
 &lt;use href="outlaw-1" /&gt;
 &lt;!-- ... --&gt;
&lt;/svg&gt;
</code></pre>

<p>Elegant. But then came the infuriating. I could reuse the characters, but couldn’t animate or style them. I added CSS rules targeting elements within the symbols referenced by a <code>&lt;use&gt;</code>, but nothing happened. Colours stayed the same, and things that should move stayed static. It felt like I’d run into an invisible barrier, and I had.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="understanding-the-shadow-dom-barrier">Understanding The Shadow DOM Barrier</h2>

<p>When you reference the contents of a <code>symbol</code> with <code>use</code>, a browser creates a copy of it in the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM">Shadow DOM</a>. Each <code>&lt;use&gt;</code> instance becomes its own encapsulated copy of the referenced <code>&lt;symbol&gt;</code>, meaning that CSS from outside can’t break through the barrier to style any elements directly. For example, in normal circumstances, this <code>tapping</code> value triggers a CSS animation:</p>

<pre><code class="language-svg">&lt;g class="outlaw-1-foot tapping"&gt;
 &lt;!-- ... --&gt;
&lt;/g&gt;
</code></pre>

<pre><code class="language-css">.tapping {
  animation: tapping 1s ease-in-out infinite;
}
</code></pre>

<p>But when the same animation is applied to a <code>&lt;use&gt;</code> instance of that same foot, nothing happens:</p>

<pre><code class="language-svg">&lt;symbol id="outlaw-1"&gt;
 &lt;g class="outlaw-1-foot"&gt;&lt;!-- ... --&gt;&lt;/g&gt;
&lt;/symbol&gt;

&lt;use href="#outlaw-1" class="tapping" /&gt;
</code></pre>

<pre><code class="language-css">.tapping {
  animation: tapping 1s ease-in-out infinite;
}
</code></pre>

<p>That’s because the <code>&lt;g&gt;</code> inside the <code>&lt;symbol&gt;</code> element is in a protected shadow tree, and the CSS Cascade stops dead at the <code>&lt;use&gt;</code> boundary. This behaviour can be frustrating, but it’s intentional as it ensures that reused symbol content stays consistent and predictable.</p>

<p>While learning how to develop adaptive SVGs, I found all kinds of attempts to work around this behaviour, but most of them sacrificed the reusability that makes SVG so elegant. I didn’t want to duplicate my characters just to make them blink at different times. I wanted a single <code>&lt;symbol&gt;</code> with instances that have their own timings and expressions.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/2-animated-elements-single-svg-symbol.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/2-animated-elements-single-svg-symbol.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/2-animated-elements-single-svg-symbol.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/2-animated-elements-single-svg-symbol.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/2-animated-elements-single-svg-symbol.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/2-animated-elements-single-svg-symbol.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/2-animated-elements-single-svg-symbol.png"
			
			sizes="100vw"
			alt="Several animated elements within a single SVG symbol"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Several animated elements within a single SVG symbol. (<a href='https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/2-animated-elements-single-svg-symbol.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="css-custom-properties-to-the-rescue">CSS Custom Properties To The Rescue</h2>

<p>While working on my pioneer animations, I learned that <strong>regular CSS values can’t cross the boundary into the Shadow DOM, but CSS Custom Properties can</strong>. And even though you can’t directly style elements inside a <code>&lt;symbol&gt;</code>, you can pass custom property values to them. So, when you insert custom properties into an inline style, a browser looks at the cascade, and those styles become available to elements inside the <code>&lt;symbol&gt;</code> being referenced.</p>

<p>I added <code>rotate</code> to an inline style applied to the <code>&lt;symbol&gt;</code> content:</p>

<pre><code class="language-svg">&lt;symbol id="outlaw-1"&gt;
  &lt;g class="outlaw-1-foot" style="
    transform-origin: bottom right; 
    transform-box: fill-box; 
    transform: rotate(var(--foot-rotate));"&gt;
    &lt;!-- ... --&gt;
  &lt;/g&gt;
&lt;/symbol&gt;
</code></pre>

<p>Then, defined the foot tapping animation and applied it to the element:</p>

<pre><code class="language-css">@keyframes tapping {
  0%, 60%, 100% { --foot-rotate: 0deg; }
  20% { --foot-rotate: -5deg; }
  40% { --foot-rotate: 2deg; }
}

use[data-outlaw="1"] {
  --foot-rotate: 0deg;
  animation: tapping 1s ease-in-out infinite;
}
</code></pre>

<h2 id="passing-multiple-values-to-a-symbol">Passing Multiple Values To A Symbol</h2>

<p>Once I’ve set up a symbol to use CSS Custom Properties, I can pass as many values as I want to any <code>&lt;use&gt;</code> instance. For example, I might define variables for <code>fill</code>, <code>opacity</code>, or <code>transform</code>. What’s elegant is that each <code>&lt;symbol&gt;</code> instance can then have its own set of values.</p>

<pre><code class="language-svg">&lt;g class="eyelids" style="
  fill: var(--eyelids-colour, #f7bea1);
  opacity: var(--eyelids-opacity, 1);
  transform: var(--eyelids-scale, 0);"
&gt;
  &lt;!-- etc. --&gt;
&lt;/g&gt;
</code></pre>

<pre><code class="language-css">use[data-outlaw="1"] {
  --eyelids-colour: #f7bea1; 
  --eyelids-opacity: 1;
}

use[data-outlaw="2"] {
  --eyelids-colour: #ba7e5e; 
  --eyelids-opacity: 0;
}
</code></pre>

<p>Support for passing CSS Custom Properties like this is solid, and every contemporary browser handles this behaviour correctly. Let me show you a few ways I’ve been using this technique, starting with a multi-coloured icon system.</p>

<div class="partners__lead-place"></div>

<h2 id="a-multi-coloured-icon-system">A Multi-Coloured Icon System</h2>

<p>When I need to maintain a set of icons, I can define an icon once inside a <code>&lt;symbol&gt;</code> and then use custom properties to apply colours and effects. Instead of needing to duplicate SVGs for every theme, each <code>use</code> can carry its own values.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/3-custom-properties-colours.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="167"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/3-custom-properties-colours.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/3-custom-properties-colours.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/3-custom-properties-colours.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/3-custom-properties-colours.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/3-custom-properties-colours.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/3-custom-properties-colours.png"
			
			sizes="100vw"
			alt="Custom properties for the fill colours in several Bluesky icons"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Custom properties for the fill colours in several Bluesky icons. (<a href='https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/3-custom-properties-colours.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>For example, I applied an <code>--icon-fill</code> custom property for the default <code>fill</code> colour of the <code>&lt;path&gt;</code> in this Bluesky icon :</p>

<pre><code class="language-svg">&lt;symbol id="icon-bluesky"&gt;
  &lt;path fill="var(--icon-fill, currentColor)" d="..." /&gt;
&lt;/symbol&gt;
</code></pre>

<p>Then, whenever I need to vary how that icon looks &mdash; for example, in a <code>&lt;header&gt;</code> and <code>&lt;footer&gt;</code> &mdash; I can pass new <code>fill</code> colour values to each instance:</p>

<pre><code class="language-html">&lt;header&gt;
  &lt;svg xmlns="http://www.w3.org/2000/svg"&gt;
    &lt;use href="#icon-bluesky" style="--icon-fill: #2d373b;" /&gt;
  &lt;/svg&gt;
&lt;/header&gt;

&lt;footer&gt;
  &lt;svg xmlns="http://www.w3.org/2000/svg"&gt;
    &lt;use href="#icon-bluesky" style="--icon-fill: #590d1a;" /&gt;
  &lt;/svg&gt;
&lt;/footer&gt;
</code></pre>

<p>These icons are the same shape but look different thanks to their inline styles.</p>

<h2 id="data-visualisations-with-css-custom-properties">Data Visualisations With CSS Custom Properties</h2>

<p>We can use <code>&lt;symbol&gt;</code> and <code>&lt;use&gt;</code> in plenty more practical ways. They’re also helpful for creating lightweight data visualisations, so imagine an infographic about three famous <a href="https://en.wikipedia.org/wiki/American_frontier">Wild West</a> sheriffs: <a href="https://en.wikipedia.org/wiki/Wyatt_Earp">Wyatt Earp</a>, <a href="https://en.wikipedia.org/wiki/Pat_Garrett">Pat Garrett</a>, and <a href="https://en.wikipedia.org/wiki/Bat_Masterson">Bat Masterson</a>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/4-data-visualisations-css-custom-properties.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="421"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/4-data-visualisations-css-custom-properties.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/4-data-visualisations-css-custom-properties.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/4-data-visualisations-css-custom-properties.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/4-data-visualisations-css-custom-properties.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/4-data-visualisations-css-custom-properties.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/4-data-visualisations-css-custom-properties.png"
			
			sizes="100vw"
			alt="Data visualisations with CSS Custom Properties"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Data visualisations with CSS Custom Properties. (<a href='https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/4-data-visualisations-css-custom-properties.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Each sheriff’s profile uses the same set of SVG three symbols: one for a bar representing the length of a sheriff’s career, another to represent the number of arrests made, and one more for the number of kills. Passing custom property values to each <code>&lt;use&gt;</code> instance can vary the bar lengths, arrests scale, and kills colour without duplicating SVGs. I first created symbols for those items:</p>

<pre><code class="language-svg">&lt;svg xmlns="http://www.w3.org/2000/svg" style="display:none;"&gt;
  &lt;symbol id="career-bar"&gt;
    &lt;rect
      height="10"
      width="var(--career-length, 100)" 
      fill="var(--career-colour, #f7bea1)"
    /&gt;
  &lt;/symbol&gt;
  
  &lt;symbol id="arrests-badge"&gt;
    &lt;path 
      fill="var(--arrest-color, #d0985f)" 
      transform="scale(var(--arrest-scale, 1))"
    /&gt;
  &lt;/symbol&gt;
  
  &lt;symbol id="kills-icon"&gt;
    &lt;path fill="var(--kill-colour, #769099)" /&gt;
  &lt;/symbol&gt;
&lt;/svg&gt;
</code></pre>

<p>Each symbol accepts one or more values:</p>

<ul>
<li><strong><code>--career-length</code></strong> adjusts the <code>width</code> of the career bar.</li>
<li><strong><code>--career-colour</code></strong> changes the <code>fill</code> colour of that bar.</li>
<li><strong><code>--arrest-scale</code></strong> controls the arrest badge size.</li>
<li><strong><code>--kill-colour</code></strong> defines the <code>fill</code> colour of the kill icon.</li>
</ul>

<p>I can use these to develop a profile of each sheriff using <code>&lt;use&gt;</code> elements with different inline styles, starting with Wyatt Earp.</p>

<div class="break-out">
<pre><code class="language-svg">&lt;svg xmlns="http://www.w3.org/2000/svg"&gt;
  &lt;g id="wyatt-earp"&gt;
    &lt;use href="&#35;career-bar" style="--career-length: 400; --career-color: &#35;769099;"/&gt;
    &lt;use href="&#35;arrests-badge" style="--arrest-scale: 2;" /&gt;
    &lt;!-- ... --&gt;
    &lt;use href="&#35;arrests-badge" style="--arrest-scale: 2;" /&gt;
    &lt;use href="&#35;arrests-badge" style="--arrest-scale: 1;" /&gt;
    &lt;use href="&#35;kills-icon" style="--kill-color: &#35;769099;" /&gt;
  &lt;/g&gt;

  &lt;g id="pat-garrett"&gt;
    &lt;use href="&#35;career-bar" style="--career-length: 300; --career-color: &#35;f7bea1;"/&gt;
    &lt;use href="&#35;arrests-badge" style="--arrest-scale: 2;" /&gt;
    &lt;!-- ... --&gt;
    &lt;use href="&#35;arrests-badge" style="--arrest-scale: 2;" /&gt;
    &lt;use href="&#35;arrests-badge" style="--arrest-scale: 1;" /&gt;
    &lt;use href="&#35;kills-icon" style="--kill-color: &#35;f7bea1;" /&gt;
  &lt;/g&gt;

  &lt;g id="bat-masterson"&gt;
    &lt;use href="#career-bar" style="--career-length: 200; --career-color: &#35;c2d1d6;"/&gt;
    &lt;use href="#arrests-badge" style="--arrest-scale: 2;" /&gt;
    &lt;!-- ... --&gt;
    &lt;use href="&#35;arrests-badge" style="--arrest-scale: 2;" /&gt;
    &lt;use href="&#35;arrests-badge" style="--arrest-scale: 1;" /&gt;
    &lt;use href="&#35;kills-icon" style="--kill-color: &#35;c2d1d6;" /&gt;
  &lt;/g&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>Each <code>&lt;use&gt;</code> shares the same symbol elements, but the inline variables change their colours and sizes. I can even animate those values to highlight their differences:</p>

<pre><code class="language-css">@keyframes pulse {
  0%, 100% { --arrest-scale: 1; }
  50% { --arrest-scale: 1.2; }
}

use[href="#arrests-badge"]:hover {
  animation: pulse 1s ease-in-out infinite;
}
</code></pre>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aCSS%20Custom%20Properties%20aren%e2%80%99t%20only%20helpful%20for%20styling;%20they%20can%20also%20channel%20data%20between%20HTML%20and%20SVG%e2%80%99s%20inner%20geometry,%20binding%20visual%20attributes%20like%20colour,%20length,%20and%20scale%20to%20semantics%20like%20arrest%20numbers,%20career%20length,%20and%20kills.%0a&url=https://smashingmagazine.com%2f2025%2f11%2fsmashing-animations-part-6-svgs-css-custom-properties%2f">
      
CSS Custom Properties aren’t only helpful for styling; they can also channel data between HTML and SVG’s inner geometry, binding visual attributes like colour, length, and scale to semantics like arrest numbers, career length, and kills.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<h2 id="ambient-animations">Ambient Animations</h2>

<p>I started learning to animate elements within symbols while creating the animated graphics for my website’s Magnificent 7. To reduce complexity and make my code lighter and more maintainable, I needed to define each character once and reuse it across SVGs:</p>

<div class="break-out">
<pre><code class="language-svg">&lt;!-- Symbols library --&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" style="display:none;"&gt;
  &lt;symbol id="outlaw-1"&gt;[…]&lt;/symbol&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;

&lt;!-- Large screens --&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" id="svg-large"&gt;
  &lt;use href="outlaw-1" /&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;

&lt;!-- Small screens --&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" id="svg-small"&gt;
  &lt;use href="outlaw-1" /&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;
</code></pre>
</div>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/5-characters.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/5-characters.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/5-characters.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/5-characters.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/5-characters.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/5-characters.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/5-characters.png"
			
			sizes="100vw"
			alt="My website’s Magnificent 7 characters"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      My website’s Magnificent 7 characters. (<a href='https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/5-characters.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>But I didn’t want those characters to stay static; I needed subtle movements that would bring them to life. I wanted their eyes to blink, their feet to tap, and their moustache whiskers to twitch. So, to animate these details, I pass animation data to elements inside those symbols using CSS Custom Properties, starting with the blinking.</p>

<p>I implemented the blinking effect by placing an SVG group over the outlaws’ eyes and then changing its <code>opacity</code>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/6-blinking-effect.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="333"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/6-blinking-effect.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/6-blinking-effect.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/6-blinking-effect.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/6-blinking-effect.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/6-blinking-effect.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/6-blinking-effect.png"
			
			sizes="100vw"
			alt="Blinking effect by animating eyelids’ opacity."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Blinking effect by animating eyelids’ opacity. (<a href='https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/6-blinking-effect.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>To make this possible, I added an inline style with a CSS Custom Property to the group:</p>

<div class="break-out">
<pre><code class="language-svg">&lt;symbol id="outlaw-1" viewBox="0 0 712 2552"&gt;
 &lt;g class="eyelids" style="opacity: var(--eyelids-opacity, 1);"&gt;
    &lt;!-- ... --&gt;
  &lt;/g&gt;
&lt;/symbol&gt;
</code></pre>
</div>

<p>Then, I defined the blinking animation by changing <code>--eyelids-opacity</code>:</p>

<pre><code class="language-css">@keyframes blink {
  0%, 92% { --eyelids-opacity: 0; }
  93%, 94% { --eyelids-opacity: 1; }
  95%, 97% { --eyelids-opacity: 0.1; }
  98%, 100% { --eyelids-opacity: 0; }
}
</code></pre>

<p>…and applied it to every character:</p>

<div class="break-out">
<pre><code class="language-css">use[data-outlaw] {
  --blink-duration: 4s;
  --eyelids-opacity: 1;
  animation: blink var(--blink-duration) infinite var(--blink-delay);
}
</code></pre>
</div>

<p>…so that each character wouldn’t blink at the same time, I set a different <code>--blink-delay</code> before they all start blinking, by passing another Custom Property:</p>

<pre><code class="language-css">use[data-outlaw="1"] { --blink-delay: 1s; }
use[data-outlaw="2"] { --blink-delay: 2s; }
<!-- ... -->
use[data-outlaw="7"] { --blink-delay: 3s; }
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/7-foot-tapping-effect.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/7-foot-tapping-effect.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/7-foot-tapping-effect.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/7-foot-tapping-effect.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/7-foot-tapping-effect.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/7-foot-tapping-effect.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/7-foot-tapping-effect.png"
			
			sizes="100vw"
			alt="Foot tapping effect by animating the foot’s rotation"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Foot tapping effect by animating the foot’s rotation. (<a href='https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/7-foot-tapping-effect.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Some of the characters tap their feet, so I added an inline style with a CSS Custom Property to those groups, too:</p>

<pre><code class="language-svg">&lt;symbol id="outlaw-1" viewBox="0 0 712 2552"&gt;
  &lt;g class="outlaw-1-foot" style="
    transform-origin: bottom right; 
    transform-box: fill-box; 
    transform: rotate(var(--foot-rotate));"&gt;
  &lt;/g&gt;
&lt;/symbol&gt;
</code></pre>

<p>Defining the foot-tapping animation:</p>

<pre><code class="language-css">@keyframes tapping {
  0%, 60%, 100% { --foot-rotate: 0deg; }
  20% { --foot-rotate: -5deg; }
  40% { --foot-rotate: 2deg; }
}
</code></pre>

<p>And adding those extra Custom Properties to the characters’ declaration:</p>

<pre><code class="language-css">use[data-outlaw] {
  --blink-duration: 4s;
  --eyelids-opacity: 1;
  --foot-rotate: 0deg;
  animation: 
    blink var(--blink-duration) infinite var(--blink-delay),
    tapping 1s ease-in-out infinite;
}
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/8-jiggling-effect.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="333"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/8-jiggling-effect.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/8-jiggling-effect.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/8-jiggling-effect.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/8-jiggling-effect.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/8-jiggling-effect.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/8-jiggling-effect.png"
			
			sizes="100vw"
			alt="Jiggling effect by animating the moustaches’ translation"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Jiggling effect by animating the moustaches’ translation. (<a href='https://files.smashing.media/articles/smashing-animations-part-6-svgs-css-custom-properties/8-jiggling-effect.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>…before finally making the character’s whiskers jiggle via an inline style with a CSS Custom Property which describes how his moustache transforms:</p>

<pre><code class="language-svg">&lt;symbol id="outlaw-1" viewBox="0 0 712 2552"&gt;
  &lt;g class="outlaw-1-tashe" style="
    transform: translateX(var(--jiggle-x, 0px));"
  &gt;
    &lt;!-- ... --&gt;
  &lt;/g&gt;
&lt;/symbol&gt;
</code></pre>

<p>Defining the jiggle animation:</p>

<pre><code class="language-css">@keyframes jiggle {
  0%, 100% { --jiggle-x: 0px; }
  20% { --jiggle-x: -3px; }
  40% { --jiggle-x: 2px; }
  60% { --jiggle-x: -1px; }
  80% { --jiggle-x: 4px; }
}
</code></pre>

<p>And adding those properties to the characters’ declaration:</p>

<pre><code class="language-css">use[data-outlaw] {
  --blink-duration: 4s;
  --eyelids-opacity: 1;
  --foot-rotate: 0deg;
  --jiggle-x: 0px;
  animation: 
    blink var(--blink-duration) infinite var(--blink-delay),
    jiggle 1s ease-in-out infinite,
    tapping 1s ease-in-out infinite;
}
</code></pre>

<p>With these moving parts, the characters come to life, but my markup remains remarkably lean. By combining several animations into a single declaration, I can choreograph their movements without adding more elements to my SVG. Every outlaw shares the same base <code>&lt;symbol&gt;</code>, and their individuality comes entirely from CSS Custom Properties.</p>

<div class="partners__lead-place"></div>

<h2 id="pitfalls-and-solutions">Pitfalls And Solutions</h2>

<p>Even though this technique might seem bulletproof, there are a few traps it’s best to avoid:</p>

<ul>
<li><strong>CSS Custom Properties only work if they’re referenced with a <code>var()</code> inside a <code>&lt;symbol&gt;</code>.</strong> Forget that, and you’ll wonder why nothing updates. Also, properties that aren’t naturally inherited, like <code>fill</code> or <code>transform</code>, need to use <code>var()</code> in their value to benefit from the cascade.</li>
<li><strong>It’s always best to include a fallback value alongside a custom property</strong>, like <code>opacity: var(--eyelids-opacity, 1);</code> to ensure SVG elements render correctly even without custom property values applied.</li>
<li><strong>Inline styles set via the <code>style</code> attribute take precedence</strong>, so if you mix inline and external CSS, remember that Custom Properties follow normal cascade rules.</li>
<li><strong>You can always use DevTools to inspect custom property values.</strong> Select a <code>&lt;use&gt;</code> instance and check the Computed Styles panel to see which custom properties are active.</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>The <code>&lt;symbol&gt;</code> and <code>&lt;use&gt;</code> elements are among the most elegant but sometimes frustrating aspects of SVG. The Shadow DOM barrier makes animating them trickier, but <strong>CSS Custom Properties act as a bridge</strong>. They let you pass colour, motion, and personality across that invisible boundary, resulting in cleaner, lighter, and, best of all, fun animations.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Andy Clarke</author><title>Ambient Animations In Web Design: Practical Applications (Part 2)</title><link>https://www.smashingmagazine.com/2025/10/ambient-animations-web-design-practical-applications-part2/</link><pubDate>Wed, 22 Oct 2025 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/10/ambient-animations-web-design-practical-applications-part2/</guid><description>Motion can be tricky: too much distracts, too little feels flat. Ambient animations sit in the middle. They’re subtle, slow-moving details that add atmosphere without stealing the show. In part two of his series, web design pioneer &lt;a href="https://stuffandnonsense.co.uk">Andy Clarke&lt;/a> shows how ambient animations can add personality to any website design.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/10/ambient-animations-web-design-practical-applications-part2/" />
              <title>Ambient Animations In Web Design: Practical Applications (Part 2)</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Ambient Animations In Web Design: Practical Applications (Part 2)</h1>
                  
                    
                    <address>Andy Clarke</address>
                  
                  <time datetime="2025-10-22T13:00:00&#43;00:00" class="op-published">2025-10-22T13:00:00+00:00</time>
                  <time datetime="2025-10-22T13:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>First, a recap:</p>

<blockquote>Ambient animations are the kind of passive movements you might not notice at first. However, they bring a design to life in subtle ways. Elements might subtly transition between colours, move slowly, or gradually shift position. Elements can appear and disappear, change size, or they could rotate slowly, adding depth to a brand’s personality.</blockquote>

<p>In <a href="https://www.smashingmagazine.com/2025/09/ambient-animations-web-design-principles-implementation/">Part 1</a>, I illustrated the concept of ambient animations by recreating the cover of a Quick Draw McGraw comic book as a CSS/SVG animation. But I know not everyone needs to animate cartoon characters, so in Part 2, I’ll share how ambient animation works in three very different projects: Reuven Herman, Mike Worth, and EPD. Each demonstrates how motion can <strong>enhance brand identity</strong>, <strong>personality</strong>, and <strong>storytelling</strong> without dominating a page.</p>

<h2 id="reuven-herman">Reuven Herman</h2>

<p>Los Angeles-based composer Reuven Herman didn’t just want a website to showcase his work. He wanted it to convey his personality and the experience clients have when working with him. Working with musicians is always creatively stimulating: they’re critical, engaged, and full of ideas.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/1-design-reuven-herman.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/1-design-reuven-herman.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/1-design-reuven-herman.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/1-design-reuven-herman.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/1-design-reuven-herman.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/1-design-reuven-herman.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/1-design-reuven-herman.png"
			
			sizes="100vw"
			alt="Design for LA-based composer Reuven Herman"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      My design for LA-based composer Reuven Herman. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/1-design-reuven-herman.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Reuven’s classical and jazz background reminded me of the work of album cover designer <a href="https://stuffandnonsense.co.uk/blog/a-book-for-your-inspiration-collection-alex-steinweiss">Alex Steinweiss</a>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/2-album-cover-designs-alex-steinweiss.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="267"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/2-album-cover-designs-alex-steinweiss.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/2-album-cover-designs-alex-steinweiss.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/2-album-cover-designs-alex-steinweiss.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/2-album-cover-designs-alex-steinweiss.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/2-album-cover-designs-alex-steinweiss.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/2-album-cover-designs-alex-steinweiss.png"
			
			sizes="100vw"
			alt="Album cover designs by Alex Steinweiss"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Album cover designs by Alex Steinweiss. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/2-album-cover-designs-alex-steinweiss.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>I was inspired by the depth and texture that Alex brought to his designs for over 2,500 unique covers, and I wanted to incorporate his techniques into my illustrations for Reuven.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/3-illustrations-reuven-herman.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="267"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/3-illustrations-reuven-herman.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/3-illustrations-reuven-herman.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/3-illustrations-reuven-herman.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/3-illustrations-reuven-herman.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/3-illustrations-reuven-herman.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/3-illustrations-reuven-herman.png"
			
			sizes="100vw"
			alt="Illustrations for Reuven Herman"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Two of my illustrations for Reuven Herman. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/3-illustrations-reuven-herman.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>To bring Reuven’s illustrations to life, I followed a few core ambient animation principles:</p>

<ul>
<li>Keep animations <strong>slow</strong> and <strong>smooth</strong>.</li>
<li><strong>Loop seamlessly</strong> and avoid abrupt changes.</li>
<li>Use <strong>layering</strong> to build complexity.</li>
<li>Avoid distractions.</li>
<li>Consider <strong>accessibility</strong> and <strong>performance</strong>.</li>
</ul>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1129468148"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>You can view this ambient animation <a href="https://stuffandnonsense.co.uk/lab/ambient-animations.html">in my lab</a>. For Reuven’s site:</p>

<ul>
<li>Sheet music stave lines morph between wavy and straight states.</li>
<li>Notes drift at different speeds to create parallax-like depth.</li>
<li>Piano keys appear to float.</li>
</ul>

<p>My first step is always to <a href="https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/">optimise my SVGs for animation</a> by exporting and optimising one set of elements at a time &mdash; always in the order they’ll appear in the final file and building the master SVG gradually. Working forwards from the background, I exported the sheet music stave lines, first in their wavy state.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/4-sheet-music-stave-lines-wavy.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="275"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/4-sheet-music-stave-lines-wavy.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/4-sheet-music-stave-lines-wavy.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/4-sheet-music-stave-lines-wavy.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/4-sheet-music-stave-lines-wavy.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/4-sheet-music-stave-lines-wavy.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/4-sheet-music-stave-lines-wavy.png"
			
			sizes="100vw"
			alt="Sheet music stave lines (wavy)"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Sheet music stave lines (wavy). (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/4-sheet-music-stave-lines-wavy.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>…followed by their straight state:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/5-sheet-music-stave-lines-straight.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/5-sheet-music-stave-lines-straight.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/5-sheet-music-stave-lines-straight.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/5-sheet-music-stave-lines-straight.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/5-sheet-music-stave-lines-straight.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/5-sheet-music-stave-lines-straight.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/5-sheet-music-stave-lines-straight.png"
			
			sizes="100vw"
			alt="Sheet music stave lines (straight)"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Sheet music stave lines (straight). (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/5-sheet-music-stave-lines-straight.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The first step in my animation is to morph the stave lines between states. They’re made up of six paths with multi-coloured strokes. I started with the wavy lines:</p>

<pre><code class="language-svg">&lt;!-- Wavy state --&gt;
&lt;g fill="none" stroke-width="2" stroke-linecap="round"&gt;
&lt;path id="p1" stroke="#D2AB99" d="[…]"/&gt;
&lt;path id="p2" stroke="#BDBEA9" d="[…]"/&gt;
&lt;path id="p3" stroke="#E0C852" d="[…]"/&gt;
&lt;path id="p4" stroke="#8DB38B" d="[…]"/&gt;
&lt;path id="p5" stroke="#43616F" d="[…]"/&gt;
&lt;path id="p6" stroke="#A13D63" d="[…]"/&gt;
&lt;/g&gt;
</code></pre>

<p>Although <a href="https://www.smashingmagazine.com/2023/10/animate-along-path-css/">CSS now enables animation between path points</a>, the number of points in each state needs to match. <a href="https://gsap.com">GSAP</a> doesn’t have that limitation and can animate between states that have different numbers of points, making it ideal for this type of animation. I defined the new set of straight paths:</p>

<pre><code class="language-javascript">&lt;!-- Straight state --&gt;
const Waves = {
  p1: "[…]",
  p2: "[…]",
  p3: "[…]",
  p4: "[…]",
  p5: "[…]",
  p6: "[…]"
};
</code></pre>

<p>Then, I created a <a href="https://gsap.com/docs/v3/GSAP/Timeline">GSAP timeline</a> that repeats backwards and forwards over six seconds:</p>

<pre><code class="language-javascript">const waveTimeline = gsap.timeline({
  repeat: -1,
  yoyo: true,
  defaults: { duration: 6, ease: "sine.inOut" }
});

Object.entries(Waves).forEach(([id, d]) =&gt; {
  waveTimeline.to(`#${id}`, { morphSVG: d }, 0);
});
</code></pre>

<p><strong>Another ambient animation principle is to use layering to build complexity.</strong> Think of it like building a sound mix. You want variation in rhythm, tone, and timing. In my animation, three rows of musical notes move at different speeds:</p>

<pre><code class="language-svg">&lt;path id="notes-row-1"/&gt;
&lt;path id="notes-row-2"/&gt;
&lt;path id="notes-row-3"/&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/6-three-rows-musical-notes.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="275"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/6-three-rows-musical-notes.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/6-three-rows-musical-notes.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/6-three-rows-musical-notes.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/6-three-rows-musical-notes.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/6-three-rows-musical-notes.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/6-three-rows-musical-notes.png"
			
			sizes="100vw"
			alt="Three rows of musical notes"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Three rows of musical notes. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/6-three-rows-musical-notes.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The duration of each row’s animation is also defined using GSAP, from <code>100</code> to <code>400</code> seconds to give the overall animation a parallax-style effect:</p>

<pre><code class="language-javascript">const noteRows = [
  { id: "#notes-row-1", duration: 300, y: 100 }, // slowest
  { id: "#notes-row-2", duration: 200, y: 250 }, // medium
  { id: "#notes-row-3", duration: 100, y: 400 }  // fastest
];

[…]
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/7-animated-shadow.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="275"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/7-animated-shadow.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/7-animated-shadow.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/7-animated-shadow.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/7-animated-shadow.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/7-animated-shadow.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/7-animated-shadow.png"
			
			sizes="100vw"
			alt="Animated shadow"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Animated shadow. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/7-animated-shadow.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The next layer contains a shadow cast by the piano keys, which slowly rotates around its centre:</p>

<pre><code class="language-javascript">gsap.to("shadow", {
  y: -10,
  rotation: -2,
  transformOrigin: "50% 50%",
  duration: 3,
  ease: "sine.inOut",
  yoyo: true,
  repeat: -1
});
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/8-animated-piano-keys.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="275"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/8-animated-piano-keys.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/8-animated-piano-keys.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/8-animated-piano-keys.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/8-animated-piano-keys.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/8-animated-piano-keys.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/8-animated-piano-keys.png"
			
			sizes="100vw"
			alt="Animated piano keys"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Animated piano keys. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/8-animated-piano-keys.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>And finally, the piano keys themselves, which rotate at the same time but in the opposite direction to the shadow:</p>

<pre><code class="language-javascript">gsap.to("#g3-keys", {
  y: 10,
  rotation: 2,
  transformOrigin: "50% 50%",
  duration: 3,
  ease: "sine.inOut",
  yoyo: true,
  repeat: -1
});
</code></pre>

<p>The complete animation can be viewed <a href="https://stuffandnonsense.co.uk/lab/ambient-animations.html">in my lab</a>. By layering motion thoughtfully, the site feels alive without ever dominating the content, which is a perfect match for Reuven’s energy.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="/printed-books/image-optimization/">Image Optimization</a></strong>, Addy Osmani’s new practical guide to optimizing and delivering <strong>high-quality images</strong> on the web. Everything in one single <strong>528-pages</strong> book.</p>
<a data-instant href="https://www.smashingmagazine.com/printed-books/image-optimization/" class="btn btn--green btn--large" style="">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://www.smashingmagazine.com/printed-books/image-optimization/" class="feature-panel-image-link">
<div class="feature-panel-image"><picture><source type="image/avif" srcSet="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/2c669cf1-c6ef-4c87-9901-018b04f7871f/image-optimization-shop-cover-opt.avif" />
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/87fd0cfa-692e-459c-b2f3-15209a1f6aa7/image-optimization-shop-cover-opt.png"
    alt="Feature Panel"
    width="480"
    height="697"
/>
</picture>
</div>
</a>
</div>
</aside>
</div>

<h2 id="mike-worth">Mike Worth</h2>

<p>As I mentioned earlier, not everyone needs to animate cartoon characters, but I do occasionally. Mike Worth is an Emmy award-winning film, video game, and TV composer who asked me to design his website. For the project, I created and illustrated the character of orangutan adventurer Orango Jones.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/9-design-mike-worth.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/9-design-mike-worth.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/9-design-mike-worth.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/9-design-mike-worth.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/9-design-mike-worth.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/9-design-mike-worth.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/9-design-mike-worth.png"
			
			sizes="100vw"
			alt="Design for Mike Worth"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      My design for Emmy award-winning composer Mike Worth. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/9-design-mike-worth.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Orango proved to be the perfect subject for ambient animations and features on every page of Mike’s website. He takes the reader on an adventure, and along the way, they get to experience Mike’s music.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/10-illustration-mike-worth.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/10-illustration-mike-worth.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/10-illustration-mike-worth.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/10-illustration-mike-worth.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/10-illustration-mike-worth.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/10-illustration-mike-worth.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/10-illustration-mike-worth.png"
			
			sizes="100vw"
			alt="Illustration for Mike Worth"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Another of my illustrations for Mike Worth. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/10-illustration-mike-worth.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>For Mike’s “About” page, I wanted to combine ambient animations with interactions. Orango is in a cave where he has found a stone tablet with faint markings that serve as a navigation aid to elsewhere on Mike’s website. The illustration contains a hidden feature, an easter egg, as when someone presses Orango’s magnifying glass, moving shafts of light stream into the cave and onto the tablet.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1129470404"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>My SVG is deliberately structured, and from back to front, it includes the cave, light shaft, Orango, and navigation:</p>

<pre><code class="language-svg">&lt;svg data-lights="lights-off"&gt;
  &lt;g id="cave"&gt;[…]&lt;/g&gt;
  &lt;path id="light-shaft" d="[…]"&gt;&lt;/path&gt;
  &lt;g id="orango"&gt;[…]&lt;/g&gt;
  &lt;g id="nav"&gt;[…]&lt;/g&gt;
&lt;/svg&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/11-cave-background.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/11-cave-background.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/11-cave-background.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/11-cave-background.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/11-cave-background.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/11-cave-background.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/11-cave-background.png"
			
			sizes="100vw"
			alt="The cave background"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The cave background. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/11-cave-background.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>I also added an anchor around a hidden circle, which I positioned over Orango’s magnifying glass, as a large tap target to toggle the light shafts on and off by changing the <code>data-lights</code> value on the SVG:</p>

<div class="break-out">
<pre><code class="language-html">&lt;a href="javascript:void(0);" id="light-switch" title="Lights on/off"&gt;
  &lt;circle cx="700" cy="1000" r="100" opacity="0" /&gt;
&lt;/a&gt;
</code></pre>
</div>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/12-orango-isolated.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/12-orango-isolated.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/12-orango-isolated.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/12-orango-isolated.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/12-orango-isolated.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/12-orango-isolated.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/12-orango-isolated.png"
			
			sizes="100vw"
			alt="Orango isolated"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Orango isolated. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/12-orango-isolated.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Then, I added two descendant selectors to my CSS, which adjust the opacity of the light shafts depending on the <code>data-lights</code> value:</p>

<pre><code class="language-css">[data-lights="lights-off"] .light-shaft {
  opacity: .05;
  transition: opacity .25s linear;
}

[data-lights="lights-on"] .light-shaft {
  opacity: .25;
  transition: opacity .25s linear;
}
</code></pre>

<p>A slow and subtle rotation adds natural movement to the light shafts:</p>

<pre><code class="language-css">@keyframes shaft-rotate {
  0% { rotate: 2deg; }
  50% { rotate: -2deg; }
  100% { rotate: 2deg; }
}
</code></pre>

<p>Which is only visible when the light toggle is active:</p>

<pre><code class="language-css">[data-lights="lights-on"] .light-shaft {
  animation: shaft-rotate 20s infinite;
  transform-origin: 100% 0;
}
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/13-light-shafts-isolated.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/13-light-shafts-isolated.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/13-light-shafts-isolated.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/13-light-shafts-isolated.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/13-light-shafts-isolated.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/13-light-shafts-isolated.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/13-light-shafts-isolated.png"
			
			sizes="100vw"
			alt="Light shafts isolated"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Light shafts isolated. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/13-light-shafts-isolated.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>When developing any ambient animation, considering performance is crucial, as even though CSS animations are lightweight, features like blur filters and drop shadows can still strain lower-powered devices. It’s also critical to consider accessibility, so <a href="https://www.smashingmagazine.com/2021/10/respecting-users-motion-preferences/">respect someone’s <code>prefers-reduced-motion</code> preferences</a>:</p>

<pre><code class="language-css">@media screen and (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
}
</code></pre>

<p>When an animation feature is purely decorative, consider adding <code>aria-hidden=&quot;true&quot;</code> to keep it from cluttering up the accessibility tree:</p>

<pre><code class="language-html">&lt;a href="javascript:void(0);" id="light-switch" aria-hidden="true"&gt;
  […]
&lt;/a&gt;
</code></pre>

<p>With Mike’s Orango Jones, ambient animation shifts from subtle atmosphere to playful storytelling. Light shafts and soft interactions weave narrative into the design without stealing focus, proving that animation can support both brand identity and user experience. See this animation <a href="https://stuffandnonsense.co.uk/lab/ambient-animations.html">in my lab</a>.</p>

<div class="partners__lead-place"></div>

<h2 id="epd">EPD</h2>

<p>Moving away from composers, EPD is a property investment company. They commissioned me to design creative concepts for a new website. A quick search for property investment companies will usually leave you feeling underwhelmed by their interchangeable website designs. They include full-width banners with faded stock photos of generic city skylines or ethnically diverse people shaking hands.</p>

<p>For EPD, I wanted to develop a distinctive visual style that the company could own, so I proposed graphic, stylised skylines that reflect both EPD’s brand and its global portfolio. I made them using various-sized circles that recall the company’s logo mark.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/14-design-epd.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/14-design-epd.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/14-design-epd.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/14-design-epd.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/14-design-epd.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/14-design-epd.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/14-design-epd.png"
			
			sizes="100vw"
			alt="Design for the property investment company"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      My design for the property investment company EPD. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/14-design-epd.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The point of an ambient animation is that it doesn’t dominate. It’s a background element and not a call to action. If someone’s eyes are drawn to it, it’s probably too much, so I dial back the animation until it feels like something you’d only catch if you’re really looking. I created three skyline designs, including Dubai, London, and Manchester.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/15-design-manchester-london.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="208"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/15-design-manchester-london.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/15-design-manchester-london.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/15-design-manchester-london.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/15-design-manchester-london.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/15-design-manchester-london.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/15-design-manchester-london.png"
			
			sizes="100vw"
			alt="Illustrations showing the skylines of Manchester and London"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Manchester and London. Two of my illustrations for EPD. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/15-design-manchester-london.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In each of these ambient animations, the wheels rotate and the large circles change colour at random intervals.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1129472862"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>To begin optimising this illustration for animation, I exported the base paths containing every element except the wheel:</p>

<pre><code class="language-svg">&lt;g id="banner-base&gt;
  &lt;path d="[…]"/&gt;
  &lt;path d="[…]"/&gt;
  &lt;path d="[…]"/&gt;
  […]
&lt;/g&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/16-manchester-illustration-base-layer.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="195"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/16-manchester-illustration-base-layer.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/16-manchester-illustration-base-layer.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/16-manchester-illustration-base-layer.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/16-manchester-illustration-base-layer.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/16-manchester-illustration-base-layer.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/16-manchester-illustration-base-layer.png"
			
			sizes="100vw"
			alt="Manchester illustration base layer"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      My Manchester illustration base layer. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/16-manchester-illustration-base-layer.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Next, I exported a layer containing the <code>circle</code> elements I want to change colour.</p>

<pre><code class="language-svg">&lt;g id="banner-dots"&gt;
  &lt;circle class="data-theme-fill" […]/&gt;
  &lt;circle class="data-theme-fill" […]/&gt;
  &lt;circle class="data-theme-fill" […]/&gt;
  […]
&lt;/g&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/17-circles-manchester-illustration.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="195"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/17-circles-manchester-illustration.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/17-circles-manchester-illustration.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/17-circles-manchester-illustration.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/17-circles-manchester-illustration.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/17-circles-manchester-illustration.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/17-circles-manchester-illustration.png"
			
			sizes="100vw"
			alt="Random-looking circles in Manchester illustration"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Random-looking circles in my Manchester illustration. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/17-circles-manchester-illustration.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Once again, I used GSAP to select groups of circles that flicker like lights across the skyline:</p>

<div class="break-out">
<pre><code class="language-javascript">function animateRandomDots() {
  const circles = gsap.utils.toArray("#banner-dots circle")
  const numberToAnimate = gsap.utils.random(3, 6, 1)
  const selected = gsap.utils.shuffle(circles).slice(0, numberToAnimate)
}
</code></pre>
</div>

<p>Then, at two-second intervals, the <code>fill</code> colour of those circles changes from the teal accent to the same off-white colour as the rest of my illustration:</p>

<pre><code class="language-javascript">gsap.to(selected, {
  fill: "color(display-p3 .439 .761 .733)",
  duration: 0.3,
  stagger: 0.05,
  onComplete: () =&gt; {
    gsap.to(selected, {
      fill: "color(display-p3 .949 .949 .949)",
      duration: 0.5,
      delay: 2
    })
  }
})

gsap.delayedCall(gsap.utils.random(1, 3), animateRandomDots) }
animateRandomDots()
</code></pre>

<p>The result is a skyline that gently flickers, as if the city itself is alive. Finally, I rotated the wheel. Here, there was no need to use GSAP as this is possible using CSS <code>rotate</code> alone:</p>

<div class="break-out">
<pre><code class="language-svg">&lt;g id="banner-wheel"&gt;
  &lt;path stroke="#F2F2F2" stroke-linecap="round" stroke-width="4" d="[…]"/&gt;
  &lt;path fill="#D8F76E" d="[…]"/&gt;
&lt;/g&gt;
</code></pre>
</div>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/18-rotating-wheel-manchester-illustration.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="195"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/18-rotating-wheel-manchester-illustration.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/18-rotating-wheel-manchester-illustration.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/18-rotating-wheel-manchester-illustration.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/18-rotating-wheel-manchester-illustration.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/18-rotating-wheel-manchester-illustration.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/18-rotating-wheel-manchester-illustration.png"
			
			sizes="100vw"
			alt="Rotating wheel in Manchester illustration"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Rotating wheel in my Manchester illustration. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-practical-applications-part2/18-rotating-wheel-manchester-illustration.png'>Large preview</a>)
    </figcaption>
  
</figure>

<pre><code class="language-css">
#banner-wheel {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: rotateWheel 30s linear infinite;
}

@keyframes rotateWheel {
  to { transform: rotate(360deg); }
}
</code></pre>

<p>CSS animations are lightweight and ideal for simple, repetitive effects, like fades and rotations. They’re easy to implement and don’t require libraries. GSAP, on the other hand, offers far more control as it can handle path morphing and sequence timelines. The choice of which to use depends on whether I need the <strong>precision of GSAP</strong> or the <strong>simplicity of CSS</strong>.</p>

<p>By keeping the wheel turning and the circles glowing, the skyline animations stay in the background yet give the design a distinctive feel. They avoid stock photo clichés while reinforcing EPD’s brand identity and are proof that, even in a conservative sector like property investment, ambient animation can add atmosphere without detracting from the message.</p>

<div class="partners__lead-place"></div>

<h2 id="wrapping-up">Wrapping up</h2>

<p>From Reuven’s musical textures to Mike’s narrative-driven Orango Jones and EPD’s glowing skylines, these projects show how <strong>ambient animation</strong> adapts to context. Sometimes it’s purely atmospheric, like drifting notes or rotating wheels; other times, it blends seamlessly with interaction, rewarding curiosity without getting in the way.</p>

<p>Whether it echoes a composer’s improvisation, serves as a playful narrative device, or adds subtle distinction to a conservative industry, the same principles hold true:</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aKeep%20motion%20slow,%20seamless,%20and%20purposeful%20so%20that%20it%20enhances,%20rather%20than%20distracts%20from,%20the%20design.%0a&url=https://smashingmagazine.com%2f2025%2f10%2fambient-animations-web-design-practical-applications-part2%2f">
      
Keep motion slow, seamless, and purposeful so that it enhances, rather than distracts from, the design.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Andy Clarke</author><title>Smashing Animations Part 5: Building Adaptive SVGs With `&lt;symbol>`, `&lt;use>`, And CSS Media Queries</title><link>https://www.smashingmagazine.com/2025/10/smashing-animations-part-5-building-adaptive-svgs/</link><pubDate>Mon, 06 Oct 2025 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/10/smashing-animations-part-5-building-adaptive-svgs/</guid><description>SVGs, they scale, yes, but how else can you make them adapt even better to several screen sizes? Web design pioneer &lt;a href="https://stuffandnonsense.co.uk">Andy Clarke&lt;/a> explains how he builds what he calls “adaptive SVGs” using &lt;code>&amp;lt;symbol&amp;gt;&lt;/code>, &lt;code>&amp;lt;use&amp;gt;&lt;/code>, and CSS Media Queries.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/10/smashing-animations-part-5-building-adaptive-svgs/" />
              <title>Smashing Animations Part 5: Building Adaptive SVGs With `&lt;symbol&gt;`, `&lt;use&gt;`, And CSS Media Queries</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Smashing Animations Part 5: Building Adaptive SVGs With `&lt;symbol&gt;`, `&lt;use&gt;`, And CSS Media Queries</h1>
                  
                    
                    <address>Andy Clarke</address>
                  
                  <time datetime="2025-10-06T13:00:00&#43;00:00" class="op-published">2025-10-06T13:00:00+00:00</time>
                  <time datetime="2025-10-06T13:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>I’ve written quite a lot recently about how I <a href="https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/">prepare and optimise</a> SVG code to use as static graphics or in <a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/">animations</a>. I love working with SVG, but there’s always been something about them that bugs me.</p>

<p>To illustrate how I build adaptive SVGs, I’ve selected an episode of <em>The Quick Draw McGraw Show</em> called “<a href="https://yowpyowp.blogspot.com/2012/06/quick-draw-mcgraw-bow-wow-bandit.html">Bow Wow Bandit</a>,” first broadcast in 1959.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png"
			
			sizes="100vw"
			alt="Bow Wow Bandit illustration"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Quick Draw McGraw Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In it, Quick Draw McGraw enlists his bloodhound Snuffles to rescue his sidekick Baba Looey. Like most Hanna-Barbera title cards of the period, the artwork was made by Lawrence (Art) Goble.</p>

<div class="refs">
  <ul><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/">Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS</a></li><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-2-css-masking-add-extra-dimension/">Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension</a></li><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-3-smil-not-dead/">Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead</a></li><li><a href="https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/">Smashing Animations Part 4: Optimising SVGs</a></li></ul>
</div>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png"
			
			sizes="100vw"
			alt="Quick Draw McGraw character pulling back on a dog leash attached to his bloodhound, Snuffles."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Andy Clarke’s Bow Wow Bandit Toon Title recreation (16:9). (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Let’s say I’ve designed an SVG scene like that one that’s based on Bow Wow Bandit, which has a 16:9 aspect ratio with a <code>viewBox</code> size of 1920×1080. This SVG scales up and down (the clue’s in the name), so it looks sharp when it’s gigantic and when it’s minute.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png"
			
			sizes="100vw"
			alt="16:9 aspect ration vs. 3:4."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Left: 16:9 aspect ratio loses its impact. Right: 3:4 format suits the screen size better. (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>But on small screens, the 16:9 aspect ratio (<a href="https://stuffandnonsense.co.uk/toon-titles/quick-draw-3a.html">live demo</a>) might not be the best format, and the image loses its impact. Sometimes, a portrait orientation, like 3:4, would suit the screen size better.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="729"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png"
			
			sizes="100vw"
			alt="Andy Clarke’s Bow Wow Bandit Toon Title recreation (3:4)."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Andy Clarke’s Bow Wow Bandit Toon Title recreation (3:4). (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>But, herein lies the problem, as it’s not easy to reposition internal elements for different screen sizes using just <code>viewBox</code>. That’s because in SVG, internal element positions are locked to the coordinate system from the original <code>viewBox</code>, so you can’t easily change their layout between, say, desktop and mobile. This is a problem because animations and interactivity often rely on element positions, which break when the <code>viewBox</code> changes.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png"
			
			sizes="100vw"
			alt="Left: 16:9 for larger screens. Right: 3:4 for smaller screens."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Left: 16:9 for larger screens. Right: 3:4 for smaller screens. (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>My challenge was to serve a 1080×1440 version of Bow Wow Bandit to smaller screens and a different one to larger ones. I wanted the position and size of internal elements &mdash; like Quick Draw McGraw and his dawg Snuffles &mdash; to change to best fit these two layouts. To solve this, I experimented with several alternatives.</p>

<p><strong>Note:</strong> Why are we not just using the <code>&lt;picture&gt;</code> with external SVGs? The <a href="https://www.smashingmagazine.com/2014/05/responsive-images-done-right-guide-picture-srcset/"><code>&lt;picture&gt;</code> element</a> is brilliant for responsive images, but it only works with raster formats (like JPEG or WebP) and external SVG files treated as images. That means that you can’t animate or style internal elements using CSS.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="showing-and-hiding-svg">Showing And Hiding SVG</h2>

<p>The most obvious choice was to include two different SVGs in my markup, one for small screens, the other for larger ones, then show or hide them using <a href="https://www.smashingmagazine.com/2018/02/media-queries-responsive-design-2018/">CSS and Media Queries</a>:</p>

<pre><code class="language-svg">&lt;svg id="svg-small" viewBox="0 0 1080 1440"&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;

&lt;svg id="svg-large" viewBox="0 0 1920 1080"&gt;
  &lt;!--... --&gt;
&lt;/svg&gt;


#svg-small { display: block; }
#svg-large { display: none; }

@media (min-width: 64rem) {
  #svg-small { display: none; }
  #svg-mobile { display: block; }
}
</code></pre>

<p>But using this method, both SVG versions are loaded, which, when the graphics are complex, means downloading lots and lots and lots of unnecessary code.</p>

<h2 id="replacing-svgs-using-javascript">Replacing SVGs Using JavaScript</h2>

<p>I thought about using JavaScript to swap in the larger SVG at a specified breakpoint:</p>

<pre><code class="language-javascript">if (window.matchMedia('(min-width: 64rem)').matches) {
  svgContainer.innerHTML = desktopSVG; 
} else {
  svgContainer.innerHTML = mobileSVG;
}
</code></pre>

<p>Leaving aside the fact that JavaScript would now be critical to how the design is displayed, both SVGs would usually be loaded anyway, which adds DOM complexity and unnecessary weight. Plus, maintenance becomes a problem as there are now two versions of the artwork to maintain, doubling the time it would take to update something as small as the shape of Quick Draw’s tail.</p>

<h2 id="the-solution-one-svg-symbol-library-and-multiple-uses">The Solution: One SVG Symbol Library And Multiple Uses</h2>

<p>Remember, my goal is to:</p>

<ul>
<li>Serve one version of Bow Wow Bandit to smaller screens,</li>
<li>Serve a different version to larger screens,</li>
<li>Define my artwork just once (DRY), and</li>
<li>Be able to resize and reposition elements.</li>
</ul>

<p>I don’t read about it enough, but the <code>&lt;symbol&gt;</code> element lets you define reusable SVG elements that can be hidden and reused to improve maintainability and reduce code bloat. They’re like components for SVG: <a href="https://css-tricks.com/svg-symbol-good-choice-icons/">create once and use wherever you need them</a>:</p>

<pre><code class="language-svg">&lt;svg xmlns="http://www.w3.org/2000/svg" style="display: none;"&gt;
  &lt;symbol id="quick-draw-body" viewBox="0 0 620 700"&gt;
    &lt;g class="quick-draw-body"&gt;[…]&lt;/g&gt;
  &lt;/symbol&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;

&lt;use href="#quick-draw-body" /&gt;
</code></pre>

<p>A <code>&lt;symbol&gt;</code> is like storing a character in a library. I can reference it as many times as I need, to keep my code consistent and lightweight. Using <code>&lt;use&gt;</code> elements, I can insert the same symbol multiple times, at different positions or sizes, and even in different SVGs.</p>

<p>Each <code>&lt;symbol&gt;</code> must have its own <code>viewBox</code>, which defines its internal coordinate system. That means paying special attention to how SVG elements are exported from apps like Sketch.</p>

<div class="partners__lead-place"></div>

<h2 id="exporting-for-individual-viewboxes">Exporting For Individual Viewboxes</h2>

<p>I wrote before about <a href="https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/">how I export elements</a> in layers to make working with them easier. That process is a little different when creating symbols.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png"
			
			sizes="100vw"
			alt=""
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      My usual process of exporting elements from Sketch. (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Ordinarily, I would export all my elements using the same <code>viewBox</code>size. But when I’m creating a <code>symbol</code>, I need it to have its own specific <code>viewBox</code>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png"
			
			sizes="100vw"
			alt="Exporting elements from Sketch as individual SVG files."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Exporting elements from Sketch as individual SVG files. (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>So I export each element as an individually sized SVG, which gives me the dimensions I need to convert its content into a <code>symbol</code>. Let’s take the SVG of Quick Draw McGraw’s hat, which has a <code>viewBox</code> size of 294×182:</p>

<pre><code class="language-svg">&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 294 182"&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;
</code></pre>

<p>I swap the SVG tags for <code>&lt;symbol&gt;</code> and add its artwork to my SVG library:</p>

<pre><code class="language-svg">&lt;svg xmlns="http://www.w3.org/2000/svg" style="display: none;"&gt;
  &lt;symbol id="quick-draw-hat" viewBox="0 0 294 182"&gt;
    &lt;g class="quick-draw-hat"&gt;[…]&lt;/g&gt;
  &lt;/symbol&gt;
&lt;/svg&gt;
</code></pre>

<p>Then, I repeat the process for all the remaining elements in my artwork. Now, if I ever need to update any of my symbols, the changes will be automatically applied to every instance it’s used.</p>

<h2 id="using-a-symbol-in-multiple-svgs">Using A <code>&lt;symbol&gt;</code> In Multiple SVGs</h2>

<p>I wanted my elements to appear in both versions of Bow Wow Bandit, one arrangement for smaller screens and an alternative arrangement for larger ones. So, I create both SVGs:</p>

<pre><code class="language-svg">&lt;svg class="svg-small" viewBox="0 0 1080 1440"&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;

&lt;svg class="svg-large" viewBox="0 0 1920 1080"&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;
</code></pre>

<p>…and insert links to my symbols in both:</p>

<pre><code class="language-svg">&lt;svg class="svg-small" viewBox="0 0 1080 1440"&gt;
  &lt;use href="#quick-draw-hat" /&gt;
&lt;/svg&gt;

&lt;svg class="svg-large" viewBox="0 0 1920 1080"&gt;
  &lt;use href="#quick-draw-hat" /&gt;
&lt;/svg&gt;
</code></pre>

<h2 id="positioning-symbols">Positioning Symbols</h2>

<p>Once I’ve placed symbols into my layout using <code>&lt;use&gt;</code>, my next step is to position them, which is especially important if I want alternative layouts for different screen sizes. Symbols behave like <code>&lt;g&gt;</code> groups, so I can scale and move them using attributes like <code>width</code>, <code>height</code>, and <code>transform</code>:</p>

<div class="break-out">
<pre><code class="language-svg">&lt;svg class="svg-small" viewBox="0 0 1080 1440"&gt;
  &lt;use href="#quick-draw-hat" width="294" height="182" transform="translate(-30,610)"/&gt;
&lt;/svg&gt;

&lt;svg class="svg-large" viewBox="0 0 1920 1080"&gt;
  &lt;use href="#quick-draw-hat" width="294" height="182" transform="translate(350,270)"/&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>I can place each <code>&lt;use&gt;</code> element independently using <code>transform</code>. This is powerful because rather than repositioning elements inside my SVGs, I move the <code>&lt;use&gt;</code> references. My internal layout stays clean, and the file size remains small because I’m not duplicating artwork. A browser only loads it once, which reduces bandwidth and speeds up page rendering. And because I’m always referencing the same <code>symbol</code>, their appearance stays consistent, whatever the screen size.</p>

<h2 id="animating-use-elements">Animating <code>&lt;use&gt;</code> Elements</h2>

<p>Here’s where things got tricky. I wanted to animate parts of my characters &mdash; like Quick Draw’s hat tilting and his legs kicking. But when I added CSS animations targeting internal elements inside a <code>&lt;symbol&gt;</code>, nothing happened.</p>

<p><strong>Tip:</strong> You can animate the <code>&lt;use&gt;</code> element itself, but not elements inside the <code>&lt;symbol&gt;</code>. If you want individual parts to move, make them their own symbols and animate each <code>&lt;use&gt;</code>.</p>

<p>Turns out, you can’t style or animate a <code>&lt;symbol&gt;</code>, because <code>&lt;use&gt;</code> creates shadow DOM clones that aren’t easily targetable. So, I had to get sneaky. Inside each <code>&lt;symbol&gt;</code> in my library SVG, I added a <code>&lt;g&gt;</code> element around the part I wanted to animate:</p>

<pre><code class="language-svg">&lt;symbol id="quick-draw-hat" viewBox="0 0 294 182"&gt;
  &lt;g class="quick-draw-hat"&gt;
    &lt;!-- ... --&gt;
  &lt;/g&gt;
&lt;/symbol&gt;
</code></pre>

<p>…and animated it using an attribute substring selector, targeting the <code>href</code> attribute of the <code>use</code> element:</p>

<pre><code class="language-css">use[href="#quick-draw-hat"] {
  animation-delay: 0.5s;
  animation-direction: alternate;
  animation-duration: 1s;
  animation-iteration-count: infinite;
  animation-name: hat-rock;
  animation-timing-function: ease-in-out;
  transform-origin: center bottom;
}

@keyframes hat-rock {
from { transform: rotate(-2deg); }
to   { transform: rotate(2deg); } }
</code></pre>

<div class="partners__lead-place"></div>

<h2 id="media-queries-for-display-control">Media Queries For Display Control</h2>

<p>Once I’ve created my two visible SVGs &mdash; one for small screens and one for larger ones &mdash; the final step is deciding which version to show at which screen size. I use CSS Media Queries to hide one SVG and show the other. I start by showing the small-screen SVG by default:</p>

<pre><code class="language-css">.svg-small { display: block; }
.svg-large { display: none; }
</code></pre>

<p>Then I use a <code>min-width</code> media query to switch to the large-screen SVG at <code>64rem</code> and above:</p>

<pre><code class="language-css">@media (min-width: 64rem) {
  .svg-small { display: none; }
  .svg-large { display: block; }
}
</code></pre>

<p>This ensures there’s only ever one SVG visible at a time, keeping my layout simple and the DOM free from unnecessary clutter. And because both visible SVGs reference the same hidden <code>&lt;symbol&gt;</code> library, the browser only downloads the artwork once, regardless of how many <code>&lt;use&gt;</code> elements appear across the two layouts.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png"
			
			sizes="100vw"
			alt="The final adaptive SVG."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      View the final adaptive SVG on my <a href='https://stuffandnonsense.co.uk/toon-titles/quick-draw-3.html'>Toon Titles website</a>. (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="wrapping-up">Wrapping Up</h2>

<p>By combining <code>&lt;symbol&gt;</code>, <code>&lt;use&gt;</code>, CSS Media Queries, and specific transforms, I can build <strong>adaptive SVGs</strong> that reposition their elements without duplicating content, loading extra assets, or relying on JavaScript. I need to define each graphic only once in a hidden symbol library. Then I can reuse those graphics, as needed, inside several visible SVGs. With CSS doing the layout switching, the <strong>result is fast and flexible</strong>.</p>

<p>It’s a reminder that some of the most powerful techniques on the web don’t need big frameworks or complex tooling &mdash; just a bit of SVG know-how and a clever use of the basics.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Andy Clarke</author><title>Ambient Animations In Web Design: Principles And Implementation (Part 1)</title><link>https://www.smashingmagazine.com/2025/09/ambient-animations-web-design-principles-implementation/</link><pubDate>Mon, 22 Sep 2025 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/09/ambient-animations-web-design-principles-implementation/</guid><description>Creating motion can be tricky. Too much and it’s distracting. Too little and a design feels flat. Ambient animations are the middle ground &amp;mdash; subtle, slow-moving details that add atmosphere without stealing the show. In this article, web design pioneer &lt;a href="https://stuffandnonsense.co.uk">Andy Clarke&lt;/a> introduces the concept of ambient animations and explains how to implement them.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/09/ambient-animations-web-design-principles-implementation/" />
              <title>Ambient Animations In Web Design: Principles And Implementation (Part 1)</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Ambient Animations In Web Design: Principles And Implementation (Part 1)</h1>
                  
                    
                    <address>Andy Clarke</address>
                  
                  <time datetime="2025-09-22T13:00:00&#43;00:00" class="op-published">2025-09-22T13:00:00+00:00</time>
                  <time datetime="2025-09-22T13:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>Unlike <em>timeline-based</em> animations, which tell stories across a sequence of events, or <em>interaction</em> animations that are triggered when someone touches something, <strong>ambient animations</strong> are the kind of passive movements you might not notice at first. But, they make a design look alive in subtle ways.</p>

<p>In an ambient animation, elements might subtly transition between colours, move slowly, or gradually shift position. Elements can appear and disappear, change size, or they could rotate slowly.</p>

<p>Ambient animations aren’t intrusive; they don’t demand attention, aren’t distracting, and don’t interfere with what someone’s trying to achieve when they use a product or website. They can be playful, too, making someone smile when they catch sight of them. That way, ambient animations <strong>add depth to a brand’s personality</strong>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="399"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png"
			
			sizes="100vw"
			alt="A three-page spread of a Quick Draw McGraw comic book including the animated cover and first two pages."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Hanna-Barbera’s Quick Draw McGraw © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p class="c-pre-sidenote--left">To illustrate the concept of ambient animations, I’ve recreated the cover of a <a href="https://en.wikipedia.org/wiki/Quick_Draw_McGraw"><em>Quick Draw McGraw</em></a> <a href="https://dn720005.ca.archive.org/0/items/QuickDrawMcGrawCharlton/Quick%20Draw%20McGraw%20%233%20%28Charlton%201971%29.pdf">comic book</a> (PDF) as a CSS/SVG animation. The comic was published by Charlton Comics in 1971, and, being printed, these characters didn’t move, making them ideal candidates to transform into ambient animations.</p>
<p class="c-sidenote c-sidenote--right"><strong>FYI</strong>: Original cover artist <a href="https://www.lambiek.net/artists/d/dirgo_ray.htm">Ray Dirgo</a> was best known for his work drawing Hanna-Barbera characters for Charlton Comics during the 1970s. Ray passed away in 2000 at the age of 92. He outlived Charlton Comics, which went out of business in 1986, and DC Comics acquired its characters.</p>

<p><strong>Tip</strong>: You can view the complete ambient animation <a href="https://codepen.io/malarkey/pen/NPGrWVy">code on CodePen</a>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png"
			
			sizes="100vw"
			alt="Quick Draw McGraw ambient animations."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Quick Draw McGraw ambient animations. (<a href='https://codepen.io/malarkey/pen/NPGrWVy'>Live Demo</a>) (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png'>Large preview</a>)
    </figcaption>
  
</figure>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="choosing-elements-to-animate">Choosing Elements To Animate</h2>

<p>Not everything on a page or in a graphic needs to move, and part of designing an ambient animation is <strong>knowing when to stop</strong>. The trick is to pick elements that lend themselves naturally to subtle movement, rather than forcing motion into places where it doesn’t belong.</p>

<h3 id="natural-motion-cues">Natural Motion Cues</h3>

<p>When I’m deciding what to animate, I look for natural motion cues and think about when something would move naturally in the real world. I ask myself: <em>“Does this thing have weight?”</em>, <em>“Is it flexible?”</em>, and <em>“Would it move in real life?”</em> If the answer’s <em>“yes,”</em> it’ll probably feel right if it moves. There are several motion cues in Ray Dirgo’s cover artwork.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png"
			
			sizes="100vw"
			alt="Vibrantly illustrated pipe adorned with two feathers on the end against a silhouetted toon title card."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Pipe and feathers swing slightly. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>For example, the peace pipe Quick Draw’s puffing on has two feathers hanging from it. They swing slightly left and right by three degrees as the pipe moves, just like real feathers would.</p>

<div class="break-out">
<pre><code class="language-css">&#35;quick-draw-pipe {
  animation: quick-draw-pipe-rotate 6s ease-in-out infinite alternate;
}

@keyframes quick-draw-pipe-rotate {
  0% { transform: rotate(3deg); }
  100% { transform: rotate(-3deg); }
}

&#35;quick-draw-feather-1 {
  animation: quick-draw-feather-1-rotate 3s ease-in-out infinite alternate;
}

&#35;quick-draw-feather-2 {
  animation: quick-draw-feather-2-rotate 3s ease-in-out infinite alternate;
}

@keyframes quick-draw-feather-1-rotate {
  0% { transform: rotate(3deg); }
  100% { transform: rotate(-3deg); }
}

@keyframes quick-draw-feather-2-rotate {
  0% { transform: rotate(-3deg); }
  100% { transform: rotate(3deg); }
}
</code></pre>
</div>

<h3 id="atmosphere-not-action">Atmosphere, Not Action</h3>

<p>I often choose elements or decorative details that add to the vibe but don’t fight for attention.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aAmbient%20animations%20aren%e2%80%99t%20about%20signalling%20to%20someone%20where%20they%20should%20look;%20they%e2%80%99re%20about%20creating%20a%20mood.%20%0a&url=https://smashingmagazine.com%2f2025%2f09%2fambient-animations-web-design-principles-implementation%2f">
      
Ambient animations aren’t about signalling to someone where they should look; they’re about creating a mood. 

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<p>Here, the chief slowly and subtly rises and falls as he puffs on his pipe.</p>

<pre><code class="language-css">&#35;chief {
  animation: chief-rise-fall 3s ease-in-out infinite alternate;
}

@keyframes chief-group-rise-fall {
  0% { transform: translateY(0); }
  100% { transform: translateY(-20px); }
}
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png"
			
			sizes="100vw"
			alt="An illustrated Indian chief seated and puffing on a pipe against a silhouetted toon title card."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The chief rises and falls as he puffs on his pipe. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>For added effect, the feather on his head also moves in time with his rise and fall:</p>

<div class="break-out">
<pre><code class="language-css">&#35;chief-feather-1 {
  animation: chief-feather-1-rotate 3s ease-in-out infinite alternate;
}

&#35;chief-feather-2 {
  animation: chief-feather-2-rotate 3s ease-in-out infinite alternate;
}

@keyframes chief-feather-1-rotate {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(-9deg); }
}

@keyframes chief-feather-2-rotate {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(9deg); }
}
</code></pre>
</div>

<h3 id="playfulness-and-fun">Playfulness And Fun</h3>

<p>One of the things I love most about ambient animations is how they bring fun into a design. They’re an opportunity to <strong>demonstrate personality</strong> through playful details that make people smile when they notice them.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png"
			
			sizes="100vw"
			alt="Closeup of the illustrated chief’s head and face."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The chief’s eyebrows rise and fall, and his eyes cross. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Take a closer look at the chief, and you might spot his eyebrows raising and his eyes crossing as he puffs hard on his pipe. Quick Draw’s eyebrows also bounce at what look like random intervals.</p>

<pre><code class="language-css">&#35;quick-draw-eyebrow {
  animation: quick-draw-eyebrow-raise 5s ease-in-out infinite;
}

@keyframes quick-draw-eyebrow-raise {
  0%, 20%, 60%, 100% { transform: translateY(0); }
  10%, 50%, 80% { transform: translateY(-10px); }
}
</code></pre>

<div class="partners__lead-place"></div>

<h2 id="keep-hierarchy-in-mind">Keep Hierarchy In Mind</h2>

<p>Motion draws the eye, and even subtle movements have a visual weight. So, I reserve the most obvious animations for elements that I need to create the biggest impact.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png"
			
			sizes="100vw"
			alt="Illustrated Quick Draw McGraw holding the feather-adorned pipe with dizzy eyes veering right."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Quick Draw McGraw wobbles under the influence of his pipe. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Smoking his pipe clearly has a big effect on Quick Draw McGraw, so to demonstrate this, I wrapped his elements &mdash; including his pipe and its feathers &mdash; within a new SVG group, and then I made that wobble.</p>

<pre><code class="language-css">&#35;quick-draw-group {
  animation: quick-draw-group-wobble 6s ease-in-out infinite;
}

@keyframes quick-draw-group-wobble {
  0% { transform: rotate(0deg); }
  15% { transform: rotate(2deg); }
  30% { transform: rotate(-2deg); }
  45% { transform: rotate(1deg); }
  60% { transform: rotate(-1deg); }
  75% { transform: rotate(0.5deg); }
  100% { transform: rotate(0deg); }
}
</code></pre>

<p>Then, to emphasise this motion, I mirrored those values to wobble his shadow:</p>

<pre><code class="language-css">&#35;quick-draw-shadow {
  animation: quick-draw-shadow-wobble 6s ease-in-out infinite;
}

@keyframes quick-draw-shadow-wobble {
  0% { transform: rotate(0deg); }
  15% { transform: rotate(-2deg); }
  30% { transform: rotate(2deg); }
  45% { transform: rotate(-1deg); }
  60% { transform: rotate(1deg); }
  75% { transform: rotate(-0.5deg); }
  100% { transform: rotate(0deg); }
}
</code></pre>

<h2 id="apply-restraint">Apply Restraint</h2>

<p>Just because something can be animated doesn’t mean it should be. When creating an ambient animation, I study the image and note the elements where subtle motion might add life. I keep in mind the questions: <em>“What’s the story I’m telling? Where does movement help, and when might it become distracting?”</em></p>

<p>Remember, restraint isn’t just about doing less; it’s about doing the right things less often.</p>

<h2 id="layering-svgs-for-export">Layering SVGs For Export</h2>

<p>In “<a href="https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/">Smashing Animations Part 4: Optimising SVGs</a>,” I wrote about the process I rely on to <em>“prepare, optimise, and structure SVGs for animation.”</em> When elements are crammed into a single SVG file, they can be a nightmare to navigate. Locating a specific path or group can feel like searching for a needle in a haystack.</p>

<blockquote>That’s why I develop my SVGs in layers, exporting and optimising one set of elements at a time &mdash; always in the order they’ll appear in the final file. This lets me build the master SVG gradually by pasting it in each cleaned-up section.</blockquote>

<p>I start by exporting background elements, optimising them, adding class and ID attributes, and pasting their code into my SVG file.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png"
			
			sizes="100vw"
			alt="The toon title card with the chief and Quick Draw characters cut out with their shapes remaining."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Exporting background elements. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Then, I export elements that often stay static or move as groups, like the chief and Quick Draw McGraw.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png"
			
			sizes="100vw"
			alt="Showing Quick Draw pasted to the toon title card’s foreground, minus details including the pipe he is holding and his eyeballs."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Exporting larger groups. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Before finally exporting, naming, and adding details, like Quick Draw’s pipe, eyes, and his stoned sparkles.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png"
			
			sizes="100vw"
			alt="Showing Quick Draw in the same toon title card but including the details that were left out before."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Adding details. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Since I export each layer from the same-sized artboard, I don’t need to worry about alignment or positioning issues as they all slot into place automatically.</p>

<h2 id="implementing-ambient-animations">Implementing Ambient Animations</h2>

<p>You don’t need an animation framework or library to add ambient animations to a project. Most of the time, all you’ll need is a well-prepared SVG and some thoughtful CSS.</p>

<p>But, let’s start with the SVG. The key is to group elements logically and give them meaningful class or ID attributes, which act as animation hooks in the CSS. For this animation, I gave every moving part its own identifier like <code>#quick-draw-tail</code> or <code>#chief-smoke-2</code>. That way, I could target exactly what I needed without digging through the DOM like a raccoon in a trash can.</p>

<p>Once the SVG is set up, CSS does most of the work. I can use <code>@keyframes</code> for more expressive movement, or <code>animation-delay</code> to simulate randomness and stagger timings. The trick is to keep everything subtle and remember I’m not animating for attention, I’m animating for atmosphere.</p>

<p>Remember that most ambient animations loop continuously, so they should be <strong>lightweight</strong> and <strong>performance-friendly</strong>. And of course, <a href="https://www.smashingmagazine.com/2021/10/respecting-users-motion-preferences/">it’s good practice to respect users who’ve asked for less motion</a>. You can wrap your animations in an <code>@media prefers-reduced-motion</code> query so they only run when they’re welcome.</p>

<div class="break-out">
<pre><code class="language-javascript">@media (prefers-reduced-motion: no-preference) {
  &#35;quick-draw-shadow {
    animation: quick-draw-shadow-wobble 6s ease-in-out infinite;
  }
}
</code></pre>
</div>

<p>It’s a small touch that’s easy to implement, and it makes your designs more inclusive.</p>

<div class="partners__lead-place"></div>

<h2 id="ambient-animation-design-principles">Ambient Animation Design Principles</h2>

<p>If you want your animations to feel ambient, more like atmosphere than action, it helps to follow a few principles. These aren’t hard and fast rules, but rather things I’ve learned while animating smoke, sparkles, eyeballs, and eyebrows.</p>

<h3 id="keep-animations-slow-and-smooth">Keep Animations Slow And Smooth</h3>

<p>Ambient animations should feel relaxed, so use <strong>longer durations</strong> and choose <strong>easing curves that feel organic</strong>. I often use <code>ease-in-out</code>, but <a href="https://www.smashingmagazine.com/2022/10/advanced-animations-css/">cubic Bézier curves</a> can also be helpful when you want a more relaxed feel and the kind of movements you might find in nature.</p>

<h3 id="loop-seamlessly-and-avoid-abrupt-changes">Loop Seamlessly And Avoid Abrupt Changes</h3>

<p>Hard resets or sudden jumps can ruin the mood, so if an animation loops, ensure it cycles smoothly. You can do this by <strong>matching start and end keyframes</strong>, or by setting the <code>animation-direction</code> to <code>alternate</code> the value so the animation plays forward, then back.</p>

<h3 id="use-layering-to-build-complexity">Use Layering To Build Complexity</h3>

<p>A single animation might be boring. Five subtle animations, each on separate layers, can feel rich and alive. Think of it like building a sound mix &mdash; you want <strong>variation in rhythm, tone, and timing</strong>. In my animation, sparkles twinkle at varying intervals, smoke curls upward, feathers sway, and eyes boggle. Nothing dominates, and each motion plays its small part in the scene.</p>

<h3 id="avoid-distractions">Avoid Distractions</h3>

<p>The point of an ambient animation is that it doesn’t dominate. It’s a <strong>background element</strong> and not a call to action. If someone’s eyes are drawn to a raised eyebrow, it’s probably too much, so dial back the animation until it feels like something you’d only catch if you’re really looking.</p>

<h3 id="consider-accessibility-and-performance">Consider Accessibility And Performance</h3>

<p>Check <code>prefers-reduced-motion</code>, and don’t assume everyone’s device can handle complex animations. SVG and CSS are light, but things like blur filters and drop shadows, and complex CSS animations can still tax lower-powered devices. When an animation is purely decorative, consider adding <code>aria-hidden=&quot;true&quot;</code> to keep it from cluttering up the accessibility tree.</p>

<h2 id="quick-on-the-draw">Quick On The Draw</h2>

<p>Ambient animation is like seasoning on a great dish. It’s the pinch of salt you barely notice, but you’d miss when it’s gone. It doesn’t shout, it whispers. It doesn’t lead, it lingers. It’s floating smoke, swaying feathers, and sparkles you catch in the corner of your eye. And when it’s done well, ambient animation <strong>adds personality to a design without asking for applause</strong>.</p>

<p>Now, I realise that not everyone needs to animate cartoon characters. So, in part two, I’ll share how I created animations for several recent client projects. Until next time, if you’re crafting an illustration or working with SVG, ask yourself: <strong>What would move if this were real?</strong> Then animate just that. Make it slow and soft. Keep it ambient.</p>

<p>You can view the complete ambient animation <a href="https://codepen.io/malarkey/pen/NPGrWVy">code on CodePen</a>.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Marius Sarca</author><title>Creating Elastic And Bounce Effects With Expressive Animator</title><link>https://www.smashingmagazine.com/2025/09/creating-elastic-bounce-effects-expressive-animator/</link><pubDate>Mon, 15 Sep 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/09/creating-elastic-bounce-effects-expressive-animator/</guid><description>Elastic and bounce effects have long been among the most desirable but time-consuming techniques in motion design. Expressive Animator streamlines the process, making it possible to produce lively animations in seconds, bypassing the tedious work of manual keyframe editing.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/09/creating-elastic-bounce-effects-expressive-animator/" />
              <title>Creating Elastic And Bounce Effects With Expressive Animator</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Creating Elastic And Bounce Effects With Expressive Animator</h1>
                  
                    
                    <address>Marius Sarca</address>
                  
                  <time datetime="2025-09-15T10:00:00&#43;00:00" class="op-published">2025-09-15T10:00:00+00:00</time>
                  <time datetime="2025-09-15T10:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                <p>This article is sponsored by <b>Expressive</b></p>
                

<p>In the world of modern web design, SVG images are used everywhere, from illustrations to icons to background effects, and are universally prized for their crispness and lightweight size. While static SVG images play an important role in web design, most of the time their true potential is unlocked only when they are combined with motion.</p>

<p>Few things add more life and personality to a website than a well-executed SVG animation. But not all animations have the same impact in terms of digital experience. For example, <strong>elastic and bounce effects</strong> have a unique appeal in motion design because they bring a <strong>sense of realism into movement</strong>, making animations more engaging and memorable.</p>

<figure><a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/grumpy-egg.gif"><img src="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/grumpy-egg-800.gif" width="800" height="800" alt="Grumpy Egg" /></a><figcaption>(<a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/grumpy-egg.gif">Large preview</a>)</figcaption></figure>

<p>However, anyone who has dived into animating SVGs knows <a href="https://www.smashingmagazine.com/2023/02/putting-gears-motion-animating-cars-with-html-svg/">the technical hurdles involved</a>. Creating a convincing elastic or bounce effect traditionally requires handling complex CSS keyframes or wrestling with JavaScript animation libraries. Even when using an SVG animation editor, it will most likely require you to manually add the keyframes and adjust the easing functions between them, which can become a time-consuming process of trial and error, no matter the level of experience you have.</p>

<p>This is where Expressive Animator shines. It allows creators to apply elastic and bounce effects <strong>in seconds</strong>, bypassing the tedious work of manual keyframe editing. And the result is always exceptional: animations that feel <em>alive</em>, produced with a fraction of the effort.</p>

<h2 id="using-expressive-animator-to-create-an-elastic-effect">Using Expressive Animator To Create An Elastic Effect</h2>

<p>Creating an elastic effect in Expressive Animator is remarkably simple, fast, and intuitive, since the effect is built right into the software as an easing function. This means you only need two keyframes (start and end) to make the effect, and the software will automatically handle the springy motion in between. Even better, the elastic easing can be applied to <strong>any animatable property</strong> (e.g., position, scale, rotation, opacity, morph, etc.), giving you a consistent way to add it to your animations.</p>

<p>Before we dive into the tutorial, take a look at the video below to see what you will learn to create and the entire process from start to finish.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1116135653"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>First things first, let’s set the scene. For this, we’ll <a href="https://expressive.app/expressive-animator/docs/v1/projects/create/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">create a new project</a> by pressing <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> + <kbd>P</kbd> and configuring it in the “Create New Project” dialog that pops up. For frame size, we’ll choose 1080×1080, for a duration of 00:01:30, and we’ll let the frame rate remain unchanged at 60 frames per second (fps).</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="467"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png"
			
			sizes="100vw"
			alt="“Create New Project” dialog"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Once you hit the “Create project” button, you can use the <a href="https://expressive.app/expressive-animator/docs/v1/tools/pen-tool/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">Pen</a> and <a href="https://expressive.app/expressive-animator/docs/v1/tools/ellipse-tool/">Ellipse</a> tools to create the artwork that will be animated, or you can simply copy and paste the artwork below.</p>

<figure class="break-out">
	<p data-height="600"
	data-theme-id="light"
	data-slug-hash="pvjmwxv"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Effects With Expressive Animator - Artwork for Animation](https://codepen.io/smashingmag/pen/pvjmwxv).</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/pvjmwxv">Effects With Expressive Animator - Artwork for Animation</a>.</figcaption>
</figure>

<p>Now that everything has been set up, let’s create the animation. Make sure that snapping and auto-record are enabled, then move the playhead to 01:00f. By <a href="https://expressive.app/expressive-animator/docs/v1/canvas/snapping/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">enabling snapping</a>, you will be able to perfectly align nodes and graphic objects on the canvas. On the other hand, as the name suggests, auto-record tracks every change you make to the artwork and adds the appropriate keyframes on the timeline.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="467"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png"
			
			sizes="100vw"
			alt="Screenshot with snapping and auto-record are enabled and the playhead moved to 01:00f"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Press the <kbd>A</kbd> key on your keyboard to switch to the <a href="https://expressive.app/expressive-animator/docs/v1/tools/node-tool/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">Node tool</a>, then select the String object and move its handle to the center-right point of the artboard. Don’t worry about precision, as the snapping will do all the heavy lifting for you. This will bend the shape and add keyframes for the Morph animator.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="467"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png"
			
			sizes="100vw"
			alt="Screenshot with the String object and its handle moved to the center-right point of the artboard"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Next, press the <kbd>V</kbd> key on your keyboard to switch to the <a href="https://expressive.app/expressive-animator/docs/v1/tools/selection-tool/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">Selection tool</a>. With this tool enabled, select the Ball, move it to the right, and place it in the middle of the string. Once again, snapping will do all the hard work, allowing you to position the ball exactly where you want to, while auto-recording automatically adds the appropriate keyframes.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="467"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png"
			
			sizes="100vw"
			alt="Screenshot with the Ball selected and moved to the middle of the string"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You can now replay the animation and disable auto-recording by clicking on the Auto-Record button again.</p>

<p>As you can see when replaying, the direction in which the String and Ball objects are moving is wrong. Fortunately, we can fix this extremely easily just by reversing the keyframes. To do this, select the keyframes in the timeline and right-click to open the context menu and choose Reverse. This will reverse the keyframes, and if you replay the animation, you will see that the direction is now correct.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="467"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png"
			
			sizes="100vw"
			alt="Screenshot with the context menu where you can choose Reverse"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>With this out of the way, we can finally add the elastic effect. Select all the keyframes in the timeline and click on the Custom easing button to open a dialog with easing options. From the dialog, choose Elastic and set the oscillations to 4 and the stiffness to 2.5.</p>

<p>That’s it! Click anywhere outside the easing dialog to close it and replay the animation to see the result.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="467"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png"
			
			sizes="100vw"
			alt="Selected custom easing button that opened a dialog with easing options"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><a href="https://expressive.app/expressive-animator/docs/v1/export/svg/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">The animation can be exported as well.</a> Press <kbd>Cmd</kbd>/<kbd>Ctrl</kbd> + <kbd>E</kbd> on your keyboard to open the export dialog and choose from various export options, ranging from vectorized formats, such as <a href="https://expressive.app/expressive-animator/docs/v1/export/svg/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">SVG</a> and <a href="https://expressive.app/expressive-animator/docs/v1/export/lottie/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">Lottie</a>, to rasterized formats, such as <a href="https://expressive.app/expressive-animator/docs/v1/export/image/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">GIF</a> and <a href="https://expressive.app/expressive-animator/docs/v1/export/video/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">video</a>.</p>

<p>For this specific animation, we’re going to choose the SVG export format. Expressive Animator allows you to choose between three different types of SVG, depending on the technology used for animation: <a href="https://expressive.app/expressive-animator/docs/v1/export/svg/smil/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">SMIL</a>, <a href="https://expressive.app/expressive-animator/docs/v1/export/svg/css/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">CSS</a>, or <a href="https://expressive.app/expressive-animator/docs/v1/export/svg/js/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">JavaScript</a>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="467"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png"
			
			sizes="100vw"
			alt="Export settings in the Expressive Animator"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Each of these technologies has different strengths and weaknesses, but for this tutorial, we are going to choose SMIL. This is because SMIL-based animations are widely supported, even on Safari browsers, and can be used as background images or embedded in HTML pages using the <code>&lt;img&gt;</code>  tag. In fact, <a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-3-smil-not-dead/">Andy Clarke recently wrote all about SMIL animations here at Smashing Magazine</a> if you want a full explanation of how it works.</p>

<p>You can visualize the exported SVG in the following CodePen demo:</p>

<figure class="break-out">
	<p data-height="600"
	data-theme-id="light"
	data-slug-hash="GgpaEyG"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Expressive Animator - Exported SVG](https://codepen.io/smashingmag/pen/GgpaEyG).</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/GgpaEyG">Expressive Animator - Exported SVG</a>.</figcaption>
</figure>

<h2 id="expressive-animator-for-bounce-and-other-effects">Expressive Animator For Bounce And Other Effects</h2>

<p>Adding a bounce effect to an animation is very similar to the process we just covered for creating an elastic effect, since both are built into Expressive Animator as easing functions. Just like elastic, bounce easing can be applied to any animatable property, giving you quick ways to create realistic motion.</p>

<p>Beyond these two effects, Expressive Animator also offers other easing options that can shape the personality of your animation, like Back, Steps, Sinc, just to name a few.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="757"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png"
			
			sizes="100vw"
			alt="Easing functions in the Expressive Animator"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="conclusion">Conclusion</h2>

<p>Elastic and bounce effects have long been among the most desirable but time-consuming techniques in motion design. By integrating them directly into its easing functions, Expressive Animator removes the complexity of manual keyframe manipulation and transforms what used to be a technical challenge into a creative opportunity.</p>

<p>The best part is that getting started with Expressive Animator comes with zero risk. The software offers a full 7&ndash;day <strong>free trial without requiring an account</strong>, so you can download it instantly and begin experimenting with your own designs right away. After the trial ends, you can buy Expressive Animator with a one-time payment, <strong>no subscription required</strong>. This will give you a perpetual license covering both Windows and macOS.</p>

<p>To help you get started even faster, I’ve prepared some extra resources for you. You’ll find the source files for the animations created in this tutorial, along with a curated list of useful links that will guide you further in exploring Expressive Animator and SVG animation. These materials are meant to give you a solid starting point so you can learn, experiment, and build on your own with confidence.</p>

<ul>
<li>Grumpy Egg: The <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/grumpy-egg.eaf" download><code>.eaf</code></a> source file for the sample animation presented at the beginning of this article.</li>
<li>Elastic Effect: Another <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/elastic-effect.eaf" download><code>.eaf</code></a> file, this time for the animation we made in this tutorial.</li>
<li><a href="https://expressive.app/expressive-animator/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">Get started with Expressive Animator</a></li>
<li>Expressive Animator <a href="https://expressive.app/expressive-animator/docs/v1/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">Documentation</a></li>
</ul>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Myriam Frisano</author><title>Decoding The SVG &lt;code>path&lt;/code> Element: Line Commands</title><link>https://www.smashingmagazine.com/2025/06/decoding-svg-path-element-line-commands/</link><pubDate>Mon, 09 Jun 2025 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/06/decoding-svg-path-element-line-commands/</guid><description>SVG is easy — until you meet &lt;code>path&lt;/code>. However, it’s not as confusing as it initially looks. In this first installment of a pair of articles, Myriam Frisano aims to teach you the basics of &lt;code>&amp;lt;path&amp;gt;&lt;/code> and its sometimes mystifying commands. With simple examples and visualizations, she’ll help you understand the easy syntax and underlying rules of SVG’s most powerful element so that by the end, you’re fully able to translate SVG semantic tags into a language &lt;code>path&lt;/code> understands.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/06/decoding-svg-path-element-line-commands/" />
              <title>Decoding The SVG &lt;code&gt;path&lt;/code&gt; Element: Line Commands</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Decoding The SVG &lt;code&gt;path&lt;/code&gt; Element: Line Commands</h1>
                  
                    
                    <address>Myriam Frisano</address>
                  
                  <time datetime="2025-06-09T08:00:00&#43;00:00" class="op-published">2025-06-09T08:00:00+00:00</time>
                  <time datetime="2025-06-09T08:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>In a previous article, we looked at some <a href="https://www.smashingmagazine.com/2024/09/svg-coding-examples-recipes-writing-vectors-by-hand/">practical examples of how to code SVG by hand</a>. In that guide, we covered the basics of the SVG elements <code>rect</code>, <code>circle</code>, <code>ellipse</code>, <code>line</code>, <code>polyline</code>, and <code>polygon</code> (and also <code>g</code>).</p>

<p>This time around, we are going to tackle a more advanced topic, the absolute powerhouse of SVG elements: <code>path</code>. Don’t get me wrong; I still stand by my point that image paths are better drawn in vector programs than coded (unless you’re the type of creative who makes non-logical visual art in code &mdash; then go forth and create awe-inspiring wonders; you’re probably not the audience of this article). But when it comes to <strong>technical drawings</strong> and <strong>data visualizations</strong>, the <code>path</code> element unlocks a wide array of possibilities and opens up the world of hand-coded SVGs.</p>

<p>The path syntax can be really complex. We’re going to tackle it in two separate parts. In this first installment, we’re learning all about <strong>straight and angular paths</strong>. In the second part, we’ll make lines bend, twist, and turn.</p>

<h2 id="required-knowledge-and-guide-structure">Required Knowledge And Guide Structure</h2>

<p><strong>Note</strong>: <em>If you are unfamiliar with the basics of SVG, such as the subject of <code>viewBox</code> and the basic syntax of the simple elements (<code>rect</code>, <code>line</code>, <code>g</code>, and so on), I recommend reading <a href="https://www.smashingmagazine.com/2024/09/svg-coding-examples-recipes-writing-vectors-by-hand/">my guide</a> before diving into this one. You should also <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/text">familiarize yourself with <code>&lt;text&gt;</code></a> if you want to understand each line of code in the examples.</em></p>

<p>Before we get started, I want to quickly recap how I code SVG using JavaScript. I don’t like dealing with numbers and math, and reading SVG Code with numbers filled into every attribute makes me lose all understanding of it. By giving coordinates names and having all my math easy to parse and write out, I have a much better time with this type of code, and I think you will, too.</p>

<p>The goal of this article is more about <strong>understanding <code>path</code> syntax</strong> than it is about doing placement or how to leverage loops and other more basic things. So, I will not run you through the entire setup of each example. I’ll instead share snippets of the code, but they may be slightly adjusted from the CodePen or simplified to make this article easier to read. However, if there are specific questions about code that are not part of the text in the CodePen demos, the comment section is open.</p>

<p>To keep this all framework-agnostic, the code is written in vanilla JavaScript (though, really, TypeScript is your friend the more complicated your SVG becomes, and I missed it when writing some of these).</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="/printed-books/image-optimization/">Image Optimization</a></strong>, Addy Osmani’s new practical guide to optimizing and delivering <strong>high-quality images</strong> on the web. Everything in one single <strong>528-pages</strong> book.</p>
<a data-instant href="https://www.smashingmagazine.com/printed-books/image-optimization/" class="btn btn--green btn--large" style="">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://www.smashingmagazine.com/printed-books/image-optimization/" class="feature-panel-image-link">
<div class="feature-panel-image"><picture><source type="image/avif" srcSet="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/2c669cf1-c6ef-4c87-9901-018b04f7871f/image-optimization-shop-cover-opt.avif" />
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/87fd0cfa-692e-459c-b2f3-15209a1f6aa7/image-optimization-shop-cover-opt.png"
    alt="Feature Panel"
    width="480"
    height="697"
/>
</picture>
</div>
</a>
</div>
</aside>
</div>

<h2 id="setting-up-for-success">Setting Up For Success</h2>

<p>As the <code>path</code> element relies on our understanding of some of the coordinates we plug into the commands, I think it is a lot easier if we have a bit of visual orientation. So, all of the examples will be coded on top of a visual representation of a traditional <code>viewBox</code> setup with the origin in the top-left corner (so, values in the shape of <code>0 0 ${width} ${height}</code>.</p>

<p>I added text labels as well to make it easier to point you to specific areas within the grid.</p>

<blockquote>Please note that I recommend being careful when adding text within the <code>&lt;text&gt;</code> element in SVG if you want your text to be accessible. If the graphic relies on text scaling like the rest of your website, it would be better to have it rendered through HTML. But for our examples here, it should be sufficient.</blockquote>

<p>So, this is what we’ll be plotting on top of:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="MYwEdVN"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Viewbox Grid Visual [forked]](https://codepen.io/smashingmag/pen/MYwEdVN) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/MYwEdVN">SVG Viewbox Grid Visual [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<p>Alright, we now have a ViewBox Visualizing Grid. I think we’re ready for our first session with the beast.</p>

<h2 id="enter-path-and-the-all-powerful-d-attribute">Enter <code>path</code> And The All-Powerful <code>d</code> Attribute</h2>

<p>The <code>&lt;path&gt;</code> element has a <code>d</code> attribute, which speaks its own language. So, within <code>d</code>, you’re talking in terms of “commands”.</p>

<p>When I think of <code>non-path</code> versus <code>path</code> elements, I like to think that the reason why we have to write much more complex drawing instructions is this: <strong>All non-path elements are just dumber paths.</strong> In the background, they have one pre-drawn path shape that they will always render based on a few parameters you pass in. But <code>path</code> has no default shape. The shape logic has to be exposed to you, while it can be neatly hidden away for all other elements.</p>

<p>Let’s learn about those commands.</p>

<h2 id="where-it-all-begins-m">Where It All Begins: <code>M</code></h2>

<p>The first, which is where each path begins, is the <code>M</code> command, which moves the pen to a point. This command places your starting point, but it <strong>does not draw a single thing</strong>. A path with just an <code>M</code> command is an <code>auto-delete</code> when cleaning up SVG files.</p>

<p>It takes two arguments: the <code>x</code> and <code>y</code> coordinates of your start position.</p>

<pre><code class="language-javascript">const uselessPathCommand = `M${start.x} ${start.y}`;
</code></pre>

<h2 id="basic-line-commands-m-l-h-v">Basic Line Commands: <code>M</code> , <code>L</code>, <code>H</code>, <code>V</code></h2>

<p>These are fun and easy: <code>L</code>, <code>H</code>, and <code>V</code>, all draw a line from the current point to the point specified.</p>

<p><code>L</code> takes <strong>two arguments</strong>, the <code>x</code> and <code>y</code> positions of the point you want to draw to.</p>

<pre><code class="language-javascript">const pathCommandL = `M${start.x} ${start.y} L${end.x} ${end.y}`;
</code></pre>

<p><code>H</code> and <code>V</code>, on the other hand, only take <strong>one argument</strong> because they are only drawing a line in one direction. For <code>H</code>, you specify the <code>x</code> position, and for <code>V</code>, you specify the <code>y</code> position. The other value is implied.</p>

<pre><code class="language-javascript">const pathCommandH = `M${start.x} ${start.y} H${end.x}`;
const pathCommandV = `M${start.x} ${start.y} V${end.y}`;
</code></pre>

<p>To visualize how this works, I created a function that draws the path, as well as points with labels on them, so we can see what happens.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="azOLrjZ"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Simple Lines with path [forked]](https://codepen.io/smashingmag/pen/azOLrjZ) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/azOLrjZ">Simple Lines with path [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<p>We have three lines in that image. The <code>L</code> command is used for the red path. It starts with <code>M</code> at <code>(10,10)</code>, then moves diagonally down to <code>(100,100)</code>. The command is: <code>M10 10 L100 100</code>.</p>

<p>The blue line is horizontal. It starts at <code>(10,55)</code> and should end at <code>(100, 55)</code>. We could use the <code>L</code> command, but we’d have to write <code>55</code> again. So, instead, we write <code>M10 55 H100</code>, and then SVG knows to look back at the <code>y</code> value of <code>M</code> for the <code>y</code> value of <code>H</code>.</p>

<p>It’s the same thing for the green line, but when we use the <code>V</code> command, SVG knows to refer back to the <code>x</code> value of <code>M</code> for the <code>x</code> value of <code>V</code>.</p>

<p>If we compare the resulting horizontal path with the same implementation in a <code>&lt;line&gt;</code> element, we may</p>

<ol>
<li>Notice how much more efficient <code>path</code> can be, and</li>
<li>Remove quite a bit of meaning for anyone who doesn’t speak <code>path</code>.</li>
</ol>

<p>Because, as we look at these strings, one of them is called “line”. And while the rest doesn’t mean anything out of context, the line definitely conjures a specific image in our heads.</p>

<pre><code class="language-svg">&lt;path d="M 10 55 H 100" /&gt;
&lt;line x1="10" y1="55" x2="100" y2="55" /&gt;
</code></pre>

<div class="partners__lead-place"></div>

<h2 id="making-polygons-and-polylines-with-z">Making Polygons And Polylines With <code>Z</code></h2>

<p>In the previous section, we learned how <code>path</code> can behave like <code>&lt;line&gt;</code>, which is pretty cool. But it can do more. It can also act like <code>polyline</code> and <code>polygon</code>.</p>

<p>Remember, how those two basically work the same, but <code>polygon</code> connects the first and last point, while <code>polyline</code> does not? The <code>path</code> element can do the same thing. There is a separate command to close the path with a line, which is the <code>Z</code> command.</p>

<div class="break-out">
<pre><code class="language-javascript">const polyline2Points = `M${start.x} ${start.y} L${p1.x} ${p1.y} L${p2.x} ${p2.y}`;
const polygon2Points  = `M${start.x} ${start.y} L${p1.x} ${p1.y} L${p2.x} ${p2.y} Z`;
</code></pre>
</div>

<p>So, let’s see this in action and create a repeating triangle shape. Every odd time, it’s open, and every even time, it’s closed. Pretty neat!</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="emNGaPm"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Alternating Triangles [forked]](https://codepen.io/smashingmag/pen/emNGaPm) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/emNGaPm">Alternating Triangles [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<p>When it comes to comparing <code>path</code> versus <code>polygon</code> and <code>polyline</code>, the other tags tell us about their names, but I would argue that fewer people know what a polygon is versus what a line is (and probably even fewer know what a polyline is. Heck, even the program I’m writing this article in tells me polyline is not a valid word). The argument to use these two tags over <code>path</code> for legibility is weak, in my opinion, and I guess you’d probably agree that this looks like equal levels of meaningless string given to an SVG element.</p>

<pre><code class="language-svg">&lt;path d="M0 0 L86.6 50 L0 100 Z" /&gt;
&lt;polygon points="0,0 86.6,50 0,100" /&gt;

&lt;path d="M0 0 L86.6 50 L0 100" /&gt;
&lt;polyline points="0,0 86.6,50 0,100" /&gt;
</code></pre>

<h2 id="relative-commands-m-l-h-v">Relative Commands: <code>m</code>, <code>l</code>, <code>h</code>, <code>v</code></h2>

<p>All of the line commands exist in absolute and relative versions. The difference is that the relative commands are lowercase, e.g., <code>m</code>, <code>l</code>, <code>h</code>, and <code>v</code>. The relative commands are always relative to the last point, so instead of declaring an <code>x</code> value, you’re declaring a <code>dx</code> value, saying this is how many units you’re moving.</p>

<p>Before we look at the example visually, I want you to look at the following three-line commands. Try not to look at the CodePen beforehand.</p>

<pre><code class="language-javascript">const lines = [
  { d: `M10 10 L 10 30 L 30 30`, color: "var(--_red)" },
  { d: `M40 10 l 0 20 l 20 0`, color: "var(--_blue)" },
  { d: `M70 10 l 0 20 L 90 30`, color: "var(--_green)" }
];
</code></pre>

<p>As I mentioned, I hate looking at numbers without meaning, but there is one number whose meaning is pretty constant in most contexts: <code>0</code>. Seeing a <code>0</code> in combination with a command I just learned means <em>relative</em> manages to instantly tell me that nothing is happening. Seeing <code>l 0 20</code> by itself tells me that this line only moves along one axis instead of two.</p>

<p>And looking at that entire blue path command, the repeated <code>20</code> value gives me a sense that the shape might have some regularity to it. The first path does a bit of that by repeating <code>10</code> and <code>30</code>. But the third? As someone who can’t do math in my head, that third string gives me <em>nothing</em>.</p>

<p>Now, you might be surprised, but they all draw the same shape, just in different places.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="vEOewQp"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Compound Paths [forked]](https://codepen.io/smashingmag/pen/vEOewQp) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/vEOewQp">SVG Compound Paths [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<p>So, how valuable is it that we can recognize the regularity in the blue path? Not very, in my opinion. In some cases, going with the relative value is easier than an absolute one. In other cases, the absolute is king. Neither is better nor worse.</p>

<blockquote>And, in all cases, that previous example would be much more efficient if it were set up with a variable for the gap, a variable for the shape size, and a function to generate the path definition that’s called from within a loop so it can take in the index to properly calculate the start point.</blockquote>

<div class="partners__lead-place"></div>

<h2 id="jumping-points-how-to-make-compound-paths">Jumping Points: How To Make Compound Paths</h2>

<p>Another very useful thing is something you don’t see visually in the previous CodePen, but it relates to the grid and its code.</p>

<p>I snuck in a grid drawing update.</p>

<p>With the method used in earlier examples, using <code>line</code> to draw the grid, the above CodePen would’ve rendered the grid with 14 separate elements. If you go and inspect the final code of that last CodePen, you’ll notice that there is just a single path element within the <code>.grid</code> group.</p>

<p>It looks like this, which is not fun to look at but holds the secret to how it’s possible:</p>

<div class="break-out">
<pre><code class="language-svg">&lt;path d="M0 0 H110 M0 10 H110 M0 20 H110 M0 30 H110 M0 0 V45 M10 0 V45 M20 0 V45 M30 0 V45 M40 0 V45 M50 0 V45 M60 0 V45 M70 0 V45 M80 0 V45 M90 0 V45" stroke="currentColor" stroke-width="0.2" fill="none"&gt;&lt;/path&gt;
</code></pre>
</div>

<p>If we take a close look, we may notice that there are multiple <code>M</code> commands. This is the magic of compound paths.</p>

<blockquote>Since the <code>M/m</code> commands don’t actually draw and just place the cursor, a <code>path</code> can have jumps.</blockquote>

<p>So, whenever we have multiple paths that share common styling and don’t need to have separate interactions, we can just chain them together to make our code shorter.</p>

<h2 id="coming-up-next">Coming Up Next</h2>

<p>Armed with this knowledge, we’re now able to replace <code>line</code>, <code>polyline</code>, and <code>polygon</code> with <code>path</code> commands and combine them in compound paths. But there is so much more to uncover because <code>path</code> doesn’t just offer foreign-language versions of lines but also gives us the option to code <code>circles</code> and <code>ellipses</code> that have open space and can sometimes also bend, twist, and turn. We’ll refer to those as <em>curves</em> and <em>arcs</em>, and discuss them more explicitly in the next article.</p>

<h3 id="further-reading-on-smashingmag">Further Reading On SmashingMag</h3>

<ul>
<li>“<a href="https://www.smashingmagazine.com/2024/12/mastering-svg-arcs/">Mastering SVG Arcs</a>,” Akshay Gupta</li>
<li>“<a href="https://www.smashingmagazine.com/2021/05/accessible-svg-patterns-comparison/">Accessible SVGs: Perfect Patterns For Screen Reader Users</a>,” Carie Fisher</li>
<li>“<a href="https://www.smashingmagazine.com/2023/01/svg-customization-animation-practical-guide/">Easy SVG Customization And Animation: A Practical Guide</a>,” Adrian Bece</li>
<li>“<a href="https://www.smashingmagazine.com/2022/05/magical-svg-techniques/">Magical SVG Techniques</a>,” Cosima Mielke</li>
</ul>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Andy Clarke</author><title>Smashing Animations Part 4: Optimising SVGs</title><link>https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/</link><pubDate>Wed, 04 Jun 2025 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/</guid><description>What’s the best way to make your SVGs faster, simpler, and more manageable? In this article, pioneering author and web designer &lt;a href="https://stuffandnonsense.co.uk/">Andy Clarke&lt;/a> explains the process he relies on &lt;em>to&lt;/em> prepare, optimise, and structure SVGs for animation and beyond.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/" />
              <title>Smashing Animations Part 4: Optimising SVGs</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Smashing Animations Part 4: Optimising SVGs</h1>
                  
                    
                    <address>Andy Clarke</address>
                  
                  <time datetime="2025-06-04T08:00:00&#43;00:00" class="op-published">2025-06-04T08:00:00+00:00</time>
                  <time datetime="2025-06-04T08:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>SVG animations take me back to the Hanna-Barbera cartoons I watched as a kid. Shows like <em>Wacky Races</em>, <em>The Perils of Penelope Pitstop</em>, and, of course, <a href="https://en.wikipedia.org/wiki/Yogi_Bear"><em>Yogi Bear</em></a>. They inspired me to lovingly recreate some classic <a href="https://stuffandnonsense.co.uk/toon-titles">Toon Titles</a> using CSS, SVG, and SMIL animations.</p>

<p>But getting animations to load quickly and work smoothly needs more than nostalgia. It takes clean design, lean code, and a process that makes complex SVGs easier to animate. Here’s how I do it.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://stuffandnonsense.co.uk/toon-titles">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png"
			
			sizes="100vw"
			alt="An example of Toon Titles from the website"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      There’s now a website where you can see all my <a href='https://stuffandnonsense.co.uk/toon-titles'>Toon Titles</a>. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png'>Large preview</a>)
    </figcaption>
  
</figure>

<div class="refs">
  <ul><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/">Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS</a></li><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-2-css-masking-add-extra-dimension/">Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension</a></li><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-3-smil-not-dead/">Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead</a></li></ul>
</div>

<p>Whether for personal projects or commercial work, preparing SVGs well ensures they’re accessible. Optimising them ensures they load quickly, especially on mobile, and thinking carefully about how they’re structured makes maintaining them easier. I’ve developed a <strong>process that balances visuals with accessibility and performance</strong> and makes complex SVGs easier to work with.</p>

<p>So, to explain my process, I’ve chosen an episode of <em>The Yogi Bear Show</em> called “Bewitched Bear,” first broadcast in January 1960. In this story, Yogi steals a witch’s broom to help him grab “pic-a-nic” baskets.</p>

<p>“Hey, hey, hey!”</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="540"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png"
			
			sizes="100vw"
			alt="An illustration from the “Bewitched Bear” episode of The Yogi Bear Show where bear is on a witch’s broom"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png'>Large preview</a>)
    </figcaption>
  
</figure>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="start-clean-and-design-with-optimisation-in-mind">Start Clean And Design With Optimisation In Mind</h2>

<p>Keeping things simple is key to making SVGs that are optimised and ready to animate. Tools like Adobe Illustrator convert bitmap images to vectors, but the output often contains too many extraneous groups, layers, and masks. Instead, I start cleaning in Sketch, work from a reference image, and use the Pen tool to create paths.</p>

<blockquote><strong>Tip</strong>: <a href="https://affinity.serif.com/en-gb/designer/">Affinity Designer</a> (UK) and <a href="https://www.sketch.com">Sketch</a> (Netherlands) are alternatives to Adobe Illustrator and Figma. Both are independent and based in Europe. Sketch has been my default design app since Adobe killed Fireworks.</blockquote>

<h2 id="beginning-with-outlines">Beginning With Outlines</h2>

<p>For these Toon Titles illustrations, I first use the Pen tool to draw black outlines with as few anchor points as possible. The more points a shape has, the bigger a file becomes, so simplifying paths and reducing the number of points makes an SVG much smaller, often with no discernible visual difference.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png"
			
			sizes="100vw"
			alt="Two outlines with different anchor points"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <strong>Left</strong>: 160 anchor points. <strong>Right</strong>: 80 points. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Bearing in mind that parts of this Yogi illustration will ultimately be animated, I keep outlines for this Bewitched Bear’s body, head, collar, and tie separate so that I can move them independently. The head might nod, the tie could flap, and, like in those classic cartoons, Yogi’s collar will hide the joins between them.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png"
			
			sizes="100vw"
			alt="Separate outlines for body, head, collar and tie, and broom."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Separate outlines for body, head, collar and tie, and broom. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="drawing-simple-background-shapes">Drawing Simple Background Shapes</h2>

<p>With the outlines in place, I use the Pen tool again to draw new shapes, which fill the areas with colour. These colours sit behind the outlines, so they don’t need to match them exactly. The fewer anchor points, the smaller the file size.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png"
			
			sizes="100vw"
			alt="Original vector artwork and a simplified version with Adobe Illustrator"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <strong>Left</strong>: Original vector artwork, 8 Kb. <strong>Right</strong>: Simplified using Adobe Illustrator, 2 Kb. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Sadly, neither Affinity Designer nor Sketch has tools that can simplify paths, but if you have it, using Adobe Illustrator can shave a few extra kilobytes off these background shapes.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png"
			
			sizes="100vw"
			alt="An illustration how to simplify paths with Adobe Illustrator"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Adobe Illustrator: Object → Path → Simplify. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="optimising-the-code">Optimising The Code</h2>

<p>It’s not just metadata that makes SVG bulkier. The way you export from your design app also affects file size.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png"
			
			sizes="100vw"
			alt="Vector artwork ready for optimisation."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Vector artwork ready for optimisation. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Exporting just those simple background shapes from Adobe Illustrator includes unnecessary groups, masks, and bloated path data by default. Sketch’s code is barely any better, and there’s plenty of room for improvement, even in its SVGO Compressor code. I rely on Jake Archibald’s <a href="https://jakearchibald.github.io/svgomg/">SVGOMG</a>, which uses SVGO v3 and consistently delivers the best optimised SVGs.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="439"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png"
			
			sizes="100vw"
			alt="Jake Archibald’s SVGOMG online optimisation tool."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Jake Archibald’s SVGOMG online optimisation tool. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png'>Large preview</a>)
    </figcaption>
  
</figure>

<div class="partners__lead-place"></div>

<h2 id="layering-svg-elements">Layering SVG Elements</h2>

<p>My process for preparing SVGs for animation goes well beyond drawing vectors and optimising paths &mdash; it also includes how I <strong>structure the code</strong> itself. When every visual element is crammed into a single SVG file, even optimised code can be a nightmare to navigate. Locating a specific path or group often feels like searching for a needle in a haystack.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png"
			
			sizes="100vw"
			alt="Toon Titles recreation of the Yogi Bear title card"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Toon Titles recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>That’s why I develop my SVGs in layers, exporting and optimising one set of elements at a time &mdash; always in the order they’ll appear in the final file. This lets me build the master SVG gradually by pasting it in each cleaned-up section. For example, I start with backgrounds like this gradient and title graphic.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png"
			
			sizes="100vw"
			alt="Gradient background and title graphic."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Gradient background and title graphic. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Instead of facing a wall of SVG code, I can now easily identify the background gradient’s path and its associated <code>linearGradient</code>, and see the group containing the title graphic. I take this opportunity to add a comment to the code, which will make editing and adding animations to it easier in the future:</p>

<pre><code class="language-svg">&lt;svg ...&gt;
  &lt;defs&gt;
    &lt;!-- ... --&gt;
  &lt;/defs&gt;
  &lt;path fill="url(#grad)" d="…"/&gt;
  &lt;!-- TITLE GRAPHIC --&gt;
  &lt;g&gt;
    &lt;path … /&gt;
    &lt;!-- ... --&gt; 
  &lt;/g&gt;
&lt;/svg&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png"
			
			sizes="100vw"
			alt="Trail with Gaussian Blur."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Trail with Gaussian Blur. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Next, I add the blurred trail from Yogi’s airborne broom. This includes defining a Gaussian Blur filter and placing its path between the background and title layers:</p>

<pre><code class="language-svg">&lt;svg ...&gt;
  &lt;defs&gt;
    &lt;linearGradient id="grad" …&gt;…&lt;/linearGradient&gt;
    &lt;filter id="trail" …&gt;…&lt;/filter&gt;
  &lt;/defs&gt;
  &lt;!-- GRADIENT --&gt;
  &lt;!-- TRAIL --&gt;
  &lt;path filter="url(#trail)" …/&gt;
  &lt;!-- TITLE GRAPHIC --&gt;
&lt;/svg&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png"
			
			sizes="100vw"
			alt="Yogi Bear’s magical stars."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear’s magical stars. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Then come the magical stars, added in the same sequential fashion:</p>

<pre><code class="language-svg">&lt;svg ...&gt;
  &lt;!-- GRADIENT --&gt;
  &lt;!-- TRAIL --&gt;
  &lt;!-- STARS --&gt;
  &lt;!-- TITLE GRAPHIC --&gt;
&lt;/svg&gt;
</code></pre>

<p>To keep everything organised and animation-ready, I create an empty group that will hold all the parts of Yogi:</p>

<pre><code class="language-svg">&lt;g id="yogi"&gt;...&lt;/g&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png"
			
			sizes="100vw"
			alt="Added Yogi Bear’s component parts"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Sequentially adding Yogi Bear’s component parts. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Then I build Yogi from the ground up &mdash; starting with background props, like his broom:</p>

<pre><code class="language-svg">&lt;g id="broom"&gt;...&lt;/g&gt;
</code></pre>

<p>Followed by grouped elements for his body, head, collar, and tie:</p>

<pre><code class="language-svg">&lt;g id="yogi"&gt;
  &lt;g id="broom"&gt;…&lt;/g&gt;
  &lt;g id="body"&gt;…&lt;/g&gt;
  &lt;g id="head"&gt;…&lt;/g&gt;
  &lt;g id="collar"&gt;…&lt;/g&gt;
  &lt;g id="tie"&gt;…&lt;/g&gt;
&lt;/g&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png"
			
			sizes="100vw"
			alt="Toon Titles recreation of the Yogi Bear title card"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Toon Titles recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Since I export each layer from the same-sized artboard, I don’t need to worry about alignment or positioning issues later on &mdash; they’ll all slot into place automatically. I keep my code <strong>clean</strong>, <strong>readable</strong>, and <strong>ordered logically</strong> by layering elements this way. It also makes animating smoother, as each component is easier to identify.</p>

<h2 id="reusing-elements-with-use">Reusing Elements With <code>&lt;use&gt;</code></h2>

<p>When duplicate shapes get reused repeatedly, SVG files can get bulky fast. My recreation of the “Bewitched Bear” title card contains 80 stars in three sizes. Combining all those shapes into one optimised path would bring the file size down to 3KB. But I want to animate individual stars, which would almost double that to 5KB:</p>

<pre><code class="language-svg">&lt;g id="stars"&gt;
 &lt;path class="star-small" fill="#eae3da" d="..."/&gt;
 &lt;path class="star-medium" fill="#eae3da" d="..."/&gt;
 &lt;path class="star-large" fill="#eae3da" d="..."/&gt;
 &lt;!-- ... --&gt;
&lt;/g&gt;
</code></pre>

<p>Moving the stars’ <code>fill</code> attribute values to their parent group reduces the overall weight a little:</p>

<pre><code class="language-svg">&lt;g id="stars" fill="#eae3da"&gt;
 &lt;path class="star-small" d="…"/&gt;
 &lt;path class="star-medium" d="…"/&gt;
 &lt;path class="star-large" d="…"/&gt;
 &lt;!-- ... --&gt;
&lt;/g&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png"
			
			sizes="100vw"
			alt="Yogi Bear’s sparkling stars."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear’s sparkling stars. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>But a more efficient and manageable option is to define each star size as a reusable template:</p>

<div class="break-out">
<pre><code class="language-svg">&lt;defs&gt;
  &lt;path id="star-large" fill="#eae3da" fill-rule="evenodd" d="…"/&gt;
  &lt;path id="star-medium" fill="#eae3da" fill-rule="evenodd" d="…"/&gt;
  &lt;path id="star-small" fill="#eae3da" fill-rule="evenodd" d="…"/&gt;
&lt;/defs&gt;
</code></pre>
</div>

<p>With this setup, changing a star’s design only means updating its template once, and every instance updates automatically. Then, I reference each one using <code>&lt;use&gt;</code> and position them with <code>x</code> and <code>y</code> attributes:</p>

<pre><code class="language-svg">&lt;g id="stars"&gt;
  &lt;!-- Large stars --&gt;
  &lt;use href="#star-large" x="1575" y="495"/&gt;
  &lt;!-- ... --&gt;
  &lt;!-- Medium stars --&gt;
  &lt;use href="#star-medium" x="1453" y="696"/&gt;
  &lt;!-- ... --&gt;
  &lt;!-- Small stars --&gt;
  &lt;use href="#star-small" x="1287" y="741"/&gt;
  &lt;!-- ... --&gt;
&lt;/g&gt;
</code></pre>

<p>This approach makes the SVG easier to manage, lighter to load, and faster to iterate on, especially when working with dozens of repeating elements. Best of all, it keeps the markup clean <strong>without compromising on flexibility or performance</strong>.</p>

<div class="partners__lead-place"></div>

<h2 id="adding-animations">Adding Animations</h2>

<p>The stars trailing behind Yogi’s stolen broom bring so much personality to the animation. I wanted them to sparkle in a seemingly random pattern against the dark blue background, so I started by defining a keyframe animation that cycles through different <code>opacity</code> levels:</p>

<pre><code class="language-css">@keyframes sparkle {
  0%, 100% { opacity: .1; }
  50% { opacity: 1; }
}
</code></pre>

<p>Next, I applied this looping animation to every <code>use</code> element inside my stars group:</p>

<pre><code class="language-css">&#35;stars use {
  animation: sparkle 10s ease-in-out infinite;
}
</code></pre>

<p>The secret to creating a convincing twinkle lies in <strong>variation</strong>. I staggered animation delays and durations across the stars using <code>nth-child</code> selectors, starting with the quickest and most frequent sparkle effects:</p>

<pre><code class="language-css">/&#42; Fast, frequent &#42;/
&#35;stars use:nth-child(n + 1):nth-child(-n + 10) {
  animation-delay: .1s;
  animation-duration: 2s;
}
</code></pre>

<p>From there, I layered in additional timings to mix things up. Some stars sparkle slowly and dramatically, others more randomly, with a variety of rhythms and pauses:</p>

<pre><code class="language-css">/&#42; Medium &#42;/
&#35;stars use:nth-child(n + 11):nth-child(-n + 20) { ... }

/&#42; Slow, dramatic &#42;/
&#35;stars use:nth-child(n + 21):nth-child(-n + 30) { ... }

/&#42; Random &#42;/
&#35;stars use:nth-child(3n + 2) { ... }

/&#42; Alternating &#42;/
&#35;stars use:nth-child(4n + 1) { ... }

/&#42; Scattered &#42;/
&#35;stars use:nth-child(n + 31) { ... }
</code></pre>

<p>By thoughtfully structuring the SVG and reusing elements, I can build complex-looking animations without bloated code, making even a simple effect like changing <code>opacity</code> sparkle.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png"
			
			sizes="100vw"
			alt="Yogi Bear"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Subtle movements bring Yogi Bear to life. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Then, for added realism, I make Yogi’s head wobble:</p>

<div class="break-out">
<pre><code class="language-css">@keyframes headWobble {
  0% { transform: rotate(-0.8deg) translateY(-0.5px); }
  100% { transform: rotate(0.9deg) translateY(0.3px); }
}

&#35;head {
  animation: headWobble 0.8s cubic-bezier(0.5, 0.15, 0.5, 0.85) infinite alternate;
}
</code></pre>
</div>

<p>His tie waves:</p>

<div class="break-out">
<pre><code class="language-css">@keyframes tieWave {
  0%, 100% { transform: rotateZ(-4deg) rotateY(15deg) scaleX(0.96); }
  33% { transform: rotateZ(5deg) rotateY(-10deg) scaleX(1.05); }
  66% { transform: rotateZ(-2deg) rotateY(5deg) scaleX(0.98); }
}

&#35;tie {
  transform-style: preserve-3d;
  animation: tieWave 10s cubic-bezier(0.68, -0.55, 0.27, 1.55) infinite;
}
</code></pre>
</div>

<p>His broom swings:</p>

<div class="break-out">
<pre><code class="language-css">@keyframes broomSwing {
  0%, 20% { transform: rotate(-5deg); }
  30% { transform: rotate(-4deg); }
  50%, 70% { transform: rotate(5deg); }
  80% { transform: rotate(4deg); }
  100% { transform: rotate(-5deg); }
}

&#35;broom {
  animation: broomSwing 4s cubic-bezier(0.5, 0.05, 0.5, 0.95) infinite;
}
</code></pre>
</div>

<p>And, finally, Yogi himself gently rotates as he flies on his magical broom:</p>

<div class="break-out">
<pre><code class="language-css">@keyframes yogiWobble {
  0% { transform: rotate(-2.8deg) translateY(-0.8px) scale(0.998); }
  30% { transform: rotate(1.5deg) translateY(0.3px); }
  100% { transform: rotate(3.2deg) translateY(1.2px) scale(1.002); }
}

&#35;yogi {
  animation: yogiWobble 3.5s cubic-bezier(.37, .14, .3, .86) infinite alternate;
}
</code></pre>
</div>

<p>All these subtle movements bring Yogi to life. By developing structured SVGs, I can create animations that feel full of character without writing a single line of JavaScript.</p>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="bNdwJBN"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Bewitched Bear CSS/SVG animation [forked]](https://codepen.io/smashingmag/pen/bNdwJBN) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/bNdwJBN">Bewitched Bear CSS/SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<h2 id="conclusion">Conclusion</h2>

<p>Whether you’re recreating a classic title card or animating icons for an interface, the principles are the same:</p>

<ol>
<li>Start clean,</li>
<li>Optimise early, and</li>
<li>Structure everything with animation in mind.</li>
</ol>

<p>SVGs offer incredible creative freedom, but only if kept <strong>lean</strong> and <strong>manageable</strong>. When you plan your process like a production cell &mdash; layer by layer, element by element &mdash; you’ll spend less time untangling code and more time bringing your work to life.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Andy Clarke</author><title>Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead</title><link>https://www.smashingmagazine.com/2025/05/smashing-animations-part-3-smil-not-dead/</link><pubDate>Wed, 21 May 2025 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/05/smashing-animations-part-3-smil-not-dead/</guid><description>While there are plenty of ways that CSS animations can bring designs to life, adding simple SMIL (Synchronized Multimedia Integration Language) animations in SVG can help them do much more. Andy Clarke explains where SMIL animations in SVG take over where CSS leaves off.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-3-smil-not-dead/" />
              <title>Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead</h1>
                  
                    
                    <address>Andy Clarke</address>
                  
                  <time datetime="2025-05-21T08:00:00&#43;00:00" class="op-published">2025-05-21T08:00:00+00:00</time>
                  <time datetime="2025-05-21T08:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>The SMIL specification was introduced by the W3C in 1998 for synchronizing multimedia. This was long before CSS animations or JavaScript-based animation libraries were available. It was built into SVG 1.1, which is why we can still use it there today.</p>

<p>Now, you might’ve heard that <a href="https://css-tricks.com/smil-is-dead-long-live-smil-a-guide-to-alternatives-to-smil-features">SMIL is dead</a>. However, it’s alive and well since Google reversed a decision to deprecate the technology almost a decade ago. It remains a terrific choice for designers and developers who want simple, semantic ways to add animations to their designs.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="540"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustration"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><strong>Tip</strong>: <em>There’s now a website where you can see all my <a href="https://stuffandnonsense.co.uk/toon-titles">Toon Titles</a>.</em></p>

<div class="refs">
  <ul><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/">Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS</a></li><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-2-css-masking-add-extra-dimension/">Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension</a></li></ul>
</div>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="introducing-mike-worth">Introducing Mike Worth</h2>

<p>I’ve recently been working on a new website for Emmy-award-winning game composer Mike Worth. He hired me to create a bold, retro-style design that showcases his work. I used animations throughout to delight and surprise his audience as they move through his website.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="550"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png"
			
			sizes="100vw"
			alt="Illustrations from Mike Worth’s website"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Design by <a href='https://stuffandnonsense.co.uk/'>Andy Clarke, Stuff & Nonsense</a>. Mike Worth’s website will launch in June 2025, but you can see <a href='https://codepen.io/collection/YwMKPb'>examples from this article on CodePen</a>. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Mike loves ’90s animation &mdash; especially <a href="https://en.wikipedia.org/wiki/DuckTales_(1987_TV_series)">Disney’s</a> <a href="https://en.wikipedia.org/wiki/DuckTales_(1987_TV_series)"><em>Duck Tales</em></a>. Unsurprisingly, my taste in cartoons stretches back a little further to <a href="https://en.wikipedia.org/wiki/Hanna-Barbera">Hanna-Barbera</a> shows like Dastardly and Muttley in <em>Their Flying Machines</em>, <em>Scooby-Doo</em>, <em>The Perils of Penelope Pitstop</em>, <em>Wacky Races</em>, and, of course, <a href="https://en.wikipedia.org/wiki/Yogi_Bear"><em>The Yogi Bear Show</em></a>. So, to explain how this era of animation relates to SVG, I’ll be adding SMIL animations in SVG to title cards from some classic Yogi Bear cartoons.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="300"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustrations"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Fundamentally, animation changes how an element looks and where it appears over time using a few basic techniques. That might be simply shifting an element up or down, left or right, to create the appearance of motion, like Yogi Bear moving across the screen.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png"
			
			sizes="100vw"
			alt="Yogi Bear title card design recreated by Andy Clarke"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Author’s recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Rotating objects around a fixed point can create everything, from simple spinning effects to natural-looking movements of totally normal things, like a bear under a parachute falling from the sky.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png"
			
			sizes="100vw"
			alt="Yogi Bear title card recreated by Andy Clarke"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Author’s recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Scaling makes an element grow, shrink, or stretch, which can add drama, create perspective, or simulate depth.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png"
			
			sizes="100vw"
			alt="Yogi Bear title card recreated by Andy Clarke"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Author’s recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Changing colour and transitioning opacity can add atmosphere, create a mood, and enhance visual storytelling. Just these basic principles can create animations that attract attention and improve someone’s experience using a design.</p>

<p>These results are all achievable using CSS animations, but some SVG properties can’t be animated using CSS. Luckily, we can do more &mdash; and have much more fun &mdash; using SMIL animations in SVG. We can combine complex animations, move objects along paths, and control when they start, stop, and everything in between.</p>

<p>Animations can be embedded within any SVG element, including <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorials/SVG_from_scratch/Basic_shapes">primitive shapes</a> like circles, ellipses, and rectangles. They can also be encapsulated into groups, paths, and polygons:</p>

<pre><code class="language-svg">&lt;circle ...&gt;
  &lt;animate&gt;...&lt;/animate&gt;
&lt;/circle&gt;
</code></pre>

<p>Animations can also be defined outside an element, elsewhere in an SVG, and connected to it using an <code>xlink</code> attribute:</p>

<pre><code class="language-svg">&lt;g id="yogi"&gt;...&lt;/g&gt;
  ...
&lt;animate xlink:href="#yogi"&gt;…&lt;/animate&gt;
</code></pre>

<h2 id="building-an-animation">Building An Animation</h2>

<p><code>&lt;animate&gt;</code> is just one of several animation elements in SVG. Together with an <code>attributeName</code> value, it enables animations based on one or more of an element’s attributes.</p>

<p>Most animation explanations start by moving a primitive shape, like this exciting circle:</p>

<pre><code class="language-svg">&lt;circle
  r="50"
  cx="50" 
  cy="50" 
  fill="#062326" 
  opacity="1"
/&gt;
</code></pre>

<p>Using this <code>attributeName</code> property, I can define which of this circle’s attributes I want to animate, which, in this example, is its <code>cx</code> (x-axis center point) position:</p>

<pre><code class="language-svg">&lt;circle ... &gt;
  &lt;animate attributename="cx"&gt;&lt;/animate&gt;
&lt;/circle&gt;
</code></pre>

<p>On its own, this does precisely nothing until I define three more values. The <code>from</code> keyword specifies the circle’s initial position, <code>to</code>, its final position, and the <code>dur</code>-ation between those two positions:</p>

<pre><code class="language-svg">&lt;circle ... &gt;
  &lt;animate 
  attributename="cx"
  from="50" 
  to="500"
  dur="1s"&gt;
  &lt;/animate&gt;
&lt;/circle&gt;
</code></pre>

<p>If I want more precise control, I can replace <code>from</code> and <code>to</code> with a set of <code>values</code> separated by semicolons:</p>

<pre><code class="language-svg">&lt;circle ... &gt;
  &lt;animate 
  attributename="cx"
  values="50; 250; 500; 250;"
  dur="1s"&gt;
  &lt;/animate&gt;
&lt;/circle&gt;
</code></pre>

<p>Finally, I can define how many times the animation repeats (<code>repeatcount</code>) and even after what period that repeating should stop (<code>repeatdur</code>):</p>

<pre><code class="language-svg">&lt;circle ... &gt;
  &lt;animate 
  attributename="cx"
  values="50; 250; 500; 250;"
  dur="1s"
  repeatcount="indefinite"
  repeatdur="180s"&gt;
&lt;/circle&gt;
</code></pre>

<p>Most SVG elements have attributes that can be animated. This title card from 1959’s <a href="https://yogibear.fandom.com/wiki/Brainy_Bear">“Brainy Bear” episode</a> shows Yogi in a crazy scientist‘s brain experiment. Yogi’s head is under the dome, and energy radiates around him.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="540"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustration where Yogi’s head is under the dome, and energy radiates around him"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>To create the buzz around Yogi, my SVG includes three <code>path</code> elements, each with <code>opacity</code>, <code>stroke</code>, and <code>stroke-width</code> attributes, which can all be animated:</p>

<pre><code class="language-svg">&lt;path opacity="1" stroke="#fff" stroke-width="5" ... /&gt;
</code></pre>

<p>I animated each path’s <code>opacity</code>, changing its value from <code>1</code> to <code>.5</code> and back again:</p>

<pre><code class="language-svg">&lt;path opacity="1" ... &gt;
  &lt;animate 
    attributename="opacity"
    values="1; .25; 1;"
    dur="1s"
    repeatcount="indefinite"&gt;
  &lt;/animate&gt;
&lt;/path&gt;
</code></pre>

<p>Then, to radiate energy from Yogi, I specified when each animation should <code>begin</code>, using a different value for each <code>path</code>:</p>

<pre><code class="language-svg">&lt;path ... &gt;
  &lt;animate begin="0" … &gt;
&lt;/path&gt;

&lt;path ... &gt;
  &lt;animate begin=".5s" … &gt;
&lt;/path&gt;

&lt;path ... &gt;
  &lt;animate begin="1s" … &gt;
&lt;/path&gt;
</code></pre>

<p>I’ll explain more about the <code>begin</code> property and how to start animations after this short commercial break.</p>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="qEEzYgG"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Brainy Bear SVG animation [forked]](https://codepen.io/smashingmag/pen/qEEzYgG) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/qEEzYgG">Brainy Bear SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>To make animations appear more natural, I can apply more than one <code>animate</code> element and give each one a different <code>attributename</code> value. Those paths also contain a <code>stroke-width</code> attribute, which I can also animate by changing the stroke widths between <code>5</code> and <code>7</code>:</p>

<pre><code class="language-svg">&lt;path ... &gt;
  &lt;animate attributename="opacity" ... &gt;&lt;/animate&gt;
  &lt;animate attributename="stroke-width" ... &gt;&lt;/animate&gt;
&lt;/path&gt;
</code></pre>

<p>Finally, I can animate the dome over Yogi’s head, changing its <code>fill</code> colour between two values over five seconds to create the impression that the crazy scientist’s machine is heating up:</p>

<pre><code class="language-svg">&lt;path fill="#50D9E0" ... &gt;
  &lt;animate
    attributename="fill"
    values="#50D9E0; #E18C50;"
    dur="5s"
    begin="2s"
  &gt;
&lt;/path&gt;
</code></pre>

<p>Implement that code, and you’ll soon notice that the dome returns to its original state after the animation is complete. To retain its colour at the end of the animation, I can add the &mdash; confusingly named &mdash; <code>fill</code> property and a value of <code>freeze.</code> This stops the animation in its final state and prevents it from returning to the original colour:</p>

<pre><code class="language-svg">&lt;path fill="#50D9E0" ... &gt;
  &lt;animate fill="freeze"&gt;
&lt;/path&gt;
</code></pre>

<p>Animating attributes brings these title card designs to life, whether by adjusting the position of a primitive shape, its opacity, and stroke width or by creating complex sequences with staggered timing. But there’s still more I can do, starting with the next animation element, <code>animateTransform</code>.</p>

<div class="partners__lead-place"></div>

<h2 id="animatetransform"><code>animateTransform</code></h2>

<p>If <code>&lt;animate&gt;</code> controls attributes, then <code>animateTransform</code> animates transformations, including rotations, scaling, skewing, and translations. It works by changing the values of a transform property, like this <code>translate</code>:</p>

<pre><code class="language-svg">&lt;path transform="translate(0,0)"/&gt;
</code></pre>

<p>Then, the animation works the same way as <code>&lt;animate&gt;</code>, adding an <code>attributename</code> and specifying the type of transform, in this example, <code>rotate:</code></p>

<pre><code class="language-svg">&lt;animatetransform 
  attributename="transform"
  type="rotate"&gt;
&lt;/animatetransform&gt;
</code></pre>

<p>I can use either <code>from</code> and <code>to</code> or the <code>values</code> attribute to define how an element is transformed.</p>

<ul>
<li><strong>Scale</strong> uses <code>x</code> and <code>y</code> values  (<code>.5</code>, <code>1</code>).</li>
<li><strong>Rotate</strong> uses degrees (<code>0</code>–<code>360</code>) plus optional <code>x</code> and <code>y</code> (<code>360</code>, <code>0</code>, <code>0</code>).</li>
<li><strong>Translate</strong> also uses <code>x</code> and <code>y</code> values  (<code>50</code>, <code>100</code>).</li>
<li><strong>Skew</strong> uses <code>x</code> and <code>y</code> values, too (<code>50</code>, <code>100</code>).</li>
</ul>

<p>What’s interesting about those values is that they can be added to an element’s existing values instead of replacing them. For example, when an attribute contains a <code>translate</code> value of <code>100, 0</code>:</p>

<pre><code class="language-svg">&lt;path transform="translate(100, 0)"/&gt;
</code></pre>

<p>And then I animate that translation horizontally by <code>100</code>:</p>

<pre><code class="language-svg">&lt;animatetransform
  attributename="transform"
  type="translate"
  from="0, 0"
  to="100, 0"
  additive="sum"&gt;
&lt;/animatetransform&gt;
</code></pre>

<p>Using the <code>additive</code> property with a value of <code>sum</code>, the animation values are relative to the original, starting the animation at <code>100</code> and ending at <code>200</code> by adding <code>100</code> to <code>100</code>.</p>

<p>Similarly, if I give the <code>accumulate</code> property a value of <code>sum</code>, each instance of animation will build on the last. So, in an animation where an element is translated by <code>100</code> and repeats five times, each movement will be cumulative, moving the element by <code>500</code>:</p>

<pre><code class="language-svg">&lt;animatetransform
  attributename="transform"
  type="translate"
  from="0, 0"
  to="100, 0"
  additive="sum"
  accumulate="sum" 
/&gt;
</code></pre>

<p>This title card from 1958’s Yogi Bear’s <a href="https://yogibear.fandom.com/wiki/Yogi_Bear%27s_Big_Break">“Big Break” episode</a> shows Yogi floating from the sky under a parachute.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="540"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustration shows Yogi floating from the sky under a parachute"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>I needed two types of transform animations to generate the effect of Yogi drifting gently downwards: <code>translate</code>, and <code>rotate</code>. I first added an <code>animatetransform</code> element to the group, which contains Yogi and his chute. I defined his initial vertical position &mdash; <code>1200</code> off the top of the <code>viewBox</code> &mdash; then translated his descent to <code>1000</code> over a 15-second duration:</p>

<pre><code class="language-svg">&lt;g transform="translate(1200, -1200)"&gt;
  ...
  &lt;animateTransform
    attributeName="transform"
    type="translate"
    values="500,-1200; 500,1000"
    dur="15s"
    repeatCount="1" 
  /&gt;
&lt;/g&gt;
</code></pre>

<p>Yogi appears to fall from the sky, but the movement looks unrealistic. So, I added a second <code>animatetransform</code> element, this time with an indefinitely repeating +/- 5-degree rotation to swing Yogi from side to side during his descent:</p>

<pre><code class="language-svg">&lt;animateTransform
  attributeName="transform"
  type="rotate"
  values="-5; 5; -5"
  dur="14s"
  repeatCount="indefinite"
  additive="sum" 
/&gt;
</code></pre>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="PwwraNm"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Big Break SVG animation [forked]](https://codepen.io/smashingmag/pen/PwwraNm) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/PwwraNm">Big Break SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<h2 id="starting-and-stopping">Starting And Stopping</h2>

<p>So far, every animation begins as soon as the page has loaded. But there are ways to not only delay the start of animation but define precisely where it begins, using the begin <code>property</code>:</p>

<p>In this title card from 1959’s <a href="https://yogibear.fandom.com/wiki/Robin_Hood_Yogi">“Robin Hood Yogi”</a>, Yogi shoots an arrow into an apple on Boo-Boo’s head.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="540"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustration where Yogi shoots an arrow into an apple on Boo-Boo’s head"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>By default, the arrow is set loose when the page loads. Blink, and you might miss it. To build some anticipation, I can <code>begin</code> the animation two seconds later:</p>

<pre><code class="language-svg">&lt;animatetransform
  attributename="transform"
  type="translate"
  from="0 0"
  to="750 0"
  dur=".25s"
  begin="2s"
  fill="freeze"
/&gt;
</code></pre>

<p>Or, I can let the viewer take the shot when they click the arrow:</p>

<pre><code class="language-svg">&lt;animatetransform
  ...
  begin="click"
/&gt;
</code></pre>

<p>And I can combine the click event and a delay, all with no JavaScript, just a smattering of SMIL:</p>

<pre><code class="language-svg">&lt;animatetransform
  ...
  begin="click + .5s"
/&gt;
</code></pre>

<p>Try this yourself by clicking the arrow:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="OPPeERj"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Robin Hood Yogi CSS animation [forked]](https://codepen.io/smashingmag/pen/OPPeERj) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/OPPeERj">Robin Hood Yogi CSS animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<h2 id="synchronising-animations">Synchronising Animations</h2>

<p>In his 1958 <a href="https://yogibear.fandom.com/wiki/Pie-Pirates">“Pie-Pirates” episode</a>, Yogi Bear tries to steal a pie and has to outwit a bulldog. The title card &mdash; designed by Lawrence Goble &mdash; shows the chase but, alas, (spoiler alert) no stolen pie.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="540"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustration showing a bulldog chasing Yogi Bear"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>To bring this title card to life, I needed two groups of paths: one for Yogi and the other for the dog. I translated them both off the left edge of the <code>viewBox</code>:</p>

<pre><code class="language-svg">&lt;g class="dog" transform="translate(-1000, 0)"&gt;
  ...
&lt;/g&gt;

&lt;g class="yogi" transform="translate(-1000, 0)"&gt;
  ...
&lt;/g&gt;
</code></pre>

<p>Then, I applied an <code>animatetransform</code> element to both groups, which moves them back into view:</p>

<pre><code class="language-svg">&lt;!-- yogi --&gt;
&lt;animateTransform
  attributeName="transform"
  type="translate"
  from="-1000,0"
  to="0,0"
  dur="2s"
  fill="freeze"
/&gt;

&lt;!-- dog --&gt;
&lt;animateTransform
  attributeName="transform"
  type="translate"
  from="-1000,0"
  to="0,0"
  dur=".5s"
  fill="freeze"
/&gt;
</code></pre>

<p>This sets up the action, but the effect feels flat, so I added another pair of animations that bounce both characters:</p>

<pre><code class="language-svg">&lt;!-- yogi --&gt;
&lt;animateTransform
  attributeName="transform"
  type="rotate"
  values="-1,0,450; 1,0,450; -1,0,450"
  dur=".25s"
  repeatCount="indefinite"
/&gt;

&lt;!-- dog --&gt;
&lt;animateTransform
  attributeName="transform"
  type="rotate"
  values="-1,0,450; 1,0,450; -1,0,450"
  dur="0.5s"
  repeatCount="indefinite"
/&gt;
</code></pre>

<p>Animations can begin when a page loads, after a specified time, or when clicked. And by naming them, they can also synchronise with other animations.</p>

<p>I wanted Yogi to enter the frame first to build anticipation, with a short pause before other animations begin, synchronising to the moment he’s arrived. First, I added an ID to Yogi’s <code>translate</code> animation:</p>

<pre><code class="language-svg">&lt;animateTransform
  id="yogi"
  type="translate"
  ...
/&gt;
</code></pre>

<blockquote><strong>Watch out</strong>: For a reason, I can’t, for the life of me, explain why Firefox won’t begin animations with an ID when the ID contains a hyphen. This isn’t smarter than the average browser, but replacing hyphens with underscores fixes the problem.</blockquote>

<p>Then, I applied a <code>begin</code> to his <code>rotate</code> animation, which starts playing a half-second after the <code>#yogi</code> animation ends:</p>

<pre><code class="language-svg">&lt;animateTransform
  type="rotate"
  begin="yogi.end + .5s"
  ...
/&gt;
</code></pre>

<p>I can build sophisticated sets of synchronised animations using the <code>begin</code> property and whether a named animation begins or ends. The bulldog chasing Yogi enters the frame two seconds after Yogi begins his entrance:</p>

<pre><code class="language-svg">&lt;animateTransform
  id="dog"
  type="translate"
  begin="yogi.begin + 2s"
  fill="freeze"
  ...
/&gt;
</code></pre>

<p>One second after the dog has caught up with Yogi, a <code>rotate</code> transformation makes him bounce, too:</p>

<pre><code class="language-svg">&lt;animateTransform
  type="rotate"
  ...
  begin="dog.begin + 1s"
  repeatCount="indefinite" 
/&gt;
</code></pre>

<p>The background rectangles whizzing past are also synchronised, this time to one second before the bulldog ends his run:</p>

<pre><code class="language-svg">&lt;rect ...&gt;
  &lt;animateTransform
    begin="dog.end + -1s"
  /&gt;
&lt;/rect&gt;
</code></pre>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="LEEKryp"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Pie-Pirates SVG animation [forked]](https://codepen.io/smashingmag/pen/LEEKryp) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/LEEKryp">Pie-Pirates SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>The timing of this background movement is synchronised with the dog arriving, which, in turn, is relative to Yogi’s arrival, building a sequence of animations that all feel connected.</p>

<div class="partners__lead-place"></div>

<h2 id="animating-along-motion-paths">Animating Along Motion Paths</h2>

<p>Until now, all the animations in these title cards have been up, down, left, right, or one combination or another. But there’s one more aspect of SMIL in SVG, which can add an extra dimension to animations: animating along motion paths using the <code>animatemotion</code> element.</p>

<p><code>animatemotion</code> accepts all the same properties and values as <code>animate</code> and <code>animateTransform</code>, but adds a few more for finer control over direction and timing. <code>animatemotion</code> uses the <code>path</code> property to enable elements to move along a motion path. It also uses the <code>d</code> value for coordinate data in the same way as any conventional path.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="540"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustration of the Runaway Bear"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In <a href="https://yogibear.fandom.com/wiki/The_Runaway_Bear">“The Runaway Bear”</a> from 1959, Yogi must avoid a hunter turning his head into a trophy. I wanted Yogi to leap in and out of the screen by making him follow a path. I also wanted to vary the speed of his dash: speeding up as he enters and exits, and slowing down as he passes the title text.</p>

<p>I first added a <code>path</code> property, using its coordinate data to give Yogi a route to follow, and specified a two-second duration for my animation:</p>

<pre><code class="language-svg">&lt;g&gt;
  &lt;animateMotion
    dur="2s"
    path="..."
  &gt;
  &lt;/animateMotion&gt;
&lt;/g&gt;
</code></pre>

<p>Alternatively, I could add a <code>path</code> element, leave it visible, or prevent it from being rendered by placing it inside a <code>defs</code> element:</p>

<pre><code class="language-svg">&lt;defs&gt;
  &lt;path id="yogi" d="..." /&gt;
&lt;/defs&gt;
</code></pre>

<p>I can then reference that by using a <code>mpath</code> element inside my <code>animateMotion</code>:</p>

<pre><code class="language-svg">&lt;animateMotion
  ...
  &lt;mpath href="#yogi" /&gt;
&lt;/animateMotion&gt;
</code></pre>

<p>I experimented with several paths before settling on the one that delivered the movement shape I was looking for:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png"
			
			sizes="100vw"
			alt="Several variants of the Yogi Bear title card recreated by Andy Clarke"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1959.) Author’s recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>One was too bouncy, one was too flat, but the third motion path was just right. Almost, as I also wanted to vary the speed of Yogi’s dash: speeding him up as he enters and exits and slowing him down as he passes the title text.</p>

<p>The <code>keyPoints</code> property enabled me to specify points along the motion path and then adjust the duration Yogi spends between them. To keep things simple, I defined five points between <code>0</code> and <code>1</code>:</p>

<pre><code class="language-svg">&lt;animateMotion
  ...
  keyPoints="0; .35; .5; .65; 1;"
&gt;
&lt;/animateMotion&gt;
</code></pre>

<p>Then I added the same number of <code>keyTimes</code> values, separated by semicolons, to control the pacing of this animation:</p>

<pre><code class="language-svg">&lt;animateMotion
  ...
  keyTimes="0; .1; .5; .95; 1;"
&gt;
&lt;/animateMotion&gt;
</code></pre>

<p>Now, Yogi rushes through the first three <code>keyPoints</code>, slows down as he passes the title text, then speeds up again as he exits the <code>viewBox</code>.</p>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="oggryox"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Runaway Bear SVG animation [forked]](https://codepen.io/smashingmag/pen/oggryox) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/oggryox">Runaway Bear SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<h2 id="smil-s-not-dead-baby-smil-s-not-dead">SMIL’s Not Dead, Baby. SMIL’s Not Dead</h2>

<p>With their ability to control transformations, animate complex motion paths, and synchronise multiple animations, SMIL animations in SVG are still powerful tools. They can bring design to life without needing a framework or relying on JavaScript. It’s compact, which makes it great for small SVG effects.</p>

<p>SMIL includes the <code>begin</code> attribute, which makes chaining animations far more intuitive than with CSS. Plus, SMIL lives inside the SVG file, making it perfect for animations that travel with an asset. So, while SMIL is not modern by today’s standards and may be a little bit niche, it can still be magical.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aDon%e2%80%99t%20let%20the%20misconception%20that%20SMIL%20is%20%e2%80%9cdead%e2%80%9d%20stop%20you%20from%20using%20this%20fantastic%20tool.%0a&url=https://smashingmagazine.com%2f2025%2f05%2fsmashing-animations-part-3-smil-not-dead%2f">
      
Don’t let the misconception that SMIL is “dead” stop you from using this fantastic tool.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<p>Google reversed its decision to deprecate SMIL almost a decade ago, so it remains a terrific choice for designers and developers who want <strong>simple</strong>, <strong>semantic ways</strong> to add animations to their designs.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Akshay Gupta</author><title>Mastering SVG Arcs</title><link>https://www.smashingmagazine.com/2024/12/mastering-svg-arcs/</link><pubDate>Mon, 09 Dec 2024 09:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2024/12/mastering-svg-arcs/</guid><description>SVG arcs demystified! Akshay Gupta explains how to master radii, rotation, and arc direction to create stunning curves. Make arcs a powerful part of your SVG toolkit for creating more dynamic, intricate designs with confidence.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2024/12/mastering-svg-arcs/" />
              <title>Mastering SVG Arcs</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Mastering SVG Arcs</h1>
                  
                    
                    <address>Akshay Gupta</address>
                  
                  <time datetime="2024-12-09T09:00:00&#43;00:00" class="op-published">2024-12-09T09:00:00+00:00</time>
                  <time datetime="2024-12-09T09:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>So, <strong>I love drawing birds with code.</strong> Inspired by my brother’s love for birdwatching, I admire the uniqueness of their feathers, colors, and sounds. But what I notice most is the way their bodies curve and different birds can have dramatically different curves! So, I took my love for drawing with SVG graphics and used it to experiment with bird shapes. Over time, I’ve drawn enough to become incredibly adept at working with arc shapes.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/mastering-svg-arcs/1-birds-with-code-svg.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="800"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/mastering-svg-arcs/1-birds-with-code-svg.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/mastering-svg-arcs/1-birds-with-code-svg.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/mastering-svg-arcs/1-birds-with-code-svg.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/mastering-svg-arcs/1-birds-with-code-svg.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/mastering-svg-arcs/1-birds-with-code-svg.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/mastering-svg-arcs/1-birds-with-code-svg.jpg"
			
			sizes="100vw"
			alt="Five examples of birds drawn in SVG, including a peacock, robin, parakeet, parrot, and toucan."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/mastering-svg-arcs/1-birds-with-code-svg.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Here are a few of my recent works. Inspired by designs I came across on <a href="https://dribbble.com">Dribbble</a>, I created my versions with code. You can browse through the code for each on my <a href="https://codepen.io/akshaygpt">CodePen</a>.</p>

<p>But before we dive into creating curves with arcs, please pause here and check out Myriam Frisano’s recent article, “<a href="https://www.smashingmagazine.com/2024/09/svg-coding-examples-recipes-writing-vectors-by-hand/">SVG Coding Examples: Useful Recipes For Writing Vectors By Hand</a>.” It’s an excellent primer to the SVG syntax and it will give you solid context heading into the concepts we’re covering here when it comes to mastering <strong>SVG arcs</strong>.</p>

<h2 id="a-quick-svg-refresher">A Quick SVG Refresher</h2>

<p>You probably know that SVGs are crisp, infinitely scalable illustrations without pixelated degradation &mdash; vectors for the win! What you might not know is that <strong>few developers write SVG code.</strong> Why? Well, the syntax looks complicated and unfamiliar compared to, say, HTML. But trust me, once you break it down, it’s not only possible to hand-code SVG but also quite a bit of fun.</p>

<p>Let’s make sure you’re up to speed on the SVG <code>viewBox</code> because it’s a key concept when it comes to the <em>scalable</em> part of *SVG. We’ll use the analogy of a camera, lens, and canvas to explain this concept. Think of your browser window as a camera and the SVG <code>viewBox</code> as the camera lens focusing on the painting of a bird you’ve created (the SVG). Imagine the painting on a large canvas that may stretch far beyond what the camera captures. The <code>viewBox</code> defines which part of this canvas is visible through the camera.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/mastering-svg-arcs/2-svg-viewbox.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="555"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/mastering-svg-arcs/2-svg-viewbox.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/mastering-svg-arcs/2-svg-viewbox.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/mastering-svg-arcs/2-svg-viewbox.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/mastering-svg-arcs/2-svg-viewbox.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/mastering-svg-arcs/2-svg-viewbox.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/mastering-svg-arcs/2-svg-viewbox.png"
			
			sizes="100vw"
			alt="Illustrating the viewBox in green, like a camera lens."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/mastering-svg-arcs/2-svg-viewbox.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Let’s say we have an SVG element that we’re sizing at <code>600px</code> square with <code>width</code> and <code>height</code> attributes directly on the <code>&lt;svg&gt;</code> element.</p>

<pre><code class="language-svg">&lt;svg width="600px" height="600px"&gt;
</code></pre>
 

<p>Let’s turn our attention to the <code>viewBox</code> attribute:</p>

<pre><code class="language-svg">&lt;svg width="600px" height="600px" viewBox="-300 -300 600 600"&gt;
</code></pre>

<p>The <code>viewBox</code> attribute defines the internal coordinate system for the SVG, with four values mapping to the SVG’s x, y, width, and height in that order.</p>

<p>Here’s how this relates to our analogy:</p>

<ul>
<li><strong>Camera Position and Size</strong><br />
The <code>-300, -300</code> represents the camera lens’ left and top edge position. Meanwhile, <code>600 x 600</code> is like the camera’s frame size, showing a specific portion of that space.</li>
<li><strong>Unchanging Canvas Size</strong><br />
Changing the <code>x</code> and <code>y</code> values adjusts where the camera points, and <code>width</code> and <code>height</code> govern how much of the canvas it frames. It doesn’t resize the actual canvas (the SVG element itself, which remains at <code>600</code>×<code>600</code> pixels). No matter where the camera is positioned or zoomed, the canvas itself remains fixed.</li>
</ul>

<p>So, when you adjust the <code>viewBox</code> coordinates, you’re simply choosing a new area of the canvas to focus on without resizing the canvas itself. This lets you control the visible area without changing the SVG’s actual display dimensions.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/mastering-svg-arcs/3-coordinates-viewbox.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="663"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/mastering-svg-arcs/3-coordinates-viewbox.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/mastering-svg-arcs/3-coordinates-viewbox.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/mastering-svg-arcs/3-coordinates-viewbox.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/mastering-svg-arcs/3-coordinates-viewbox.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/mastering-svg-arcs/3-coordinates-viewbox.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/mastering-svg-arcs/3-coordinates-viewbox.png"
			
			sizes="100vw"
			alt="Demonstrating coordinates for the top-left corner (-300,-300), center (0,0), and the direction of x and y axis (left to right and top to bottom respectively) of the viewBox."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/mastering-svg-arcs/3-coordinates-viewbox.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You now have the context you need to learn how to work with <code>&lt;path&gt;</code> elements in SVG, which is where we start working with arcs!</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p><strong>Web forms</strong> are at the center of every meaningful interaction. Meet Adam Silver&rsquo;s <strong><a href="https://www.smashingmagazine.com/printed-books/form-design-patterns/">Form Design Patterns</a></strong>, a practical guide to <strong>designing and building forms</strong> for the web.</p>
<a data-instant href="https://www.smashingmagazine.com/printed-books/form-design-patterns/" class="btn btn--green btn--large" style="">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://www.smashingmagazine.com/printed-books/form-design-patterns/" class="feature-panel-image-link">
<div class="feature-panel-image"><picture><source type="image/avif" srcSet="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/64e57b41-b7f1-4ae3-886a-806cce580ef9/form-design-patterns-shop-image-1-1.avif" />
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/51e0f837-d85d-4b28-bfab-1c9a47f0ce33/form-design-patterns-shop-image.png"
    alt="Feature Panel"
    width="481"
    height="698"
/>
</picture>
</div>
</a>
</div>
</aside>
</div>

<h2 id="the-path-element">The <code>&lt;path&gt;</code> Element</h2>

<p>We have an <code>&lt;svg&gt;</code> element. And we’re viewing the element’s contents through the “lens” of a <code>viewBox</code>.</p>

<p>A <code>&lt;path&gt;</code> allows us to draw shapes. We have other elements for drawing shapes &mdash; namely <code>&lt;circle&gt;</code>, <code>&lt;line&gt;</code>, and <code>&lt;polygon&gt;</code> &mdash; but imagine being restricted to strict geometrical shapes as an artist. That’s where the custom <code>&lt;path&gt;</code> element comes in. It’s used to draw complex shapes that cannot be created with the basic ones. Think of <code>&lt;path&gt;</code> as a flexible container that lets you mix and match different drawing commands.</p>

<p>With a single <code>&lt;path&gt;</code>, you can combine multiple drawing commands into one smooth, elegant design. Today, we’re focusing on a super specific path command: <strong>arcs.</strong> In other words, what we’re doing is drawing arc shapes with <code>&lt;path&gt;</code>.</p>

<p>Here’s a quick, no-frills example that places a <code>&lt;path&gt;</code> inside the <code>&lt;svg&gt;</code> example we looked at earlier:</p>

<div class="break-out">
<pre><code class="language-svg">&lt;svg width="600px" height="600px" viewBox="-300 -300 600 600"&gt;</code>
  <code style="font-weight: bold;">&lt;path d="M 0 0 A 100 100 0 1 1 200 0"</code> 
    <code class="language-svg">fill="transparent"
    stroke="black"
    stroke-width="24"
  /&gt;
&lt;/svg&gt;
</code></pre>
</div>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="bNbeQQy"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Arc path animation [forked]](https://codepen.io/smashingmag/pen/bNbeQQy) by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/bNbeQQy">Arc path animation [forked]</a> by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</figcaption>
</figure>

<p>Now, I get it. Looking at that string of numbers for the first time is like staring into the Matrix, right? But once you get the hang of it, you’ll see that arcs aren’t as scary as they look.</p>

<p>Let’s break down the <code>&lt;path&gt;</code> in that example. We’ll break it down even further in the next section, but for now:</p>

<ul>
<li><code>M 0 0</code> moves the path to the center of the <code>viewBox</code> but doesn’t actually “draw” anything just yet.</li>
<li><code>A 100 100 0 1 1 200 0</code> draws an arc with a radius of <code>100</code> in both the X and Y axes, ending at <code>(200, 0)</code>.</li>
</ul>

<p>You can visualize the coordinate positions in red resulting from different <code>M</code> commands in the following demo:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="EaYyOGW"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Arc Possibilities b/w 2 points [forked]](https://codepen.io/smashingmag/pen/EaYyOGW) by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/EaYyOGW">Arc Possibilities b/w 2 points [forked]</a> by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</figcaption>
</figure>

<p>See that? We have two points along the X-axis that are relative to the <code>viewBox</code>&rsquo;s center, and a curved line connects them. Now, know that the numbers in an <code>M</code> command are setting coordinates, and the numbers in an <code>A</code> command draw a line along the SVG’s axes. You just drew a curve in SVG!</p>

<div class="partners__lead-place"></div>

<h2 id="dissecting-an-arc">Dissecting An Arc</h2>

<p>We can zoom into the <code>M</code> and <code>A</code> commands even further to better understand what’s happening.</p>

<pre><code class="language-svg">&lt;path d="M 0 0 A 100 100 0 1 1 200 0" /&gt;
</code></pre>

<p>First off, we’re working with an arc, or more accurately, an <strong>elliptical arc</strong>, which is a curved line. We know that a perfect circle is merely an <em>ellipse</em> with equal radii in both the X and Y directions. We can change the shape of the circle by giving it different, unmatching radii values.</p>

<p>This is what we know so far:</p>

<ul>
<li><code>M</code>

<ul>
<li><strong><code>0</code>:</strong>  Coordinate along the X-axis.</li>
<li><strong><code>0</code>:</strong> Coordinate along the Y-axis.</li>
</ul></li>
<li><code>A</code>

<ul>
<li><strong><code>100</code>:</strong> Radius value in the X direction.</li>
<li><strong><code>100</code>:</strong> Radius value in the Y direction.</li>
<li><strong><code>200</code>:</strong> The arc’s endpoint in the X-direction.</li>
<li><strong><code>0</code>:</strong> The arc’s endpoint in the Y-direction.</li>
</ul></li>
</ul>

<p>There are three values in the <code>A</code> command that we sort of skipped. These are like “switches” in the sense that they are Boolean values that enable or disable certain things about the arc.</p>

<ul>
<li><strong><code>0</code>:</strong> Rotates the arc along the X-axis.</li>
<li><strong><code>1</code>:</strong> Determines whether this is a “small” arc (<code>0</code>) with a span greater than 180° or a “large” arc (<code>1</code>) with a span greater than 180°.</li>
<li><strong><code>1</code>:</strong> Sets whether the arc &ldquo;sweeps” in a clockwise direction or a counter-clockwise direction, where <code>0</code> equals clockwise and <code>1</code> equals counter-clockwise.</li>
</ul>

<p>If we take this information and re-write the <code>&lt;path&gt;</code> with these definitions, then it starts to come together more clearly:</p>

<div class="break-out">
<pre><code class="language-svg">&lt;path d="
  M &lt;x-coordinate&gt; &lt;y-coordinate&gt; 
  A &lt;radius-x&gt; &lt;radius-y&gt; &lt;rotation-x&gt; &lt;large-arc-flag&gt; &lt;sweep-flag&gt; &lt;endpoint-x&gt; &lt;endpoint-y&gt;
" /&gt;
</code></pre>
</div>

<p>Maybe we can simplify that a bit using abbreviations:</p>

<pre><code class="language-svg">&lt;path d="
  M &lt;x&gt; &lt;y&gt; 
  A &lt;rx&gt; &lt;ry&gt; &lt;rotation&gt; &lt;arc&gt; &lt;sweep&gt; &lt;ex&gt; &lt;ey&gt;
" /&gt;
</code></pre>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/mastering-svg-arcs/4-illustrating-arc-x-y-radii.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="754"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/mastering-svg-arcs/4-illustrating-arc-x-y-radii.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/mastering-svg-arcs/4-illustrating-arc-x-y-radii.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/mastering-svg-arcs/4-illustrating-arc-x-y-radii.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/mastering-svg-arcs/4-illustrating-arc-x-y-radii.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/mastering-svg-arcs/4-illustrating-arc-x-y-radii.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/mastering-svg-arcs/4-illustrating-arc-x-y-radii.png"
			
			sizes="100vw"
			alt="Illustrating an arc’s X and Y radii, as well as the amount of its rotation."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/mastering-svg-arcs/4-illustrating-arc-x-y-radii.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Let’s take this information and start playing with values to see how it behaves.</p>

<div class="partners__lead-place"></div>

<h2 id="visualizing-the-possibilities">Visualizing The Possibilities</h2>

<p>Again, if this is the <code>&lt;path&gt;</code> we’re starting with:</p>

<pre><code class="language-svg">&lt;path d="M 0 0 A 100 100 0 1 1 200 0"/&gt;
</code></pre>

<p>Then, we can manipulate it in myriad ways. Mathematically speaking, you can create an infinite number of arcs between any two points by adjusting the parameters. Here are a few variations of an arc that we get when all we do is change the arc’s endpoints in the X (<code>&lt;ex&gt;</code>) and Y (<code>&lt;ey&gt;</code>) directions.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="WbexYLV"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Arc Possibilities b/w 2 points [forked]](https://codepen.io/smashingmag/pen/WbexYLV) by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/WbexYLV">Arc Possibilities b/w 2 points [forked]</a> by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</figcaption>
</figure>

<p>Or, let’s control the arc’s width and height by updating its radius in the X direction (<code>&lt;rx&gt;</code>) and the Y direction (<code>&lt;ry&gt;</code>). If we play around with the <code>&lt;rx&gt;</code> value, we can manipulate the arc’s height:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="xbKOQMr"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Rx [forked]](https://codepen.io/smashingmag/pen/xbKOQMr) by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/xbKOQMr">Rx [forked]</a> by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</figcaption>
</figure>

<p>Similarly, we can manipulate the arc’s width by updating the <code>&lt;ry&gt;</code> value:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="mybEQvG"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Ry [forked]](https://codepen.io/smashingmag/pen/mybEQvG) by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/mybEQvG">Ry [forked]</a> by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</figcaption>
</figure>

<p>Let’s see what happens when we rotate the arc along its X-axis (<code>&lt;rotation&gt;</code>). This parameter rotates the arc’s ellipse around its center. It won’t affect circles, but it’s a game-changer for ellipses.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="VYZjVRx"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [x-axis-rotation [forked]](https://codepen.io/smashingmag/pen/VYZjVRx) by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/VYZjVRx">x-axis-rotation [forked]</a> by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</figcaption>
</figure>

<p>Even with a fixed set of endpoints and radii (<code>&lt;rx&gt;</code> and <code>&lt;ry&gt;</code>), and a given angle of rotation, four distinct arcs can connect them. That’s because we have the <code>&lt;arc&gt;</code> flag value that can be one of two values, as well as the <code>&lt;sweep&gt;</code> flag that is also one of two values. Two boolean values, each with two arguments, give us four distinct possibilities.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="wBwWQOb"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [4 cases [forked]](https://codepen.io/smashingmag/pen/wBwWQOb) by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/wBwWQOb">4 cases [forked]</a> by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</figcaption>
</figure>

<p>And lastly, adjusting the arc’s endpoint along the X (<code>&lt;ex&gt;</code>) and Y (<code>&lt;ey&gt;</code>) directions shifts the arc’s location without changing the overall shape.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="xbKOQeL"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [endx, endy [forked]](https://codepen.io/smashingmag/pen/xbKOQeL) by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/xbKOQeL">endx, endy [forked]</a> by <a href="https://codepen.io/akshaygpt">akshaygpt</a>.</figcaption>
</figure>

<h2 id="wrapping-up">Wrapping Up</h2>

<p>And there you have it, SVG arcs demystified! Whether you’re manipulating radii, rotation, or arc direction, you now have all the tools to master these beautiful curves. With practice, arcs will become just another part of your SVG toolkit, one that gives you the power to create <strong>more dynamic, intricate designs</strong> with confidence.</p>

<p>So keep playing, keep experimenting, and soon you’ll be bending arcs like a pro &mdash; making your SVGs not just functional but beautifully artistic. If you enjoyed this dive into arcs, drop a like or share it with your friends. Let’s keep pushing the boundaries of what SVG can do!</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Myriam Frisano</author><title>SVG Coding Examples: Useful Recipes For Writing Vectors By Hand</title><link>https://www.smashingmagazine.com/2024/09/svg-coding-examples-recipes-writing-vectors-by-hand/</link><pubDate>Wed, 18 Sep 2024 09:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2024/09/svg-coding-examples-recipes-writing-vectors-by-hand/</guid><description>Myriam Frisano explores the basics of hand-coding SVGs with practical examples to demystify the inner workings of common SVG elements. In this guide, you’ll learn about asking the right questions to solve common positioning problems and how to leverage JavaScript so that, by the end, you can add “SVG coding” to your toolbox. You’ll also be able to declare proudly, “I know how to draw literal pictures with words!”</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2024/09/svg-coding-examples-recipes-writing-vectors-by-hand/" />
              <title>SVG Coding Examples: Useful Recipes For Writing Vectors By Hand</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>SVG Coding Examples: Useful Recipes For Writing Vectors By Hand</h1>
                  
                    
                    <address>Myriam Frisano</address>
                  
                  <time datetime="2024-09-18T09:00:00&#43;00:00" class="op-published">2024-09-18T09:00:00+00:00</time>
                  <time datetime="2024-09-18T09:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>Even though I am the kind of front-end engineer who manually cleans up SVG files when they are a mess, I never expected to become one of <em>those</em> people. You know, those crazy people that <em>draw with code.</em></p>

<p>But here we are.</p>

<p>I dove deep into SVG specs last winter when I created a project to <a href="https://code.halfapx.com/guideline-generator/">draw Calligraphy Grids</a>, and even though I knew the basic structures and rules of SVG, it was only then that I fully tried to figure out and understand what all of those numbers meant and how they interacted with each other.</p>

<p>And, once you get the hang of it, it is actually very interesting and quite fun to code SVG by hand.</p>

<blockquote><strong>No &lt;path&gt; ahead</strong><br /><br />We won’t go into more complex SVG shapes like <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths">paths</a> in this article, this is more about practical information for simple SVGs. When it comes to drawing curves, I still recommend using a tool like Illustrator or Affinity. However, if you are super into compounding your lines, a path is useful. Maybe we’ll do that in Part 2.<br /><br />Also, this guide focuses mostly on practical examples that illustrate some of the math involved when drawing SVGs. There is a wonderful article here that goes a bit deeper into the specs, which I recommend reading if you’re more interested in that: “<a href="https://www.smashingmagazine.com/2019/05/svg-design-tools-practical-guide/#comments-svg-design-tools-practical-guide">A Practical Guide To SVG And Design Tools</a>.”</blockquote>

<h2 id="drawing-with-math-remember-coordinate-systems">Drawing With Math. Remember Coordinate Systems?</h2>

<p>Illustrator, Affinity, and all other vector programs are basically just helping you draw on a coordinate system, and then those paths and shapes are stored in SVG files.</p>

<p>If you open up these files in an editor, you’ll see that they are just a bunch of paths that contain lots of numbers, which are coordinates in that coordinate system that make up the lines.</p>

<p>But, there is a difference between the all-powerful <code>&lt;path&gt;</code> and the other, more semantic elements like <code>&lt;rect&gt;</code>, <code>&lt;circle&gt;</code>, <code>&lt;line&gt;</code>, <code>&lt;ellipse&gt;</code>, <code>&lt;polygon&gt;</code>, and <code>&lt;polyline&gt;</code>.</p>

<p>These elements are not that hard to read and write by hand, and they open up a lot of possibilities to add animation and other fun stuff. So, while most people might only think of SVGs as never-pixelated, infinitely scaling images, they can also be quite comprehensive pieces of code.</p>

<h3 id="how-does-svg-work-unit-unit">How Does SVG Work? <code>unit != unit</code></h3>

<p>Before we get started on how SVG elements are drawn, let’s talk about the ways units work in SVG because they might be a bit confusing when you first get started.</p>

<p>The beauty of SVG is that it’s a vector format, which means that the units are somewhat detached from the browser and are instead just relative to the coordinate system you’re working in.</p>

<p>That means you would <strong>not</strong> use a unit within SVG but rather just use numbers and then define the size of the document you’re working with.</p>

<p>So, your <code>width</code> and <code>height</code> might be using CSS <code>rem</code> units, but in your <code>viewBox</code>, units become just a concept that helps you in establishing sizing relationships.</p>

<h3 id="what-is-the-viewbox">What Is The <code>viewBox</code>?</h3>

<p>The <code>viewBox</code> works a little bit like the CSS <code>aspect-ratio</code> property. It helps you establish a relationship between the width and the height of your coordinate system and sets up the box you’re working in. I tend to think of the <code>viewBox</code> as my “document” size.</p>

<p>Any element that is placed within the SVG with bigger dimensions than the <code>viewBox</code> will not be visible. So, the <code>viewBox</code> is the cutout of the coordinate system we’re looking through. The <code>width</code> and <code>height</code> attributes are unnecessary if there is a <code>viewBox</code> attribute.</p>

<p>So, in short, having an SVG with a <code>viewBox</code> makes it behave a lot like a regular image. And just like with images, it’s usually easiest to just set either a <code>width</code> or a <code>height</code> and let the other dimension be automatically sized based on the intrinsic aspect ratio dimensions.</p>

<p>So, if we were to create a function that draws an SVG, we might store three separate variables and fill them in like this:</p>

<pre><code class="language-html">`&lt;svg 
  width="${svgWidth}" 
  viewBox="0 0 ${documentWidth} ${documentHeight}" 
  xmlns="http://www.w3.org/2000/svg"
&gt;`;
</code></pre>

<h3 id="svg-things-of-note">SVG Things Of Note</h3>

<p>There is a lot to know about SVG: When you want to reuse an image a lot, you may want to turn it into a <code>symbol</code> that can then be referenced with a <code>use</code> tag, you can create sprites, and there are some best practices when using them for icons, and so on.</p>

<p>Unfortunately, this is a bit out of the scope of this article. Here, we’re mainly focusing on designing SVG files and not on how we can optimize and use them.</p>

<p>However, one thing of note that is easier to implement from the start is <strong>accessibility</strong>.</p>

<p>SVGs can be used in an <code>&lt;img&gt;</code> tag, where <code>alt</code> tags are available, but then you lose the ability to interact with your SVG code, so inlining might be your preference.</p>

<p>When inlining, it’s easiest to declare <code>role=&quot;img&quot;</code> and then add a <code>&lt;title&gt;</code> tag with your image title.</p>

<p><strong>Note</strong>: <em>You can check out <a href="https://www.smashingmagazine.com/2021/05/accessible-svg-patterns-comparison/">this article for SVG and Accessibility recommendations</a>.</em></p>

<pre><code class="language-javascript">&lt;svg
  role="img"
  [...attr]
&gt;
  &lt;title&gt;An accessible title&lt;/title&gt;
  &lt;!-- design code --&gt;
&lt;/svg&gt;
</code></pre>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p><strong>Web forms</strong> are at the center of every meaningful interaction. Meet Adam Silver&rsquo;s <strong><a href="https://www.smashingmagazine.com/printed-books/form-design-patterns/">Form Design Patterns</a></strong>, a practical guide to <strong>designing and building forms</strong> for the web.</p>
<a data-instant href="https://www.smashingmagazine.com/printed-books/form-design-patterns/" class="btn btn--green btn--large" style="">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://www.smashingmagazine.com/printed-books/form-design-patterns/" class="feature-panel-image-link">
<div class="feature-panel-image"><picture><source type="image/avif" srcSet="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/64e57b41-b7f1-4ae3-886a-806cce580ef9/form-design-patterns-shop-image-1-1.avif" />
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/51e0f837-d85d-4b28-bfab-1c9a47f0ce33/form-design-patterns-shop-image.png"
    alt="Feature Panel"
    width="481"
    height="698"
/>
</picture>
</div>
</a>
</div>
</aside>
</div>

<h2 id="drawing-svg-with-javascript">Drawing SVG With JavaScript</h2>

<p>There is usually some mathematics involved when drawing SVGs. It’s usually fairly simple arithmetic (except, you know, in case you draw calligraphy grids and then have to dig out trigonometry…), but I think even for simple math, most people don’t write their SVGs in pure HTML and thus would like to use algebra.</p>

<p>At least for me, I find it much easier to understand SVG Code when giving meaning to numbers, so I always stick to JavaScript, and by giving my coordinates names, I like them immeasurable times more.</p>

<p>So, for the upcoming examples, we’ll look at the list of variables with the simple math and then JSX-style templates for interpolation, as that gives more legible syntax highlighting than string interpolations, and then each example will be available as a CodePen.</p>

<p>To keep this Guide framework-agnostic, I wanted to quickly go over drawing SVG elements with just good old vanilla JavaScript.</p>

<p>We’ll create a container element in HTML that we can put our SVG into and grab that element with JavaScript.</p>

<pre><code class="language-html">&lt;div data-svg-container&gt;&lt;/div&gt;
&lt;script src="template.js"&gt;&lt;/script&gt;
</code></pre>

<p>To make it simple, we’ll draw a rectangle <code>&lt;rect&gt;</code> that covers the entire <code>viewBox</code> and uses a fill.</p>

<p><strong>Note</strong>: <em>You can add all valid CSS values as fills, so a fixed color, or something like <code>currentColor</code> to access the site’s text color or a CSS variable would work here if you’re inlining your SVG and want it to interact with the page it’s placed in.</em></p>

<p>Let’s first start with our variable setup.</p>

<div class="break-out">
<pre><code class="language-javascript">// vars
const container = document.querySelector("[data-svg-container]");
const svgWidth = "30rem"; // use any value with units here
const documentWidth = 100;
const documentHeight = 100;
const rectWidth = documentWidth;
const rectHeight = documentHeight;
const rectFill = "currentColor"; // use any color value here
const title = "A simple square box";
</code></pre>
</div>

<h3 id="method-1-create-element-and-set-attributes">Method 1: Create Element and Set Attributes</h3>

<p>This method is easier to keep type-safe (if using TypeScript) &mdash; uses proper SVG elements and attributes, and so on &mdash; but it is less performant and may take a long time if you have many elements.</p>

<div class="break-out">
<pre><code class="language-javascript">const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
const titleElement = document.createElementNS("http://www.w3.org/2000/svg", "title");
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");

svg.setAttribute("width", svgWidth);
svg.setAttribute("viewBox", `0 0 ${documentWidth} ${documentHeight}`);
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svg.setAttribute("role", "img");

titleElement.textContent = title;

rect.setAttribute("width", rectWidth);
rect.setAttribute("height", rectHeight);
rect.setAttribute("fill", rectFill);

svg.appendChild(titleElement);
svg.appendChild(rect);

container.appendChild(svg);
</code></pre>
</div>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="LYKKVzg"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Rectangle (JS Method 1) [forked]](https://codepen.io/smashingmag/pen/LYKKVzg) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/LYKKVzg">SVG Rectangle (JS Method 1) [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<h3 id="method-2-create-an-svg-string">Method 2: Create An SVG String</h3>

<p>Alternatively, you can create an SVG string and set the <code>innerHTML</code> of the container to that string. This is more performant, but you lose type safety, and the elements aren’t properly created in the DOM.</p>

<pre><code class="language-javascript">container.innerHTML = `
&lt;svg 
  width="${svgWidth}" 
  viewBox="0 0 ${documentWidth} ${documentHeight}" 
  xmlns="http://www.w3.org/2000/svg" 
  role="img"
&gt;
  &lt;title&gt;${title}&lt;/title&gt;
  &lt;rect 
    width="${rectWidth}" 
    height="${rectHeight}" 
    fill="${rectFill}" 
  /&gt;
&lt;/svg&gt;`;
</code></pre>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="BaggNmN"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Rectangle (JS Method 2) [forked]](https://codepen.io/smashingmag/pen/BaggNmN) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/BaggNmN">SVG Rectangle (JS Method 2) [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<h3 id="method-3-best-of-both-worlds">Method 3: Best Of Both Worlds</h3>

<p>The best of both worlds is to just create the SVG itself as a DOM element and then set the content of the SVG via <code>innerHTML</code>.</p>

<p>We’re appending a proper SVG element to the container and can type-check that and have access to it properly. You aren’t typically going to be changing the content of the SVG that much, so I feel like this is probably the best way to do it.</p>

<div class="break-out">
<pre><code class="language-javascript">const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");

svg.setAttribute("width", svgWidth);
svg.setAttribute("viewBox", `0 0 ${documentWidth} ${documentHeight}`);
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svg.setAttribute("role", "img");

svg.innerHTML = `
  &lt;title&gt;${title}&lt;/title&gt;
  &lt;rect 
    width="${rectWidth}" 
    height="${rectHeight}" 
    fill="${rectFill}" 
  /&gt;
`;

container.appendChild(svg);
</code></pre>
</div>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="RwzzPjz"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Rectangle (JS Method 3) [forked]](https://codepen.io/smashingmag/pen/RwzzPjz) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/RwzzPjz">SVG Rectangle (JS Method 3) [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<h2 id="drawing-basic-elements">Drawing Basic Elements</h2>

<p>Okay, so now that we have the basics of the SVG setup, let’s look into how the most common elements are drawn.</p>

<h3 id="drawing-boxes">Drawing Boxes</h3>

<p><code>&lt;rect&gt;</code> creates a box, as we&rsquo;ve learned in the previous example. It has <code>y</code> and <code>x</code> attributes, which define the position of the top left corner. They are optional, and if not set, the box will be drawn at the origin <code>(0,0)</code> like in that previous example.</p>

<p>There are also <code>rx</code> and <code>ry</code> attributes. Those are radii. If you define <code>rx</code>, <code>ry</code> will automatically be set to the same value unless you redeclare it, then you’d use an elliptical corner-radius instead of a circular one.</p>

<p>Let’s draw four different rectangles in our next SVG, one in each quadrant:</p>

<ol>
<li>Top left: This is just a rectangle with a top and left offset and a width and height.</li>
<li>Top right: We will make use of a small corner radius to make it a rounded rectangle.</li>
<li>Bottom left: It uses such a large corner radius that it turns into a circle. It has a bit of a weird box origin, but it’s an option.</li>
<li>Bottom right: It uses an elliptical corner radius for this squoval shape.</li>
</ol>

<p>This is the implementation in JavaScript:</p>

<pre><code class="language-javascript">const rectDocWidth = 200;
const rectDocHeight = 200;
const rectFill = "currentColor";
const docOffset = 15;
const rectSize = rectDocWidth / 2 - docOffset &#42; 2;
const roundedCornerRadius = 10;
const circleLookRadius = rectSize / 2;
const ellipticalRy = roundedCornerRadius &#42; 2;
</code></pre>

<p>And to then set up the SVG, we’ll apply these variables to the template:</p>

<div class="break-out">
<pre><code class="language-jsx">&lt;svg
  width={svgWidth}
  viewBox={`0 0 ${rectDocWidth} ${rectDocHeight}`}
  xmlns="http://www.w3.org/2000/svg"
  role="img"
&gt;
  &lt;title&gt;Four Rectangles of different qualities placed in each quadrant&lt;/title&gt;
  &lt;rect
    x={docOffset}
    y={docOffset}
    width={rectSize}
    height={rectSize}
    fill={rectFill}
  /&gt;
  &lt;rect
    x={rectDocWidth - rectSize - docOffset}
    rx={roundedCornerRadius}
    y={docOffset}
    width={rectSize}
    height={rectSize}
    fill={rectFill}
  /&gt;
  &lt;rect
    x={docOffset}
    rx={circleLookRadius}
    y={rectDocHeight - rectSize - docOffset}
    width={rectSize}
    height={rectSize}
    fill={rectFill}
  /&gt;
  &lt;rect
    x={rectDocWidth - rectSize - docOffset}
    rx={roundedCornerRadius}
    ry={ellipticalRy}
    y={rectDocHeight - rectSize - docOffset}
    width={rectSize}
    height={rectSize}
    fill={rectFill}
  /&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>And this is the result:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="mdZZJpN"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Rect [forked]](https://codepen.io/smashingmag/pen/mdZZJpN) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/mdZZJpN">SVG Rect [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<div class="partners__lead-place"></div>

<h2 id="drawing-lines">Drawing Lines</h2>

<p>There is a <code>&lt;line&gt;</code> element in SVG that takes an <code>x1</code>, <code>y1</code>, <code>x2</code>, and <code>y2</code> attribute, which are the coordinates of the start and end points of the line.</p>

<p>For me, knowing how to draw straight horizontal or vertical lines was fairly important.</p>

<p>The rules for that are simple: <strong>We’ll just have to make sure that the <code>y</code> values are the same for a horizontal line and the <code>x</code> values are the same for a vertical line.</strong></p>

<p>Let’s look at an example where we draw a horizontal and a vertical line through the center of our document. I purposefully used some weirder numbers here; you’ll see that the resulting SVG is still perfectly centered, though, since it’s totally fine to use floating point numbers in SVG, and we don’t really run into subpixel rendering issues as we do in some CSS cases, where we end up with fractional pixels.</p>

<p>These are the JavaScript variables we set up:</p>

<pre><code class="language-javascript">const lineDocWidth = 421;
const lineDocHeight = 391;
const lineStroke = "currentColor";
const lineStrokeWidth = 5;
const horizontalLineStart = 0;
const horizontalLineEnd = lineDocWidth;
const horizontalLineY = lineDocHeight / 2;
const verticalLineStart = 0;
const verticalLineEnd = lineDocHeight;
const verticalLineX = lineDocWidth / 2;
</code></pre>

<p>And this is how we can integrate these variables into the SVG element:</p>

<div class="break-out">
<pre><code class="language-jsx">&lt;svg
  width={svgWidth}
  viewBox={`0 0 ${lineDocWidth} ${lineDocHeight}`}
  xmlns="http://www.w3.org/2000/svg"
  role="img"
&gt;
  &lt;title&gt;Horizontal and Vertical Line through the middle of the document&lt;/title&gt;
  &lt;line
    x1={horizontalLineStart}
    x2={horizontalLineEnd}
    y1={horizontalLineY}
    y2={horizontalLineY}
    stroke={lineStroke}
    stroke-width={lineStrokeWidth}
  /&gt;
  &lt;line
    x1={verticalLineX}
    x2={verticalLineX}
    y1={verticalLineStart}
    y2={verticalLineEnd}
    stroke={lineStroke}
    stroke-width={lineStrokeWidth}
  /&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>And here’s our result:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="abggOqK"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Line [forked]](https://codepen.io/smashingmag/pen/abggOqK) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/abggOqK">SVG Line [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<h3 id="drawing-circles">Drawing Circles</h3>

<p><code>&lt;circle&gt;</code> elements have <code>cx</code>, <code>cy</code>, and <code>r</code> as coordinates. The <code>x</code> and <code>y</code> values are relative to the circle center, and <code>r</code> describes the radius of the circle.</p>

<p>This is where things are less intuitive in my head because there will be times when I want the edge of the circle to be placed at a certain point and not the center, and I’ll usually also think in terms of diameters, not radii.</p>

<p>So, let’s say we want to draw a circle whose outer edge is offset from the bottom left corner by a certain amount and whose diameter is a certain size. We’d have to do some math again to calculate our coordinates.</p>

<p>These are the variables in JavaScript that we’re working with:</p>

<div class="break-out">
<pre><code class="language-javascript">const circleDocWidth = 100;
const circleDocHeight = 100;
const circleOffset = 10;
const circleDiameter = 20;
const circleRadius = circleDiameter / 2;
const circleX = circleOffset + circleRadius;
const circleY = circleDocHeight - circleOffset - circleRadius;
</code></pre>
</div>

<p>And, just like before, this is how we might integrate them into the SVG element:</p>

<pre><code class="language-jsx">&lt;svg
  width={svgWidth}
  viewBox={`0 0 ${circleDocWidth} ${circleDocHeight}`}
  xmlns="http://www.w3.org/2000/svg"
  role="img"
&gt;
  &lt;circle
    cx={circleX}
    cy={circleY}
    r={circleRadius}
    fill="red"
  /&gt;
&lt;/svg&gt;
</code></pre>

<p>And this is what it looks like:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="KKjjpoW"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Circle [forked]](https://codepen.io/smashingmag/pen/KKjjpoW) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/KKjjpoW">SVG Circle [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<h3 id="drawing-ellipses">Drawing Ellipses</h3>

<p><code>&lt;ellipse&gt;</code> elements have <code>cx</code>, <code>cy</code>, <code>rx</code>, and <code>ry</code> as coordinates. The <code>x</code> and <code>y</code> values are relative to the ellipse center, and <code>rx</code> and <code>ry</code> describe the radius of the ellipse.</p>

<p>Let’s draw an ellipse that is offset from the top right corner by a certain amount, whose horizontal radius is a certain size, and whose vertical radius is half of that.</p>

<p>For that we need to define our variables in JavaScript:</p>

<div class="break-out">
<pre><code class="language-javascript">const ellipseSVGWidth = 100;
const ellipseDocWidth = 100;
const ellipseDocHeight = 100;
const ellipseOffset = 10;
const ellipseHorizontalRadius = ellipseDocWidth / 2 - ellipseOffset;
const ellipseVerticalRadius = ellipseHorizontalRadius / 2;
const ellipseX = ellipseDocWidth - ellipseOffset - ellipseHorizontalRadius;
const ellipseY = ellipseOffset + ellipseVerticalRadius;
</code></pre>
</div>

<p>…and integrate them into the SVG element:</p>

<div class="break-out">
<pre><code class="language-jsx">&lt;svg
  width={svgWidth}
  viewBox={`0 0 ${ellipseDocWidth} ${ellipseDocHeight}`}
  xmlns="http://www.w3.org/2000/svg"
  role="img"
&gt;
  &lt;title&gt;Ellipse offset from the top right corner&lt;/title&gt;
  &lt;ellipse
    cx={ellipseX}
    cy={ellipseY}
    rx={ellipseHorizontalRadius}
    ry={ellipseVerticalRadius}
    fill="hotpink"
  /&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>Here’s the result:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="NWZZqMR"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Ellipse [forked]](https://codepen.io/smashingmag/pen/NWZZqMR) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/NWZZqMR">SVG Ellipse [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<h3 id="connecting-dots-with-polyline-and-polygon">Connecting Dots With <code>&lt;polyline&gt;</code> And <code>&lt;polygon&gt;</code></h3>

<p>Let’s say we want to have a line that has multiple points but doesn’t make a rectangle or a circle.</p>

<p>This is where we can use <code>polyline</code> and <code>polygon</code>, which share the same attributes and only differ in the way that a <code>polygon</code> will connect the first and last point, while a <code>polyline</code> won’t.</p>

<p>They take a <code>points</code> attribute, which is a list of <code>x</code> and <code>y</code> values separated by a space, and, by default, both of them have a <code>fill</code>, which can be a bit strange. That’s especially true for a <code>polyline</code>, so you might want to set that value to <code>none</code>.</p>

<p>Let’s say we have three circles, and we want to have lines connecting their centers. We can just take the <code>cx</code> and <code>cy</code> values of those circles and chain them together in the <code>points</code> attribute.</p>

<p>SVG is drawn from background to foreground, so the circles are drawn first, then the lines so they are stacked on top of each other.</p>

<p>To notice the differences between the polyline and the polygon, we’ll draw our composite four times, like we did before with the circles.</p>

<p>This time, we have more than one element, though. To make it quicker to scan which set belongs together, we can make use of the <code>g</code> element, which groups multiple elements together. It allows us to apply certain attributes to all children at the same time.</p>

<p>To see that in action and to save us a bit of time, in having to adjust <code>x</code> and <code>y</code> values for each separate element within the composite, we can apply a <code>transform</code> to that group element to push our composite into the different quadrants.</p>

<p><code>transform=&quot;translate(x,y)</code>&rdquo; is how we do that. The transform attribute works a lot like CSS transforms, with slight differences in syntax. But in most simple cases, we can assume the same thing to happen. The translate attribute will take the original position and then move the elements contained within the group along the <code>x</code> and <code>y</code> axis.</p>

<p>So, let’s have a look at our SVG:</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/svg-coding-examples-recipes-writing-vectors-by-hand/polygon-polyline-composite-fixed.svg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="800"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/svg-coding-examples-recipes-writing-vectors-by-hand/polygon-polyline-composite-fixed.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/svg-coding-examples-recipes-writing-vectors-by-hand/polygon-polyline-composite-fixed.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/svg-coding-examples-recipes-writing-vectors-by-hand/polygon-polyline-composite-fixed.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/svg-coding-examples-recipes-writing-vectors-by-hand/polygon-polyline-composite-fixed.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/svg-coding-examples-recipes-writing-vectors-by-hand/polygon-polyline-composite-fixed.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/svg-coding-examples-recipes-writing-vectors-by-hand/polygon-polyline-composite-fixed.png"
			
			sizes="100vw"
			alt="From left to right, bottom to top: Polyline with no fill applied, polyline with fill, polygon with no fill, polygon with fill"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      From left to right, bottom to top: Polyline with no fill applied, polyline with fill, polygon with no fill, polygon with fill. (<a href='https://files.smashing.media/articles/svg-coding-examples-recipes-writing-vectors-by-hand/polygon-polyline-composite-fixed.svg'>SVG preview</a>)
    </figcaption>
  
</figure>

<p>Here, you can see that with the same coordinates, a polyline won’t draw the line between the blue and the red dot, while a polygon will. However, when applying a fill, they take the exact same information as if the shape was closed, which is the right side of the graphic, where the polyline makes it look like a piece of a circle is missing.</p>

<p>This is the second time where we have dealt with quite a bit of repetition, and we can have a look at how we could leverage the power of JavaScript logic to render our template faster.</p>

<p>But first, we need a basic implementation like we’ve done before. We’re creating objects for the circles, and then we’re chaining the <code>cx</code> and <code>cy</code> values together to create the <code>points</code> attribute. We’re also storing our transforms in variables.</p>

<div class="break-out">
<pre><code class="language-javascript">const polyDocWidth = 200;
const polyDocHeight = 200;
const circleOne = { cx: 25, cy: 80, r: 10, fill: "red" };
const circleTwo = { cx: 40, cy: 20, r: 5, fill: "lime" };
const circleThree = { cx: 70, cy: 60, r: 8, fill: "cyan" };
const points = `${circleOne.cx},${circleOne.cy} ${circleTwo.cx},${circleTwo.cy} ${circleThree.cx},${circleThree.cy}`;
const moveToTopRight = `translate(${polyDocWidth / 2}, 0)`;
const moveToBottomRight = `translate(${polyDocWidth / 2}, ${polyDocHeight / 2})`;
const moveToBottomLeft = `translate(0, ${polyDocHeight / 2})`;
</code></pre>
</div>

<p>And then, we apply the variables to the template, using either a <code>polyline</code> or <code>polygon</code> element and a <code>fill</code> attribute that is either set to <code>none</code> or a color value.</p>

<pre><code class="language-jsx">
&lt;svg
  width={svgWidth}
  viewBox={`0 0 ${polyDocWidth} ${polyDocHeight}`}
  xmlns="http://www.w3.org/2000/svg"
  role="img"
&gt;
  &lt;title&gt;Composite shape comparison&lt;/title&gt;
  &lt;g&gt;
    &lt;circle
      cx={circleOne.cx}
      cy={circleOne.cy}
      r={circleOne.r}
      fill={circleOne.fill}
    /&gt;
    &lt;circle
      cx={circleTwo.cx}
      cy={circleTwo.cy}
      r={circleTwo.r}
      fill={circleTwo.fill}
    /&gt;
    &lt;circle
      cx={circleThree.cx}
      cy={circleThree.cy}
      r={circleThree.r}
      fill={circleThree.fill}
    /&gt;
    &lt;polyline
      points={points}
      fill="none"
      stroke="black"
    /&gt;
  &lt;/g&gt;
  &lt;g transform={moveToTopRight}&gt;
    &lt;circle
      cx={circleOne.cx}
      cy={circleOne.cy}
      r={circleOne.r}
      fill={circleOne.fill}
    /&gt;
    &lt;circle
      cx={circleTwo.cx}
      cy={circleTwo.cy}
      r={circleTwo.r}
      fill={circleTwo.fill}
    /&gt;
    &lt;circle
      cx={circleThree.cx}
      cy={circleThree.cy}
      r={circleThree.r}
      fill={circleThree.fill}
    /&gt;
    &lt;polyline
      points={points}
      fill="white"
      stroke="black"
    /&gt;
  &lt;/g&gt;
  &lt;g transform={moveToBottomLeft}&gt;
    &lt;circle
      cx={circleOne.cx}
      cy={circleOne.cy}
      r={circleOne.r}
      fill={circleOne.fill}
    /&gt;
    &lt;circle
      cx={circleTwo.cx}
      cy={circleTwo.cy}
      r={circleTwo.r}
      fill={circleTwo.fill}
    /&gt;
    &lt;circle
      cx={circleThree.cx}
      cy={circleThree.cy}
      r={circleThree.r}
      fill={circleThree.fill}
    /&gt;
    &lt;polygon
      points={points}
      fill="none"
      stroke="black"
    /&gt;
  &lt;/g&gt;
  &lt;g transform={moveToBottomRight}&gt;
    &lt;circle
      cx={circleOne.cx}
      cy={circleOne.cy}
      r={circleOne.r}
      fill={circleOne.fill}
    /&gt;
    &lt;circle
      cx={circleTwo.cx}
      cy={circleTwo.cy}
      r={circleTwo.r}
      fill={circleTwo.fill}
    /&gt;
    &lt;circle
      cx={circleThree.cx}
      cy={circleThree.cy}
      r={circleThree.r}
      fill={circleThree.fill}
    /&gt;
    &lt;polygon
      points={points}
      fill="white"
      stroke="black"
    /&gt;
  &lt;/g&gt;
&lt;/svg&gt;
</code></pre>

<p>And here’s a version of it to play with:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="OJeeVoM"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Polygon / Polyline (simple) [forked]](https://codepen.io/smashingmag/pen/OJeeVoM) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/OJeeVoM">SVG Polygon / Polyline (simple) [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<div class="partners__lead-place"></div>

<h2 id="dealing-with-repetition">Dealing With Repetition</h2>

<p>When it comes to drawing SVGs, you may find that you’ll be repeating a lot of the same code over and over again. This is where JavaScript can come in handy, so let’s look at the composite example again and see how we could optimize it so that there is less repetition.</p>

<p><strong>Observations:</strong></p>

<ul>
<li>We have three circle elements, all following the same pattern.</li>
<li>We create one repetition to change the <code>fill</code> style for the element.</li>
<li>We repeat those two elements one more time, with either a <code>polyline</code> or a <code>polygon</code>.</li>
<li>We have four different <code>transforms</code> (technically, no transform is a transform in this case).</li>
</ul>

<p>This tells us that we can create nested loops.</p>

<p>Let’s go back to just a vanilla implementation for this since the way loops are done is quite different across frameworks.</p>

<p>You could make this more generic and write separate generator functions for each type of element, but this is just to give you an idea of what you could do in terms of logic. There are certainly still ways to optimize this.</p>

<p>I’ve opted to have arrays for each type of variation that we have and wrote a helper function that goes through the data and builds out an array of objects with all the necessary information for each group. In such a short array, it would certainly be a viable option to just have the data stored in one element, where the values are repeated, but we’re taking the DRY thing seriously in this one.</p>

<p>The group array can then be looped over to build our SVG HTML.</p>

<div class="break-out">
<pre><code class="language-javascript">const container = document.querySelector("[data-svg-container]");
const svgWidth = 200;
const documentWidth = 200;
const documentHeight = 200;
const halfWidth = documentWidth / 2;
const halfHeight = documentHeight / 2;
const circles = [
  { cx: 25, cy: 80, r: 10, fill: "red" },
  { cx: 40, cy: 20, r: 5, fill: "lime" },
  { cx: 70, cy: 60, r: 8, fill: "cyan" },
];
const points = circles.map(({ cx, cy }) =&gt; `${cx},${cy}`).join(" ");
const elements = ["polyline", "polygon"];
const fillOptions = ["none", "white"];
const transforms = [
  undefined,
  `translate(${halfWidth}, 0)`,
  `translate(0, ${halfHeight})`,
  `translate(${halfWidth}, ${halfHeight})`,
];
const makeGroupsDataObject = () =&gt; {
  let counter = 0;
  const g = [];
  elements.forEach((element) =&gt; {
    fillOptions.forEach((fill) =&gt; {
      const transform = transforms[counter++];
      g.push({ element, fill, transform });
    });
  });
  return g;
};
const groups = makeGroupsDataObject();
// result:
// [
//   {
//     element: "polyline",
//     fill: "none",
//   },
//   {
//     element: "polyline",
//     fill: "white",
//     transform: "translate(100, 0)",
//   },
//   {
//     element: "polygon",
//     fill: "none",
//     transform: "translate(0, 100)",
//   },
//   {
//     element: "polygon",
//     fill: "white",
//     transform: "translate(100, 100)",
//   }
// ]

const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", svgWidth);
svg.setAttribute("viewBox", `0 0 ${documentWidth} ${documentHeight}`);
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svg.setAttribute("role", "img");
svg.innerHTML = "&lt;title&gt;Composite shape comparison&lt;/title&gt;";
groups.forEach((groupData) =&gt; {
  const circlesHTML = circles
    .map((circle) =&gt; {
      return `
        &lt;circle 
          cx="${circle.cx}" 
          cy="${circle.cy}" 
          r="${circle.r}" 
          fill="${circle.fill}"
        /&gt;`;
    })
    .join("");
  const polyElementHTML = `
    &lt;${groupData.element} 
      points="${points}" 
      fill="${groupData.fill}" 
      stroke="black" 
    /&gt;`;
  const group = `
      &lt;g ${groupData.transform ? `transform="${groupData.transform}"` : ""}&gt;
        ${circlesHTML}
        ${polyElementHTML}
      &lt;/g&gt;
    `;
  svg.innerHTML += group;
});
container.appendChild(svg);
</code></pre>
</div>

<p>And here’s the Codepen of that:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="XWLLbPq"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Polygon / Polyline (JS loop version) [forked]](https://codepen.io/smashingmag/pen/XWLLbPq) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/XWLLbPq">SVG Polygon / Polyline (JS loop version) [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<h2 id="more-fun-stuff">More Fun Stuff</h2>

<p>Now, that’s all the basics I wanted to cover, but there is so much more you can do with SVG. There is more you can do with <code>transform</code>; you can use a <code>mask</code>, you can use a <code>marker</code>, and so on.</p>

<p>We don’t have time to dive into all of them today, but since this started for me when making Calligraphy Grids, I wanted to show you the two most satisfying ones, which I, unfortunately, can’t use in the generator since I wanted to be able to open my generated SVGs in Affinity and it doesn’t support <code>pattern</code>.</p>

<p>Okay, so <code>pattern</code> is part of the <code>defs</code> section within the SVG, which is where you can define reusable elements that you can then reference in your SVG.</p>

<h3 id="graph-grid-with-pattern">Graph Grid with <code>pattern</code></h3>

<p>If you think about it, a graph is just a bunch of horizontal and vertical lines that repeat across the x- and y-axis.</p>

<p>So, <code>pattern</code> can help us with that. We can create a <code>&lt;rect&gt;</code> and then reference a <code>pattern</code> in the <code>fill</code> attribute of the <code>rect</code>. The pattern then has its own <code>width</code>, <code>height</code>, and <code>viewBox</code>, which defines how the pattern is repeated.</p>

<p>So, let’s say we want to perfectly center our graph grid in any given width or height, and we want to be able to define the size of our resulting squares (cells).</p>

<p>Once again, let’s start with the JavaScipt variables:</p>

<pre><code class="language-javascript">const graphDocWidth = 226;
const graphDocHeight = 101;
const cellSize = 5;
const strokeWidth = 0.3;
const strokeColor = "currentColor";
const patternHeight = (cellSize / graphDocHeight) &#42; 100;
const patternWidth = (cellSize / graphDocWidth) &#42; 100;
const gridYStart = (graphDocHeight % cellSize) / 2;
const gridXStart = (graphDocWidth % cellSize) / 2;
</code></pre>

<p>Now, we can apply them to the SVG element:</p>

<pre><code class="language-jsx">&lt;svg
  width={svgWidth}
  viewBox={`0 0 ${graphDocWidth} ${graphDocHeight}`}
  xmlns="http://www.w3.org/2000/svg"
  role="img"
&gt;
  &lt;defs&gt;
    &lt;pattern
      id="horizontal"
      viewBox={`0 0 ${graphDocWidth} ${strokeWidth}`}
      width="100%"
      height={`${patternHeight}%`}
    &gt;
      &lt;line
        x1="0"
        x2={graphDocWidth}
        y1={gridYStart}
        y2={gridYStart}
        stroke={strokeColor}
        stroke-width={strokeWidth}
      /&gt;
    &lt;/pattern&gt;
    &lt;pattern
      id="vertical"
      viewBox={`0 0 ${strokeWidth} ${graphDocHeight}`}
      width={`${patternWidth}%`}
      height="100%"
    &gt;
      &lt;line
        y1={0}
        y2={graphDocHeight}
        x1={gridXStart}
        x2={gridXStart}
        stroke={strokeColor}
        stroke-width={strokeWidth}
      /&gt;
    &lt;/pattern&gt;
  &lt;/defs&gt;
  &lt;title&gt;A graph grid&lt;/title&gt;
  &lt;rect
    width={graphDocWidth}
    height={graphDocHeight}
    fill="url(&#35;horizontal)"
  /&gt;
  &lt;rect
    width={graphDocWidth}
    height={graphDocHeight}
    fill="url(&#35;vertical)"
  /&gt;
&lt;/svg&gt;
</code></pre>

<p>And this is what that then looks like:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="XWLLbxq"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Graph Grid [forked]](https://codepen.io/smashingmag/pen/XWLLbxq) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/XWLLbxq">SVG Graph Grid [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<h3 id="dot-grid-with-pattern">Dot Grid With <code>pattern</code></h3>

<p>If we wanted to draw a dot grid instead, we could simply repeat a circle. Or, we could alternatively use a line with a <code>stroke-dasharray</code> and <code>stroke-dashoffset</code> to create a dashed line. And we’d only need one line in this case.</p>

<p>Starting with our JavaScript variables:</p>

<pre><code class="language-javascript">const dotDocWidth = 219;
const dotDocHeight = 100;
const cellSize = 4;
const strokeColor = "black";
const gridYStart = (dotDocHeight % cellSize) / 2;
const gridXStart = (dotDocWidth % cellSize) / 2;
const dotSize = 0.5;
const patternHeight = (cellSize / dotDocHeight) &#42; 100;
</code></pre>

<p>And then adding them to the SVG element:</p>

<pre><code class="language-jsx">&lt;svg
  width={svgWidth}
  viewBox={`0 0 ${dotDocWidth} ${dotDocHeight}`}
  xmlns="http://www.w3.org/2000/svg"
  role="img"
&gt;
  &lt;defs&gt;
    &lt;pattern
      id="horizontal-dotted-line"
      viewBox={`0 0 ${dotDocWidth} ${dotSize}`}
      width="100%"
      height={`${patternHeight}%`}
    &gt;
      &lt;line
        x1={gridXStart}
        y1={gridYStart}
        x2={dotDocWidth}
        y2={gridYStart}
        stroke={strokeColor}
        stroke-width={dotSize}
        stroke-dasharray={`0,${cellSize}`}
        stroke-linecap="round"
      &gt;&lt;/line&gt;
    &lt;/pattern&gt;
  &lt;/defs&gt;
  &lt;title&gt;A Dot Grid&lt;/title&gt;
  &lt;rect
    x="0"
    y="0"
    width={dotDocWidth}
    height={dotDocHeight}
    fill="url(&#35;horizontal-dotted-line)"
  &gt;&lt;/rect&gt;
&lt;/svg&gt;
</code></pre>

<p>And this is what that looks like:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="eYwwNQM"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Dot Grid [forked]](https://codepen.io/smashingmag/pen/eYwwNQM) by <a href="https://codepen.io/mynimi">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/eYwwNQM">SVG Dot Grid [forked]</a> by <a href="https://codepen.io/mynimi">Myriam</a>.</figcaption>
</figure>

<h2 id="conclusion">Conclusion</h2>

<p>This brings us to the end of our little introductory journey into SVG. As you can see, coding SVG by hand is not as scary as it seems. If you break it down into the basic elements, it becomes quite like any other coding task:</p>

<ul>
<li>We analyze the problem,</li>
<li>Break it down into smaller parts,</li>
<li>Examine each coordinate and its mathematical breakdown,</li>
<li>And then put it all together.</li>
</ul>

<p>I hope that this article has given you a starting point into the wonderful world of coded images and that it gives you the motivation to delve deeper into the specs and try drawing some yourself.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Mihai Cora</author><title>Creating Custom Lottie Animations With SVGator</title><link>https://www.smashingmagazine.com/2024/09/creating-custom-lottie-animations-svgator/</link><pubDate>Tue, 17 Sep 2024 11:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2024/09/creating-custom-lottie-animations-svgator/</guid><description>Creating ready-to-implement Lottie animations with a single tool is now possible thanks to SVGator’s latest feature updates. In this article, you will learn how to create and animate a Lottie using SVGator, an online animation tool that has zero learning curve if you’re familiar with at least one design tool. Have a closer look at the tool’s main functionalities and the straightforward creation process.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2024/09/creating-custom-lottie-animations-svgator/" />
              <title>Creating Custom Lottie Animations With SVGator</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Creating Custom Lottie Animations With SVGator</h1>
                  
                    
                    <address>Mihai Cora</address>
                  
                  <time datetime="2024-09-17T11:00:00&#43;00:00" class="op-published">2024-09-17T11:00:00+00:00</time>
                  <time datetime="2024-09-17T11:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                <p>This article is sponsored by <b>SVGator</b></p>
                

<p>SVGator has gone through a series of updates since our last article, which was published in 2021, when it was already considered to be the most advanced web-based tool for vector animation. The first step toward more versatile software came with the mobile export feature that made it possible to implement the animations in iOS and Android applications.</p>

<p>The animation tool continued its upgrade with a series of new export options: video formats including MP4, AVI, MKV, MOV, and WebM, as well as image formats such as GIF, Animated PNG, WebP, and image sequence. By covering a larger area of users’ needs, the app now enables anyone to create animated stickers, social media, and newsletter animations, video assets, and many more types of visual content on demand.</p>

<p>The goal of becoming a “one tool for all” still lacked the last piece of the puzzle, namely full support for Lottie files. Lottie, just like SVG, is a vector-based format, but it has even <strong>better comprehensive multi-platform support</strong>, a fact that makes it super popular among developers and design professionals. It is built for use across various platforms, enabling <strong>smooth integration into both web and mobile applications</strong>. Its file size is minimal, it is <strong>infinitely scalable</strong>, and developers find it straightforward to implement once they get familiar with the format. Lottie can incorporate raster graphics and also supports interactivity.</p>

<p>SVGator’s latest version has everything you need for your various applications without the need for any third-party apps or plug-ins.</p>

<p><strong>Note</strong>: <em>You can test all of SVGator’s functionalities free of charge before committing to the Pro plan. However, you can export up to three watermarked files, with videos and GIFs limited to basic quality.</em></p>

<p>In this article, we will follow a creation process made of these steps:</p>

<ul>
<li>Importing an existent Lottie JSON and making some minor adjustments;</li>
<li>Importing new animated assets created with SVGator (using the library);</li>
<li>Creating and animating new elements from scratch;</li>
<li>Exporting the Lottie animation.</li>
</ul>

<h2 id="getting-started-with-svgator">Getting Started With SVGator</h2>

<p>The sign-up process is simple, fast, and straightforward, and no credit card is required. Sign up either with Google or Facebook or, alternatively, by providing your name, email address, and password. Start a project either with a Lottie animation or a static SVG. If you don’t have an existing file, you can design and animate everything starting from a blank canvas.</p>

<p>Now that you’ve created your account, let’s dive right into the fun part. Here’s a preview of how your animation is going to look by the time you’re done following this guide. Neat, right?</p>

<figure><a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/1-final-animation.gif"><img src="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/1-final-animation.gif" width="800" height="450" alt="Final animation" /></a><figcaption>(<a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/1-final-animation.gif">Large preview</a>)</figcaption></figure>

<h2 id="create-a-new-project">Create A New Project</h2>

<p>After logging in and clicking on the <strong>New Project</strong> option, you will be taken to the <strong>New Project Panel</strong>, where you can choose between starting from a blank project or uploading a file. Let’s start this project with an existing Lottie JSON.</p>

<ul>
<li><a href="https://cdn.svgator.com/assets/pub/fast-response.json">Download the Lottie demo</a></li>
</ul>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/2-upload-file-lottie.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/2-upload-file-lottie.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/2-upload-file-lottie.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/2-upload-file-lottie.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/2-upload-file-lottie.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/2-upload-file-lottie.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/2-upload-file-lottie.png"
			
			sizes="100vw"
			alt="A screenshot with the selected Upload button"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/2-upload-file-lottie.png'>Large preview</a>)
    </figcaption>
  
</figure>

<ol>
  <li>Click on the <strong>Upload file</strong> button and navigate to the directory where you have saved your Lottie file.<br />
<br />













<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/3-lottie-open-file.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/3-lottie-open-file.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/3-lottie-open-file.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/3-lottie-open-file.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/3-lottie-open-file.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/3-lottie-open-file.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/3-lottie-open-file.png"
			
			sizes="100vw"
			alt="A screenshot with the selected Fast response.json file and Open button"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/3-lottie-open-file.png'>Large preview</a>)
    </figcaption>
  
</figure>
  </li>
  <li>Select the “<strong>Fast response.json</strong>” file and click <strong>Open</strong>.<br /> 
<br />
Hit play in the editor, and the animation should look like this:<br />
<br />

<figure><a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/4-animation-svgator.gif"><img src="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/4-animation-svgator.gif" width="800" height="449" alt="An animation opened in SVGator" /></a><figcaption>(<a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/4-animation-svgator.gif"><em>Large preview</em></a>)</figcaption></figure>
  </li>
</ol>

<p><strong>Note</strong>: <em>Make sure to hit</em> <strong><em>Save</em></strong> <em>after each step to make sure you don’t lose any of your progress while working on this project alongside our guide.</em></p>

<h2 id="import-an-animated-asset">Import An Animated Asset</h2>

<p>In this step, you will learn how to use the <strong>Library</strong> to import new assets to your project. You can easily choose from a variety of ready-made SVGs stored in different categories, load new files from your computer (Lottie, static SVG, and images), or save animations from other SVGator projects and reuse them.</p>

<p>In this case, let’s use an animated message bubble previously created and saved to the <strong>Uploads</strong> section of the <strong>Library</strong>.</p>

<p>Learn how to <strong>create and save animated assets</strong> with this <a href="https://www.youtube.com/watch?v=R2Px90nfOEI">short video tutorial</a>.</p>

<ul>
<li><a href="https://cdn.svgator.com/assets/pub/message-bubble.json">Download the message bubble Lottie animation</a></li>
</ul>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/5-message-bubble-lottie-animation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/5-message-bubble-lottie-animation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/5-message-bubble-lottie-animation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/5-message-bubble-lottie-animation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/5-message-bubble-lottie-animation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/5-message-bubble-lottie-animation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/5-message-bubble-lottie-animation.png"
			
			sizes="100vw"
			alt="A screenshot with the message bubble Lottie animation."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/5-message-bubble-lottie-animation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<ol>
  <li>Navigate to the left sidebar of the app and switch to the Library tab, then click the “+” icon to upload the message bubble asset that you downloaded earlier.</li>
  <li>After it is loaded in the uploads section, simply click on it to add it to your project.<br /><br />All the animated properties of the asset are now present in the timeline, and you can edit them if you want.<br /><br />
  <strong>Note</strong>: <em>Make sure the playhead is at the second “0” before adding the animated asset. When adding an animated asset, it will always start animating from the point where the playhead is placed.</em></li>
  <li>Freely adjust its position and size as you wish.</li>
  <li>With the playhead at the second 0, click on the <strong>Animate</strong> button, then choose <strong>Position</strong>.</li>
</ol>

<p>At this point, you should have the first Position keyframe automatically added at the second 0, and you are ready to start animating.</p>

<h2 id="animate-the-message-bubble">Animate The Message Bubble</h2>

<ol>
  <li>Start by dragging the playhead on the timeline at 0.2 seconds:<br />
<br />













<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/6-animate-message-bubble.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/6-animate-message-bubble.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/6-animate-message-bubble.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/6-animate-message-bubble.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/6-animate-message-bubble.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/6-animate-message-bubble.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/6-animate-message-bubble.png"
			
			sizes="100vw"
			alt="A screenshot showing steps how to animate the message bubble"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/6-animate-message-bubble.png'>Large preview</a>)
    </figcaption>
  
</figure>
  </li>
  <li>Then, drag the message bubble up a few pixels. The second keyframe will appear in the timeline, marking the element’s new position, thus creating the 2 milliseconds animation.<br /><br /><strong>Note</strong>: <em>You can hit Play at any moment to check how everything looks!</em><br /><br />Next, you can use the Scale animator to make the bubble disappear after the dots representing the typing are done animating by scaling it down to 0 for both the X and Y axes:<br /><br />














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/7-animate-message-bubble.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/7-animate-message-bubble.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/7-animate-message-bubble.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/7-animate-message-bubble.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/7-animate-message-bubble.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/7-animate-message-bubble.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/7-animate-message-bubble.png"
			
			sizes="100vw"
			alt="A screenshot showing steps how to animate the message bubble"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/7-animate-message-bubble.png'>Large preview</a>)
    </figcaption>
  
</figure>
  </li>
  <li>With the message bubble still selected, drag the playhead at 2.2 seconds, click on <strong>Animate</strong>, and select Scale (or just press <kbd>Shift</kbd> + <kbd>S</kbd> on the keyboard) to set the first Scale keyframe, then drag the playhead at 2.5 seconds.</li>
  <li>Set the scale properties to 0 for both the X and Y axes (in the right side panel). The bubble won’t be visible anymore at this point.<br /><br /><strong>Note</strong>: <em>To maintain the ratio while changing the scale values, make sure you have the Maintain proportions on (the link icon next to the scale inputs).</em><br /><br />To add an extra touch of interest to this scaling motion, add an easing function preset:<br /><br />














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/8-animate-message-bubble.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/8-animate-message-bubble.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/8-animate-message-bubble.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/8-animate-message-bubble.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/8-animate-message-bubble.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/8-animate-message-bubble.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/8-animate-message-bubble.png"
			
			sizes="100vw"
			alt="A screenshot showing steps how to animate the message bubble"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/8-animate-message-bubble.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
  <li>First, jump back to the first Scale keyframe (you can also double-click the keyframe to jump the playhead right at it).</li>
  <li>Open the <strong>Easing Panel</strong> next to the time indicator and scroll down through the presets list, then select <strong>Ease in Back</strong>. Due to its bezier going out of the graph, this easing function will create a bounce-back effect for the scale animation.<br /><br /><strong>Note</strong>: <em>You can adjust the bezier of a selected easing preset and create a new custom function, which will appear at the top of the list.</em><br /><br /><em>Keep in mind that you need at least one keyframe selected if you intend to apply an easing. The easing function will apply from the selected keyframe toward the next keyframe at its right. Of course, you can apply a certain easing for multiple keyframes at once.</em><br /><br />To get a smoother transition when the message bubble disappears, add an Opacity animation of one millisecond at the end of the scaling:<br /><br />














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/9-animate-message-bubble.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/9-animate-message-bubble.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/9-animate-message-bubble.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/9-animate-message-bubble.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/9-animate-message-bubble.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/9-animate-message-bubble.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/9-animate-message-bubble.png"
			
			sizes="100vw"
			alt="A screenshot showing steps how to animate the message bubble"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/9-animate-message-bubble.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
  <li>Choose <strong>Opacity</strong> from the animators’ list and set the first keyframe at 2.4 seconds, then drag the playhead at 2.5 seconds to match the ending keyframe from the scale animation above.</li>
  <li>From the <strong>Appearance panel</strong>, drag the <strong>Opacity slider</strong> all the way to the left, at 0%.</li>
</ol>

<h2 id="create-an-email-icon">Create An Email Icon</h2>

<p>For the concept behind this animation to be complete, let’s create (and later animate) a “new email” notification as a response to the character sending that message.</p>

<p>Once again, SVGator’s asset library comes in handy for this step:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/10-create-email-icon-animation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/10-create-email-icon-animation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/10-create-email-icon-animation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/10-create-email-icon-animation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/10-create-email-icon-animation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/10-create-email-icon-animation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/10-create-email-icon-animation.png"
			
			sizes="100vw"
			alt="A screenshot showing steps how to create an email icon."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/10-create-email-icon-animation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<ol>
  <li>Go to the search bar from the <strong>Library</strong> and type in “<strong>mail</strong>,” then click on the mail asset from the results.</li>
  <li>Place it somewhere above the laptop. Edit the mail icon to better fit the style of the animation:<br /><br />














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/11-edit-mail-icon-animation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/11-edit-mail-icon-animation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/11-edit-mail-icon-animation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/11-edit-mail-icon-animation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/11-edit-mail-icon-animation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/11-edit-mail-icon-animation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/11-edit-mail-icon-animation.png"
			
			sizes="100vw"
			alt="A screenshot showing steps how to edit the mail icon."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/11-edit-mail-icon-animation.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
  <li>Open the <strong>email</strong> group and select the rectangle from the back.</li>
  <li>Change its fill color to a dark purple.</li>
  <li>Round up the corners using the <strong>Radius</strong> slider.<br /><br />














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/12-edit-mail-icon-animation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/12-edit-mail-icon-animation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/12-edit-mail-icon-animation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/12-edit-mail-icon-animation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/12-edit-mail-icon-animation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/12-edit-mail-icon-animation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/12-edit-mail-icon-animation.png"
			
			sizes="100vw"
			alt="A screenshot showing steps how to edit the mail icon."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/12-edit-mail-icon-animation.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
  <li>Make the element’s design minimal by deleting these two lines from the lower part of the envelope.<br /><br />














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/13-edit-mail-icon-animation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/13-edit-mail-icon-animation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/13-edit-mail-icon-animation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/13-edit-mail-icon-animation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/13-edit-mail-icon-animation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/13-edit-mail-icon-animation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/13-edit-mail-icon-animation.png"
			
			sizes="100vw"
			alt="A screenshot steps showing how to edit the mail icon."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/13-edit-mail-icon-animation.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
    <li>Select the envelope seal flap, which is the <strong>Polyline</strong> element in the group, above the rectangle.</li>
    <li>Add a lighter purple for the fill, set the stroke to 2 px width, and also make it white.<br /><br />To make the animation even more interesting, create a notification alert in the top-right corner of the envelope:<br /><br />














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/14-edit-mail-icon-animation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/14-edit-mail-icon-animation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/14-edit-mail-icon-animation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/14-edit-mail-icon-animation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/14-edit-mail-icon-animation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/14-edit-mail-icon-animation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/14-edit-mail-icon-animation.png"
			
			sizes="100vw"
			alt="A screenshot showing steps how to edit the mail icon."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/14-edit-mail-icon-animation.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
  <li>Use the <strong>Ellipse tool (O)</strong> from the toolbar on top and draw a circle in the top-right corner of the envelope.</li>
  <li>Choose a nice red color for the fill, and set the stroke to white with a 2 px width.<br /><br />














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/15-edit-mail-icon-animation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/15-edit-mail-icon-animation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/15-edit-mail-icon-animation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/15-edit-mail-icon-animation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/15-edit-mail-icon-animation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/15-edit-mail-icon-animation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/15-edit-mail-icon-animation.png"
			
			sizes="100vw"
			alt="A screenshot showing steps how to edit the mail icon."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/15-edit-mail-icon-animation.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
  <li>Click on the “T” icon to select the <strong>Text</strong> tool.</li>
  <li>Click on the circle and type “1”.</li>
  <li>Set the color to white and click on the “B” icon to make it bold.<br /><br />














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/16-edit-mail-icon-animation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/16-edit-mail-icon-animation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/16-edit-mail-icon-animation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/16-edit-mail-icon-animation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/16-edit-mail-icon-animation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/16-edit-mail-icon-animation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/16-edit-mail-icon-animation.png"
			
			sizes="100vw"
			alt="A screenshot showing steps how to edit the mail icon."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/16-edit-mail-icon-animation.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
  <li>Select both the red circle and the number, and group them: right-click, and hit <strong>Group</strong>.<br /><br /> 
You can also hit <kbd>Command</kbd> or <kbd>Ctrl</kbd> + <kbd>G</kbd> on your keyboard. Double-click on the newly created group to rename it to “Notification.”<br /><br />














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/17-edit-mail-icon-animation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/17-edit-mail-icon-animation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/17-edit-mail-icon-animation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/17-edit-mail-icon-animation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/17-edit-mail-icon-animation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/17-edit-mail-icon-animation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/17-edit-mail-icon-animation.png"
			
			sizes="100vw"
			alt="A screenshot showing steps how to edit the mail icon."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/17-edit-mail-icon-animation.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
<li>Select both the notification group and email group below and create a new group, which you can name “new email.”</li>
</ol>

<h2 id="animate-the-new-email-group">Animate The New Email Group</h2>

<p>Let’s animate the new email popping out of the laptop right after the character has finished texting his message:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/18-animate-new-email-group.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/18-animate-new-email-group.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/18-animate-new-email-group.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/18-animate-new-email-group.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/18-animate-new-email-group.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/18-animate-new-email-group.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/18-animate-new-email-group.png"
			
			sizes="100vw"
			alt="A screenshot showing steps how to animate the new email group."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/18-animate-new-email-group.png'>Large preview</a>)
    </figcaption>
  
</figure>

<ol>
  <li>With the “New email” group selected, click twice on the <strong>Move down</strong> icon from the header to place the group last.<br />You can also press <kbd>Command</kbd> or <kbd>Ctrl</kbd> + arrow down on your keyboard.</li>
<li>Drag the group behind the laptop (on the canvas) to hide it entirely, and also scale it down a little.</li>
<li>With the playhead at 3 seconds, add the animators <strong>Scale</strong> and <strong>Position</strong>.<br />You can also do that by pressing <kbd>Shift</kbd> + <kbd>S</kbd> and <kbd>Shift</kbd> + <kbd>P</kbd> on your keyboard.<br /><br />














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/19-animate-new-email-group.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/19-animate-new-email-group.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/19-animate-new-email-group.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/19-animate-new-email-group.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/19-animate-new-email-group.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/19-animate-new-email-group.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/19-animate-new-email-group.png"
			
			sizes="100vw"
			alt="A screenshot showing steps how to animate the new email group."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/19-animate-new-email-group.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
<li>Drag the playhead at the second 3.3 on the timeline.</li>
<li>Move the New Email group above the laptop and scale it up a bit.</li>
<li>You can also bend the motion path line to create a curved trajectory for the position animation.<br /><br />














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/20-animate-new-email-group.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/20-animate-new-email-group.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/20-animate-new-email-group.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/20-animate-new-email-group.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/20-animate-new-email-group.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/20-animate-new-email-group.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/20-animate-new-email-group.png"
			
			sizes="100vw"
			alt="A screenshot showing steps how to animate the new email group."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/20-animate-new-email-group.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
<li>Select the first keyframes at the second 3.</li>
<li>Open the easing panel.</li>
<li>And click on the <strong>Ease Out Cubic</strong> preset to add it to both keyframes.</li>
</ol>

<h2 id="animate-the-notification">Animate The Notification</h2>

<p>Let’s animate the notification dot separately. We’ll make it pop in while the email group shows up.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/21-animate-notification.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/21-animate-notification.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/21-animate-notification.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/21-animate-notification.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/21-animate-notification.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/21-animate-notification.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/21-animate-notification.png"
			
			sizes="100vw"
			alt="A screenshot showing steps of how to animate the notification."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/21-animate-notification.png'>Large preview</a>)
    </figcaption>
  
</figure>

<ol>
<li>Select the <strong>Notification</strong> group.</li>
<li>Create a scale-up animation for it with 0 for both the X and Y axes at 3.2 and 1 at 3.5 seconds.</li>
<li>Select the first keyframe and, from the easing panel, choose <strong>Ease Out Back</strong>. This easing function will ensure the popping effect.</li>
</ol>

<h2 id="add-expressiveness-to-the-character">Add Expressiveness To The Character</h2>

<p>Make the character smile while looking at the email that just popped out. For this, you need to animate the stroke offset of the mouth:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/22-add-expressiveness-character.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/22-add-expressiveness-character.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/22-add-expressiveness-character.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/22-add-expressiveness-character.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/22-add-expressiveness-character.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/22-add-expressiveness-character.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/22-add-expressiveness-character.png"
			
			sizes="100vw"
			alt="A screenshot showing the steps of how to add expressiveness to the character."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/22-add-expressiveness-character.png'>Large preview</a>)
    </figcaption>
  
</figure>

<ol>
  <li>Select the mouth path. You can use the <strong>Node tool</strong> to select it directly with one click.</li>
  <li>Drag the playhead at 3.5 seconds, which is the moment from where the smile will start.</li>
  <li>Select the last keyframe of the <strong>Stroke offset</strong> animator from the timeline and duplicate it at second 3.5, or you can also use <kbd>Ctrl</kbd> or <kbd>Cmd</kbd> + <kbd>D</kbd> for duplication.<br /><br />














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/23-add-expressiveness-character.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/23-add-expressiveness-character.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/23-add-expressiveness-character.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/23-add-expressiveness-character.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/23-add-expressiveness-character.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/23-add-expressiveness-character.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/23-add-expressiveness-character.png"
			
			sizes="100vw"
			alt="A screenshot showing the steps of how to add expressiveness to the character."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/23-add-expressiveness-character.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
<li>Drag the playhead at second 3.9.</li>
<li>Go to the properties panel and set the Offset to 0. The stroke will now fill the path all the way, creating a stroke offset animation of 4 milliseconds.</li>
</ol>

<h2 id="final-edits">Final Edits</h2>

<p>You can still make all kinds of adjustments to your animation before exporting it. In this case, let’s change the color of the initial Lottie animation we used to start this project:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/24-final-edits-color.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/24-final-edits-color.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/24-final-edits-color.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/24-final-edits-color.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/24-final-edits-color.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/24-final-edits-color.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/24-final-edits-color.png"
			
			sizes="100vw"
			alt="A screenshot showing how to change the color of the initial Lottie animation"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/24-final-edits-color.png'>Large preview</a>)
    </figcaption>
  
</figure>

<ol>
<li>Use the <strong>Node tool</strong> to select all the green paths that form the character’s arms and torso.</li>
<li>Change the color as you desire.</li>
</ol>

<h2 id="export-lottie">Export Lottie</h2>

<p>Once you’re done editing, you can export the animation by clicking on the top right <strong>Export</strong> button and selecting the Lottie format. Alternatively, you can press <kbd>Command</kbd> or <kbd>Ctrl</kbd> + <kbd>E</kbd> on your keyboard to jump directly to the export panel, from where you can still select the animation you want to export.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/25-export-lottie.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/25-export-lottie.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/25-export-lottie.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/25-export-lottie.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/25-export-lottie.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/25-export-lottie.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/25-export-lottie.png"
			
			sizes="100vw"
			alt="A screenshot showing how to export the animation."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-custom-lottie-animations-svgator/25-export-lottie.png'>Large preview</a>)
    </figcaption>
  
</figure>

<ol>
<li>Make sure the Lottie format is selected from the dropdown. In the export panel, you can set a name for the file you are about to export, choose the frame rate and animation speed, or set a background color.</li>
<li>You can preview the Lottie animation with a Lottie player.<br />
<strong>Note</strong>: <em>This step is recommended to make sure all animations are supported in the Lottie format by previewing it on a webpage using the Lottie player. The preview in the export panel isn’t an actual Lottie animation.</em></li>
<li>Get back to the export panel and simply click <strong>Export</strong> to download the Lottie JSON.</li>
</ol>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>Now that you’re done with your animation don’t forget that you have plenty of export options available besides Lottie. You can post the same project on social media in video format, export it as an SVG animation for the web, or turn it into a GIF sticker or any other type of visual you can think of. GIF animations can also be used in Figma presentations and prototypes as a high-fidelity preview of the production-ready Lottie file.</p>

<p>We hope you enjoyed this article and that it will inspire you to create amazing Lottie animations in your next project.</p>

<p>Below, you can find a few useful resources to continue your journey with SVG and SVGator:</p>

<ul>
<li><a href="https://www.svgator.com/tutorials?utm_source=smashingmagazine.com&amp;utm_medium=referral&amp;utm_campaign=lottie-article">SVGator tutorials</a><br />
Check out a series of short video tutorials to help you get started with SVGator.</li>
<li><a href="https://www.svgator.com/help?utm_source=smashingmagazine.com&amp;utm_medium=referral&amp;utm_campaign=lottie-article">SVGator Help Center</a><br />
It answers the most common questions about SVGator, its features, and membership plans.</li>
</ul>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(yk, il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Costa Alexoglou</author><title>Better Context Menus With Safe Triangles</title><link>https://www.smashingmagazine.com/2023/08/better-context-menus-safe-triangles/</link><pubDate>Mon, 21 Aug 2023 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/08/better-context-menus-safe-triangles/</guid><description>Imagine the situation when you’ve hovered over a menu item that reveals another list of menu items, then tried to hover over that nested menu only to have the entire menu close on you. Is this a UX challenge you’ve struggled with? A well-known concept called the “safe triangle” solves this issue. While it’s been tackled many ways over the years, Costa Alexoglou has what he believes is a relatively straightforward approach using SVG and tracking a user’s mouse position to prevent nested menus from inadvertently closing on a user.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/08/better-context-menus-safe-triangles/" />
              <title>Better Context Menus With Safe Triangles</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Better Context Menus With Safe Triangles</h1>
                  
                    
                    <address>Costa Alexoglou</address>
                  
                  <time datetime="2023-08-21T08:00:00&#43;00:00" class="op-published">2023-08-21T08:00:00+00:00</time>
                  <time datetime="2023-08-21T08:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>You’ve no doubt wrestled with menus that have nested menus before. I can’t count how many times I’ve hovered over a menu item that reveals another list of menu items, then tried to hover over that nested menu only to have the entire menu close on me.</p>

<p>That’s the setup for what I think is a pretty common issue when making menus &mdash; preventing nested menus from closing inadvertently. It’s not the users’ fault; leaving hover between menu levels is easy. It’s also not exactly the web’s fault; the menu is supposed to close if the pointer leaves the interactive area.</p>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/855059002"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Keeping the nested menu open is more challenging than it looks.</figcaption>
	
</figure>

<p>Before we dig deeper into the issue, let’s acknowledge that relying on hover interactions for displaying and hiding menu items is already somewhat problematic. Not all devices have a mouse, or a pointer, for that matter. You could argue that click (or tap) interactions are better. Take Amazon’s main navigation as an example. It requires a click to open the main menu and another click to open the nested menus.</p>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/855060398"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Amazon employs a click (or tap) to open and close nested menus.</figcaption>
	
</figure>

<p>Looking past a “hover versus tap” debate, the appeal of hover interactions is obvious. It would be nice to save the user extra clicks, right?</p>

<p>That’s what we’re aiming for in this article. The reason I want to tackle this at all is that I saw Notion showing off its new menu hover interactions.</p>

<p><blockquote class="twitter-tweet"><p lang="en" dir="ltr">A li’l quality-of-life update:<br><br>Before, you had to be really precise with your cursor so menus wouldn’t disappear on you. Should feel much more polished now 🫡 <a href="https://t.co/0yTRz3CMce">pic.twitter.com/0yTRz3CMce</a></p>&mdash; Notion (@NotionHQ) <a href="https://twitter.com/NotionHQ/status/1629175696177389569?ref_src=twsrc%5Etfw">February 24, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p>

<p>While I don’t have inside information about Notion’s approach, I have what I think is a practical way to go about it based on other examples I’ve seen, and I want to show you how I got there.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="the-solution-safe-triangles">The Solution: Safe Triangles</h2>

<p>The solution is <strong>safe triangles</strong>. It’s not exactly a new idea, either. Amazon popularized the idea, and <a href="https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown?ref=height-blog.ghost.io">Ben Kamens blogged it back in 2013</a> while introducing a jQuery plugin to accomplish it.</p>

<p>The basic idea is nicely illustrated in this video that <a href="https://height.app/blog/guide-to-build-context-menus">Michael Villar includes in a post on the Height app blog</a>.</p>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/855062328"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>The safe triangle represents the interactive area that allows the nested menu to remain open.</figcaption>
	
</figure>

<p>There has to be a “modern” approach for adding safe triangles to nested menus. I sought out more examples.</p>

<h3 id="example-1-vs-code">Example 1: VS Code</h3>

<p>VS Code pulls off a nice hover interaction in its web app.</p>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/855063062"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>VS Code also supports a safe triangle.</figcaption>
	
</figure>

<p>The VS Code approach uses a delayed trigger of a <code>mouseover</code> event callback in JavaScript. But before the event fires, CSS styles are applied to the <code>:hover</code> state of menu items.</p>

<h3 id="macos">macOS</h3>

<p>I’m on a Mac and noticed that macOS also implements some sort of safe triangle in its menus.</p>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/855064686"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>A safe triangle in macOS menus cancels the safe area when returning hover from the nested menu to the top-level menu item.</figcaption>
	
</figure>

<p>I cannot crack macOS open and inspect its code, but this is an excellent example. Notice how the safe triangle works when moving from a top-level menu to a nested menu but not when returning from the nested menu to the top-level menu. It’s the sort of subtle, polished difference we might expect from Apple.</p>

<h3 id="radix-navigation-component">Radix Navigation Component</h3>

<p>The open-source Radix library provides a <a href="https://www.radix-ui.com/docs/primitives/components/dropdown-menu">Dropdown Menu component</a> that pulls it off.</p>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/855065079"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>The Radix dropdown component implementation of a safe triangle.</figcaption>
	
</figure>

<p>What a great job! I found this example from a talk <a href="https://www.youtube.com/watch?v=pcMYcjtWwVI">Vercel posted to YouTube</a> where Radix co-creator <a href="https://twitter.com/peduarte">Pedro Duarte</a> gives a master class on the design challenges of dropdown menus.</p>

<p>The functionality is pretty much the same as the macOS approach, where <code>:hover</code> is not triggered in the sibling elements, making the experience really smooth. Unlike macOS, though, this is code that I can access and inspect.</p>

<p>The Radix approach was hugely helpful as far as informing my own work. So, let’s break down the process to show you how I implemented this into the main navigation of the project I work on, <a href="https://neo4j.com">Neo4j</a>.</p>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/856321151"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>A safe triangle implementation in Neo4j.</figcaption>
	
</figure>

<h2 id="demo">Demo</h2>

<p>I put together a <a href="https://codesandbox.io/s/vmgyfg?file=%2FApp.js&amp;utm_medium=sandpack">simplified version</a> of my implementation that you can try. The code is written in React, but the solution is not React-specific and can be paired with any UI framework you like.</p>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/855065911"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Demonstrating a safe triangle.</figcaption>
	
</figure>

<p>See that? The second menu exposes the “safe triangle” on hover, showing the hoverable area that allows the nested menu to stay open, even after the pointer has left the hover state.</p>

<div class="partners__lead-place"></div>

<h2 id="how-it-works">How it Works</h2>

<p>The two key ingredients for this approach are SVG and the CSS <code>pointer-events</code> property.</p>

<h3 id="mouse-enter-and-leave">Mouse Enter and Leave</h3>

<p>First things first. When hovering over a menu item that includes nested elements, we trigger an <code>onMouseEnter</code> callback that opens the nested menu, with <code>onMouseEnter={() =&gt; setOpen(true)}</code>.</p>

<p>Then, an <code>onMouseLeave</code> callback is responsible for closing the submenu when the pointer leaves the element and its children via <code>onMouseLeave={() =&gt; setOpen(false)}</code>. This part is identical to a simple nested menu, but notice <code>&lt;SafeArea /&gt;</code> because this is where the magic happens.</p>

<pre><code class="language-javascript">const SafeAreaNestedOption = () =&gt; {
  const [open, setOpen] = useState&lt;boolean&gt;(false);
  const parent = useRef&lt;HTMLLIElement&gt;(null);
  const child = useRef&lt;HTMLDivElement&gt;(null);
  const getTop = useCallback(() =&gt; {
    const height = child.current?.offsetHeight;
    return height ? `-${height / 2 - 15}px` : 0;
  }, [child]);

  return (
    &lt;li
      ref={parent}
      style={{ position: "relative" }}
      onMouseEnter={() =&gt; setOpen(true)}
      onMouseLeave={() =&gt; setOpen(false)}
    &gt;
      &lt;NestedPlaceholder /&gt;
      {/&#42; Safe mouse area &#42;/}
      {/&#42; This is where the magic will happen &#42;/}
      {open && parent.current && child.current && (
        &lt;SafeArea anchor={parent.current} submenu={child.current} /&gt;
      )}
      {/&#42; Nested elements as children &#42;/}
      &lt;div
        style={{
          visibility: open ? "visible" : "hidden",
          position: "absolute",
          left: parent.current?.offsetWidth || 0,
          top: getTop()
        }}
        ref={child}
      &gt;
        &lt;ul&gt;
          &lt;li&gt;Nested Option 1&lt;/li&gt;
          &lt;li&gt;Nested Option 2&lt;/li&gt;
          &lt;li&gt;Nested Option 3&lt;/li&gt;
          &lt;li&gt;Nested Option 4&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/div&gt;
    &lt;/li&gt;
  );
};
</code></pre>

<h3 id="svg">SVG</h3>

<p>We use SVG to “draw” the safe triangle inside the <code>SafeArea</code> component. When a nested menu is open, we create the SVG as a child element that isn’t visible but is there. The idea is that users interact with it when it is exposed, even if they don’t realize it.</p>

<p>The trick is to make sure that the SVG is rectangular with a height equal to the height of the nested menu and a width equal to the distance between the cursor and the nested menu.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/better-context-menus-safe-triangles/2-safe-triangle-safe-area.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="383"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/better-context-menus-safe-triangles/2-safe-triangle-safe-area.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/better-context-menus-safe-triangles/2-safe-triangle-safe-area.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/better-context-menus-safe-triangles/2-safe-triangle-safe-area.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/better-context-menus-safe-triangles/2-safe-triangle-safe-area.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/better-context-menus-safe-triangles/2-safe-triangle-safe-area.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/better-context-menus-safe-triangles/2-safe-triangle-safe-area.jpg"
			
			sizes="100vw"
			alt="The SVG element draws a “safe” area that users interact with, even if they cannot see it"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The SVG element draws a “safe” area that users interact with, even if they cannot see it. (<a href='https://files.smashing.media/articles/better-context-menus-safe-triangles/2-safe-triangle-safe-area.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>As long as the pointer is hovering over the SVG element, we have something we can use to maintain the nested menu’s open state.</p>

<h3 id="pointer-events">Pointer Events</h3>

<p>There are two steps we need to take to achieve this. First, we’ll create a “desired” path that connects our cursor to the submenu.</p>

<p>A triangular shape is the most straightforward path we can construct between a menu item and a nested menu. You can visualize what this triangle might look like in the image below. The green represents the safe area, indicating that it won’t trigger any <code>onMouseLeave</code> events. Conversely, the red area signifies that it will start the <code>onMouseLeave</code> event since we’re likely moving toward a sibling menu item.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-pointer-events.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="608"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-pointer-events.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-pointer-events.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-pointer-events.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-pointer-events.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-pointer-events.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-pointer-events.jpg"
			
			sizes="100vw"
			alt="The bounding SVG forms a safe area in a triangular shape"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The bounding SVG forms a safe area in a triangular shape. (<a href='https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-pointer-events.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>I approached this by creating a <code>SafeArea</code> component in React that contains the SVG markup:</p>

<pre><code class="language-html">&lt;svg
  style={{
    position: "fixed",
    width: svgWidth,
    height: submenuHeight,
    pointerEvents: "none",
    zIndex: 2,
    top: submenuY,
    left: mouseX - 2
  }}
  id="svg-safe-area"
&gt;
  {/&#42; Safe Area &#42;/}
  &lt;path
    pointerEvents="auto"
    stroke="red"
    strokeWidth="0.4"
    fill="rgb(114 140 89 / 0.3)"
    d={
      `M 0, ${mouseY-submenuY} 
        L ${svgWidth},${svgHeight}
        L ${svgWidth},0 
        z`
    }
  /&gt;
&lt;/svg&gt;
</code></pre>

<p>Also, to constantly update our safe triangle and position it appropriately, we need a mouse listener, specifically <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/mousemove_event"><code>onmousemove</code></a>. I relied on a <a href="https://www.joshwcomeau.com/snippets/react-hooks/use-mouse-position/">React hook from Josh Comeau</a> called <code>useMousePosition</code> in a <code>useMousePosition.tsx</code> file that provides the safe triangle component, designating the mouse position with <code>mouseX</code> and <code>mouseY</code>.</p>

<h3 id="the-safe-triangle">The Safe Triangle</h3>

<p>The triangle is the SVG’s only <code>path</code> element. For this to work correctly, we must set the CSS <code>pointer-events</code> property to <code>none</code>, which we can do inline directly in the SVG. Then we set <code>pointer-events</code> to <code>auto</code> inline in the <code>path</code> element. This way, we stop propagating events when they are coming from the <code>path</code> element &mdash; the safe triangle &mdash; but not when events come from the SVG’s <code>red</code> area.</p>

<p>Let’s break down the path we are drawing, as it’s way more straightforward than it looks:</p>

<pre><code class="language-html">&lt;path
  pointerEvents="auto"
  stroke="red"
  strokeWidth="0.4"
  fill="rgb(114 140 89 / 0.3)"
  d={
    `M 0, ${mouseY-submenuY} 
      L ${svgWidth},${svgHeight}
      L ${svgWidth},0 
      z`
  }
/&gt;
</code></pre>

<p>We set the <code>pointer-events</code> property to <code>auto</code> to capture all mouse events, and it does not trigger the <code>onMouseLeave</code> event as long as the cursor is inside the path.</p>

<p>Next, we provide the path with some basic CSS styles for debugging purposes. This way, we can see the safe area while testing interactions.</p>

<p>The <code>0, ${mouseY-submenuY}</code> part is the path’s starting point, designating the center of the SVG’s area.</p>

<p>Then we continue our path drawing with two lines: <code>L ${svgWidth},${svgHeight}</code> and <code>L ${svgWidth},0</code>. The former represents the first line (<code>L</code>) based on the SVG’s width and height, while the latter draws the second line (<code>L</code>) based on the SVG’s width.</p>

<p>The <code>z</code> part of the path is what makes everything work. <code>z</code> is what closes the path, making a straight line to the path’s starting point, preventing the need to draw a third line.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-path-points.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="501"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-path-points.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-path-points.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-path-points.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-path-points.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-path-points.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-path-points.jpg"
			
			sizes="100vw"
			alt="Safe triangle path points"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Showing the points of the safe triangle SVG path. (<a href='https://files.smashing.media/articles/better-context-menus-safe-triangles/safe-triangle-path-points.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You can explore the path in more detail or adjust it using <a href="https://yqnn.github.io/svg-path-editor/#P=M_0_300_L_600_600_L_600_0_z">this SVG path editor</a>.</p>

<div class="partners__lead-place"></div>

<h2 id="there-are-some-gotchas">There Are Some Gotchas</h2>

<p>This is a relatively simple solution on purpose. There are some situations where this approach may be too simple, and you will need another creative solution, particularly if you’re not working in React like me.</p>

<p>For example, what if the user’s pointer moves diagonally and touches a different menu item? This approach does not capture that interaction to prevent the current nested menu from closing, but that might not be what you want it to do. Perhaps you <em>want</em> the nested menu to close and need to adjust the SVG with a different shape. An “easy” way to solve this is to debounce a cleanup function so that, on every mouse movement, you call the cleanup function. And after some number of milliseconds have passed without a mouse movement, you would remove the SVG element, and the sibling listeners would trigger as expected.</p>

<p>Another example is the navigation paths. A triangle is terrific but might not be the ideal shape for your menu and how it is designed. After doing some of my own tests, I’ve found that a curved path tends to be more effective, closer to <a href="https://www.neo4j.design/40a8cff71/p/96952e-context-menu">Needle’s approach</a> for a safe area:</p>

<figure><a href="https://files.smashing.media/articles/better-context-menus-safe-triangles/needle-context-menu-safe-area-paths.gif"><img src="https://files.smashing.media/articles/better-context-menus-safe-triangles/needle-context-menu-safe-area-paths-800.gif" width="800" height="333" alt="Needle’s Context Menu Safe Area paths" /></a><figcaption>Needle’s Context Menu Safe Area paths. (<a href="https://files.smashing.media/articles/better-context-menus-safe-triangles/needle-context-menu-safe-area-paths.gif">Large preview</a>)</figcaption></figure>

<h2 id="wrapping-up">Wrapping Up</h2>

<p>As you now know, coming up with a solution for nested menus that reveal on hover is more of a challenge than it looks on the surface. Whether a hover-based approach and the clicks it saves are worth the additional considerations that make a better user experience versus a click-based approach is totally up to you. If you go with a menu that relies on a mouse hover to reveal a nested menu, you now have a resource that enhances its usability.</p>

<p>What about you? Is this a UX challenge you’ve struggled with? Have you attempted to solve it differently? Is the “safe triangle” concept effective for your particular use case? I’d love to know in the comments!</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Paul Scanlon</author><title>How To Create Dynamic Donut Charts With TailwindCSS And React</title><link>https://www.smashingmagazine.com/2023/03/dynamic-donut-charts-tailwind-css-react/</link><pubDate>Tue, 07 Mar 2023 15:30:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/03/dynamic-donut-charts-tailwind-css-react/</guid><description>In this article, Paul Scanlon shares a super lightweight approach to creating a Donut chart using &lt;code>conic-gradient()&lt;/code>. There are no additional libraries to install or maintain, and there’s no heavy JavaScript that needs to be downloaded by the browser in order for them to work. Let’s explore!</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/03/dynamic-donut-charts-tailwind-css-react/" />
              <title>How To Create Dynamic Donut Charts With TailwindCSS And React</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>How To Create Dynamic Donut Charts With TailwindCSS And React</h1>
                  
                    
                    <address>Paul Scanlon</address>
                  
                  <time datetime="2023-03-07T15:30:00&#43;00:00" class="op-published">2023-03-07T15:30:00+00:00</time>
                  <time datetime="2023-03-07T15:30:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>CSS is amazing &mdash; I’m regularly surprised at how far it has come in the years I’ve been using it (~2005 &ndash; present). One such surprise came when I noticed this tweet by <a href="https://twitter.com/shrutibalasa/status/1612785019159982080?s=20&amp;t=6TLkMmRjOFQxKP7W-jFPcA">Shruti Balasa</a> which demonstrated how to create a pie chart using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/conic-gradient#gradient_pie-chart"><code>conic-gradient()</code></a>.</p>

<p>It’s fairly straightforward. Here’s a code snippet:</p>

<pre><code class="language-css">div {
  background: conic-gradient(red 36deg, orange 36deg 170deg, yellow 170deg);
  border-radius: 50%;
}
</code></pre>

<p>Using this tiny amount of CSS, you can create gradients that start and stop at specific angles and define a color for each ‘segment’ of the pie chart.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg"
			
			sizes="100vw"
			alt="CSS conic-gradient charts with Donut Charts and a Pie Chart"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="happy-days">Happy Days!</h2>

<p>Brills, I thought I could use this instead of a charting library for a data dashboard project I’m working on for the new <a href="https://www.cockroachlabs.com/docs/api/cloud/v1.html#get-/api/v1/clusters">CockroachDB Cloud API</a>, but I had a problem. I didn’t know the values for my chart ahead of time, and the values I was receiving from the API weren’t in degrees!</p>

<p>Here’s a preview link and Open-source repo of how I worked around those two problems, and in the rest of this post, I’ll explain how it all works.</p>

<ul>
<li>🚀 Preview: <a href="https://css-conic-gradient-charts.vercel.app/">https://css-conic-gradient-charts.vercel.app/</a></li>
<li>⚙️ Repo: <a href="https://github.com/PaulieScanlon/css-conic-gradient-charts">https://github.com/PaulieScanlon/css-conic-gradient-charts</a></li>
</ul>

<h2 id="dynamic-data-values">Dynamic Data Values</h2>

<p>Here’s some sample data from a <em>typical</em> API response which I’ve sorted by <code>value</code>.</p>

<pre><code class="language-css">const data = [
  {
    name: 'Cluster 1',
    value: 210,
  },
  {
    name: 'Cluster 2',
    value: 30,
  },
  {
    name: 'Cluster 3',
    value: 180,
  },
  {
    name: 'Cluster 4',
    value: 260,
  },
  {
    name: 'Cluster 5',
    value: 60,
  },
].sort((a, b) =&gt; a.value - b.value);
</code></pre>

<p>You can see that each item in the array has a <code>name</code> and a <code>value</code>.</p>

<p>In order to convert the <code>value</code> from a number into a <code>deg</code> value to use with CSS, there are a few things you need to do:</p>

<ul>
<li>Calculate the total amount of all the values.</li>
<li>Use the total amount to calculate the percentage that each value represents.</li>
<li>Convert the percentage into degrees.</li>
</ul>

<p><strong>Note</strong>: <em>The code I’ll be referring to in the steps below can be found in the repo here: <a href="https://github.com/PaulieScanlon/css-conic-gradient-charts/blob/main/components/donut-1.js">/components/donut-1.js</a>.</em></p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h3 id="calculate-the-total-amount">Calculate The Total Amount</h3>

<p>Using JavaScript, you can use this little one-liner to <em>sum</em> up each value from the data array, which results in a single total.</p>

<pre><code class="language-javascript">const total&#95;value = data.reduce((a, b) =&gt; a + b.value, 0);

// =&gt; 740
</code></pre>

<h3 id="calculate-the-percentage">Calculate The Percentage</h3>

<p>Now that you have a <code>total_value</code>, you can convert each of the values from the data array to a percentage using a JavaScript function. I’ve called this function <code>covertToPercent</code>.</p>

<p><strong>Note</strong>: <em>I’ve used the value of 210 from Cluster 1 in this example.</em></p>

<pre><code class="language-javascript">const convertToPercent = (num) =&gt; Math.round((num / total&#95;value) &#42; 100);

// convertToPercent(210) =&gt; 28
</code></pre>

<h3 id="convert-percentage-to-degrees">Convert Percentage to Degrees</h3>

<p>Once you have a percentage, you can convert the percentage into degrees using another JavaScript function. I’ve called this function <code>convertToDegrees</code>.</p>

<pre><code class="language-javascript">const convertToDegrees = (num) =&gt; Math.round((num / 100) &#42; 360);

// convertToDegrees(28) =&gt; 101
</code></pre>

<h3 id="the-result">The Result</h3>

<p>As a temporary test, if I were to <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map">map</a> over the items in the sorted data array, using the two functions explained above, you’d end up with the following output:</p>

<pre><code class="language-javascript">const test_output = data.map((item) =&gt; {
  const percentage = convertToPercent(item.value);
  const degrees = convertToDegrees(percentage);

  return `${degrees}deg`;
});

// =&gt; ['14deg', '29deg', '86deg', '101deg', '126deg']
</code></pre>

<p>The return value of <code>test_output</code> is an array of the <code>value</code> (in degrees) + the string <code>deg</code>.</p>

<p>This solves one of a two-part problem. I’ll now explain the other part of the problem.</p>

<p>To create a Pie chart using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/conic-gradient#gradient_pie-chart"><code>conic-gradient()</code></a>, you need two <code>deg</code> values. The first is the angle from where the gradient should start, and the second is the angle where the gradient should stop. You’ll also need a color for each segment, but I’ll come to that in a moment.</p>

<div class="break-out">

<pre><code class="language-css"> ['red 🤷 14deg', 'blue 🤷 29deg', 'green 🤷 86deg', 'orange 🤷 101deg', 'pink 🤷 126deg']
</code></pre>
</div>

<p>Using the values from the <code>test_output</code>, I only have the end value (where the gradient should stop). The start angle for each segment is actually the end angle from the previous item in the array, and the end angle is the cumulative value of all previous end values plus the current end value. And to make matters worse, the start value for the first angle needs to be manually set to <code>0</code> 🥴.</p>

<p>Here’s a diagram to better explain what that means:</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png"
			
			sizes="100vw"
			alt="A diagram which explains a function"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>If that sounds confusing, it’s because it is, but if you look at the output of a function that can do all this, it might make more sense.</p>

<pre><code class="language-css">"#...", 0, 14,
"#...",, 14, 43,
"#...",, 43, 130,
"#...",, 130, 234,
"#...",, 234, 360,
</code></pre>

<h2 id="the-function-that-can-do-all-this">The Function That Can Do All This</h2>

<p>And here’s the function that can indeed do all of this. It uses <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce"><code>reduce()</code></a> to iterate over the data array, performs the necessary addition to calculate the angles, and returns a new set of numbers that can be used to create the correct start and end angles for use in a Chart.</p>

<div class="break-out">

<pre><code class="language-javascript">const total&#95;value = data.reduce((a, b) =&gt; a + b.value, 0);
const convertToPercent = (num) =&gt; Math.round((num / total&#95;value) &#42; 100);
const convertToDegrees = (num) =&gt; Math.round((num / 100) &#42; 360);

const css&#95;string = data
  .reduce((items, item, index, array) =&gt; {
    items.push(item);

    item.count = item.count || 0;
    item.count += array[index - 1]?.count || item.count;
    item.start&#95;value = array[index - 1]?.count ? array[index - 1].count : 0;
    item.end&#95;value = item.count += item.value;
    item.start&#95;percent = convertToPercent(item.start&#95;value);
    item.end&#95;percent = convertToPercent(item.end&#95;value);
    item.start&#95;degrees = convertToDegrees(item.start&#95;percent);
    item.end&#95;degrees = convertToDegrees(item.end&#95;percent);

    return items;
  }, [])
  .map((chart) =&gt; {
    const { color, start&#95;degrees, end&#95;degrees } = chart;
    return ` ${color} ${start&#95;degrees}deg ${end&#95;degrees}deg`;
  })
  .join();
</code></pre>
</div>

<p>I’ve purposefully left this pretty verbose, so it’s easier to add in <code>console.log()</code>. I found this to be quite helpful when I was developing this function.</p>

<p>You might notice the additional <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map"><code>map</code></a> chained to the end of the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce"><code>reduce</code></a>. By using a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map"><code>map</code></a> I’m able to modify the returned values and tack on <code>deg</code>, then return them all together as an array of strings.</p>

<p>Using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join"><code>join</code></a> right at the end converts the array back to a single <code>css_string</code>, which can be used with <code>conic-gradient()</code> 😅.</p>

<pre><code class="language-css">"#..." 0deg 14deg,
"#..." 14deg 43deg,
"#..." 43deg 130deg,
"#..." 130deg 234deg,
"#..." 234deg 360deg
</code></pre>

<div class="partners__lead-place"></div>

<h2 id="using-the-css-string-with-an-svg-foreignobject">Using The <code>css_string</code> With An SVG <code>foreignObject</code></h2>

<p>Now, unfortunately, you can’t use <code>conic-gradient()</code> with <a href="https://developer.mozilla.org/en-US/docs/Web/SVG">SVG</a>. But you can wrap an HTML element inside a <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject"><code>foreignObject</code></a> and style the <code>background</code> using a <code>conic-gradient()</code>.</p>

<div class="break-out">

<pre><code class="language-html">&lt;svg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' style={{ borderRadius: '100%' }}&gt;
  &lt;foreignObject x='0' y='0' width='100' height='100'&gt;
    &lt;div
      xmlns='http://www.w3.org/1999/xhtml'
      style={{
        width: '100%',
        height: '100%',
        background: `conic-gradient(${css_string})`, // &lt;- 🥳
      }}
    /&gt;
  &lt;/foreignObject&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>Using the above, you should be looking at a Pie chart. In order to make a Donut chart, I’ll need to explain how to make the hole.</p>

<h2 id="let-s-talk-about-the-hole">Let’s Talk About the Hole</h2>

<p>There’s only really one way you can ‘mask’ off the middle of the Pie chart to reveal the background. This approach involves using a <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath"><code>clipPath</code></a>. This approach looks like the below code snippet. I’ve used this for Donut 1.</p>

<p><strong>Note</strong>: <em>The <code>src</code> for Donut 1 can be seen here: <a href="https://github.com/PaulieScanlon/css-conic-gradient-charts/blob/main/components/donut-1.js#L44">components/donut-1.js</a>.</em></p>

<div class="break-out">

<pre><code class="language-html">&lt;svg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' style={{ borderRadius: '100%' }}&gt;

  &lt;clipPath id='hole'&gt;
    &lt;path d='M 50 0 a 50 50 0 0 1 0 100 50 50 0 0 1 0 -100 v 18 a 2 2 0 0 0 0 64 2 2 0 0 0 0 -64' /&gt;
  &lt;/clipPath&gt;

  &lt;foreignObject x='0' y='0' width='100' height='100' clipPath='url(#hole)'&gt;
    &lt;div
      xmlns='http://www.w3.org/1999/xhtml'
      style={{
        width: '100%',
        height: '100%',
        background: `conic-gradient(${css_string})`
      }}
    /&gt;
  &lt;/foreignObject&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>However, there is another way. This approach involves using a <code>&lt;circle /&gt;</code> element and placing it in the center of the pie chart. This will work if the fill of the <code>&lt;circle /&gt;</code> matches the background color of whatever the chart is placed on. In my example, I’ve used a pattern background, and you’ll notice if you look closely at Donut 3 that you can’t see the <a href="https://heropatterns.com/">bubble pattern</a> through the center of the chart.</p>

<p><strong>Note</strong>: <em>The <code>src</code> for Donut 3 can be seen here: <a href="https://github.com/PaulieScanlon/css-conic-gradient-charts/blob/main/components/donut-3.js#L44">components/donut-3.js</a>.</em></p>

<div class="break-out">

<pre><code class="language-html">&lt;svg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' style={{ borderRadius: '100%' }}&gt;
  &lt;foreignObject x='0' y='0' width='100' height='100'&gt;
    &lt;div
      xmlns='http://www.w3.org/1999/xhtml'
      style={{
        width: '100%',
        height: '100%',
        background: `conic-gradient(${css&#95;string})`
      }}
    /&gt;
  &lt;/foreignObject&gt;
  &lt;circle cx='50' cy='50' r='32' fill='white' /&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>IMO the <code>clipPath</code> approach is nicer, but it can be more difficult to amend the path points to get the desired thickness of the hole if you don’t have access to something like Figma or Illustrator.</p>

<h2 id="finally-colors">Finally, Colors!</h2>

<p>Colors for charts are something that always cause me problems. Most of the time, the colors I use are defined in CSS, and all this stuff is happening in JavaScript, so how do you use CSS variables in JavaScript?</p>

<p>In my example site, I’m using <a href="https://tailwindcss.com/">Tailwind</a> to style ‘all the things’ and by using <a href="https://gist.github.com/Merott/d2a19b32db07565e94f10d13d11a8574">this trick</a>, I’m able to expose the CSS variables so they can be referred to by their name.</p>

<p>If you want to do the same, you could add a <code>color</code> key to the data array:</p>

<pre><code class="language-css">data={[
  {
    name: 'Cluster 1',
    value: 210,
    color: 'var(--color-fuchsia-400)',
  },
  {
    name: 'Cluster 2',
    value: 30,
    color: 'var(--color-fuchsia-100)',
  },
  {
    name: 'Cluster 3',
    value: 180,
    color: 'var(--color-fuchsia-300)',
  },
  {
    name: 'Cluster 4',
    value: 260,
    color: 'var(--color-fuchsia-500)',
  },
  {
    name: 'Cluster 5',
    value: 60,
    color: 'var(--color-fuchsia-200)',
  },
].sort((a, b) =&gt; a.value - b.value)
</code></pre>

<p>And then reference the <code>color</code> key in the array <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map"><code>map</code></a> to return it as part of the <code>css_string</code>. I’ve used this approach in Donut 2.</p>

<p><strong>Note</strong>: <em>You can see the <code>src</code> for Donut 2 here: <a href="https://github.com/PaulieScanlon/css-conic-gradient-charts/blob/main/components/donut-2.js#L25">components/donut-2.js</a>.</em></p>

<pre><code class="language-javascript">.map((chart) =&gt; {
  const { color, start&#95;degrees, end&#95;degrees } = chart;
  return ` ${color} ${start&#95;degrees}deg ${end&#95;degrees}deg`;
})
.join();
</code></pre>

<p>You could even dynamically create the color name using a hard-coded value (<code>color-pink-</code>) + the <code>index</code> from the array. I’ve used this approach in Donut 1.</p>

<p><strong>Note</strong>: <em>You can see the <code>src</code> for Donut 1 here: <a href="https://github.com/PaulieScanlon/css-conic-gradient-charts/blob/main/components/donut-1.js#L26">components/donut-1.js</a>.</em></p>

<div class="break-out">

<pre><code class="language-javascript">.map((chart, index) =&gt; {
  const { start&#95;degrees, end&#95;degrees } = chart;
  return ` var(--color-pink-${(index + 1) &#42; 100}) ${start&#95;degrees}deg ${end&#95;degrees}deg`;
})
.join();
</code></pre>
</div>

<div class="partners__lead-place"></div>

<h2 id="if-you-re-lucky">If You’re Lucky!</h2>

<p>However, you might get lucky and be working with an API that actually returns values with an associated color. This is the case with the <a href="https://docs.github.com/en/graphql">GitHub GraphQL API</a>. So. I popped together one last example.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png"
			
			sizes="100vw"
			alt="GitHub GraphQL API with Github chart with ten different languages associated with its own color"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You can see this working in your browser by visiting <a href="https://css-conic-gradient-charts.vercel.app/github">/github</a>, and the <code>src</code> for both the GitHub Donut Chart and Legend can be found here:</p>

<ul>
<li><a href="https://github.com/PaulieScanlon/css-conic-gradient-charts/blob/main/components/github-chart.js">components/github-chart.js</a>;</li>
<li><a href="https://github.com/PaulieScanlon/css-conic-gradient-charts/blob/main/components/github-legend.js">components/github-legend.js</a>.</li>
</ul>

<h2 id="wrapping-up">Wrapping Up</h2>

<p>You might be thinking this is quite complicated, and it’s probably easier to use a Charting Library, and you’re probably right. It probably is. But this way is <strong>super lightweight</strong>. There are no additional libraries to install or maintain, and there’s no heavy JavaScript that needs to be downloaded by the browser in order for them to work.</p>

<p>I experimented once before with creating Donut Charts using an SVG and the <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray"><code>stroke-dashoffset</code></a>. You can read about that in my article, “<a href="https://paulie.dev/posts/2021/01/react-svg-doughnut-chart/">Create an SVG Doughnut Chart From Scratch For Your Gatsby Blog</a>.” That approach worked really well, but I think I prefer the approach described in this post. CSS is simply the best!</p>

<p>If you’d like to discuss any of the methods I’ve used here, please come find me on Twitter: <a href="https://twitter.com/PaulieScanlon">@PaulieScanlon</a>.</p>

<p>See you around the internet!</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(yk, il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Paul Scanlon</author><title>Putting Gears In Motion: Animating Cars With HTML And SVG</title><link>https://www.smashingmagazine.com/2023/02/putting-gears-motion-animating-cars-with-html-svg/</link><pubDate>Thu, 16 Feb 2023 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/02/putting-gears-motion-animating-cars-with-html-svg/</guid><description>SVG &lt;code>&amp;lt;animateMotion&amp;gt;&lt;/code> provides a way to define how an element moves along a motion path. In this article, Paul Scanlon shares an idea of how to use it by animating race cars in an infinite loop as easy as one-two-three!</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/02/putting-gears-motion-animating-cars-with-html-svg/" />
              <title>Putting Gears In Motion: Animating Cars With HTML And SVG</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Putting Gears In Motion: Animating Cars With HTML And SVG</h1>
                  
                    
                    <address>Paul Scanlon</address>
                  
                  <time datetime="2023-02-16T13:00:00&#43;00:00" class="op-published">2023-02-16T13:00:00+00:00</time>
                  <time datetime="2023-02-16T13:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>Hello! And if you like HTML, you’ve come to the right place!</p>

<p>I love HTML. As an old-school front-end developer, I think it’s a hugely underrated skill. I’ve been writing HTML since ~2005, and today the browser alone can almost do all the things Flash could do nearly two decades ago!</p>

<p>One such <em>trick</em> HTML now has is called <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animateMotion"><code>&lt;animateMotion&gt;</code></a> &mdash; those familiar with Flash will remember this as <em>The Motion Guide</em>. I found this video from 14 years ago, but the method existed for a while before that:</p>


<figure class="video-embed-container">
  <div
  
  class="video-embed-container--wrapper">
		<lite-youtube
			videoid="owojvNuuOxg"
      
			videotitle="Flash Tutorial - 5 - Motion Guide Layers (&lt;a href=&#39;https://www.youtube.com/watch?v=owojvNuuOxg&#39;&gt;Watch on YouTube&lt;/a&gt;)"
		></lite-youtube>
	</div>
	
		<figcaption>Flash Tutorial - 5 - Motion Guide Layers (<a href='https://www.youtube.com/watch?v=owojvNuuOxg'>Watch on YouTube</a>)</figcaption>
	
</figure>

<p>The idea is, you create a path for elements to follow… and that’s it!</p>

<p>Here’s an example of what you can do with <code>&lt;animateMotion&gt;</code>:</p>

<ul>
<li>🚀 <a href="https://animate-motion-race-cars.vercel.app/">Live Preview</a></li>
<li>⚙️ <a href="https://github.com/PaulieScanlon/animate-motion-race-cars">Repository</a></li>
</ul>

<p>If you take a look at the <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animateMotion#example">MDN Docs</a>, you’ll see a simple example of a red circle following a path on an infinite loop. The race cars in the live preview follow the same simple rules, and it works just like this!</p>

<figure><a href="https://animate-motion-race-cars.vercel.app/"><img src="https://smashing-files.ams3.digitaloceanspaces.com/articles/animate-race-cars-using-html/race-cars-animation.gif" width="600" height="374" alt="Three animated cars in blue, green and pink following dashed lines" /></a><figcaption>A simple example of what can be achieved using <code>animateMotion</code>. (<a href='https://animate-motion-race-cars.vercel.app/'>See animation</a>)</figcaption></figure>

<h2 id="svg-using-animatemotion">SVG Using <code>animateMotion</code></h2>

<p>Here’s a <a href="https://animate-motion-race-cars.vercel.app/simple-version.html">simplified version</a> which I&rsquo;ll use to explain some of the finer details.</p>

<p><strong>Note</strong>: <em>I’ve removed some of the path values for brevity, but you can see  <code>src</code> for the below snippet at <a href="https://github.com/PaulieScanlon/animate-motion-race-cars/blob/main/simple-version.html">simple-version.html</a>.)</em></p>

<div class="break-out">

<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Simple Example&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;main&gt;
      &lt;svg viewBox="0 0 307 184" xmlns="http://www.w3.org/2000/svg"&gt;
        &lt;g id="track"&gt;
          &lt;g id="track-lines"&gt;
            &lt;path fill="none" stroke="#facc15" d="M167.88,111.3..." /&gt;
          &lt;/g&gt;

          &lt;g id="pink-car"&gt;
            &lt;animateMotion dur="4s" repeatCount="indefinite" rotate="auto" path="M167.88,111.3..." /&gt;
            &lt;path fill="#EC4899" d="M13.71,18.65c0.25-0.5..." /&gt;
          &lt;/g&gt;
        &lt;/g&gt;
      &lt;/svg&gt;
    &lt;/main&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
</div>

<p>The first thing to look at is the <code>&lt;g&gt;</code> element with the <code>id</code> of <code>track-lines</code>. This is the yellow dashed line that represents the path the car will follow.</p>

<p>You’ll also see another <code>&lt;g&gt;</code> element with the <code>id</code> of <code>pink-car</code>. Within this group is the <code>&lt;animateMotion&gt;</code> element. It has an attribute of <code>path</code>. The numbers used to form this path are the same as the numbers that form the <code>track-lines</code>. An <code>&lt;animateMotion&gt;</code> element is invisible, and its only purpose is to provide a path for an element to follow.</p>

<p>Speaking of which, below the <code>&lt;animateMotion&gt;</code> element is another <code>&lt;path&gt;</code> element, this is the pink car, and it will follow the path of its nearest neighbor.</p>

<h2 id="animatemotion-attributes"><code>animateMotion</code> Attributes</h2>

<p>There’s some additional attributes that the <code>&lt;animateMotion&gt;</code> element accepts; these are as follows:</p>

<ul>
<li><code>dur</code>: The duration of the animation.</li>
<li><code>repeatCount</code>: The number of times the animation should loop.</li>
<li><code>rotate</code>: This can be considered as an orientation to the path. It will ensure the element that’s animating around the path always faces the direction of travel.</li>
<li><code>path</code>: As explained, this is the actual path an element will follow.<br /></li>
</ul>

<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animateMotion#example">MDN Docs</a> show the <code>&lt;animateMotion&gt;</code> element as a child of an Svg <code>&lt;circle&gt;</code> shape e.g:</p>

<pre><code class="language-html">&lt;circle r="5" fill="red"&gt;
  &lt;animateMotion
    dur="10s"
    repeatCount="indefinite"
    path="M20,50 C20,-50 180,150 180,50 C180-50 20,150 20,50 z" /&gt;
&lt;/circle&gt;
</code></pre>

<p>Whilst this approach works for shapes, it will only work if the element can accept a child. The SVG path element can’t, so wrapping everything in the <code>&lt;g&gt;</code> element allows HTML to work out where the coordinate system should start and which elements should follow the path. Sneaky ay!</p>

<p>And that’s it. I designed the track and the other elements seen on the <a href="https://animate-motion-race-cars.vercel.app/">preview link</a> in Adobe Illustrator and exported the whole thing as an SVG. I then did a little bit of manual refactoring to ensure the cars were adjacent to an <code>&lt;animateMotion&gt;</code> element. Et voilà! A race track!</p>

<div class="partners__lead-place"></div>

<h2 id="accessibility">Accessibility</h2>

<p>One small snag, the <code>&lt;animateMotion&gt;</code> element doesn’t natively observe <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion">prefers-reduce-motion</a>. To work around this in the preview I&rsquo;ve added a <a href="https://github.com/PaulieScanlon/animate-motion-race-cars/blob/main/index.html#L17">media query</a> that sets any element with the class name of <code>car</code> to <code>display: none;</code>. Not ideal, but it is at least motion safe!</p>

<p>I hope you’ve enjoyed this post, and if you have any questions, please come and find me on Twitter. <a href="https://twitter.com/PaulieScanlon">@PaulieScanlon</a>, oh and if you’re a better illustrator than I am, please, feel free to re-design the race track and cars, and I’ll be happy to convert it into code!</p>

<p>See you around the internet!</p>

<h3 id="further-reading-on-smashingmag">Further Reading On SmashingMag</h3>

<ul>
<li>“<a href="https://www.smashingmagazine.com/2021/10/real-time-multi-user-game/">How To Build A Real-Time Multi-User Game From Scratch</a>,” Martin Grubinger</li>
<li>“<a href="https://www.smashingmagazine.com/2023/01/svg-customization-animation-practical-guide/">Easy SVG Customization And Animation: A Practical Guide</a>,” Adrian Bece</li>
<li>“<a href="https://www.smashingmagazine.com/2021/10/composable-css-animation-vue-animxyz/">Composable CSS Animation In Vue With AnimXYZ</a>,” Ejiro Asiuwhu</li>
<li>“<a href="https://www.smashingmagazine.com/2022/11/guide-keyboard-accessibility-html-css-part1/">A Guide To Keyboard Accessibility: HTML And CSS (Part 1)</a>,” Cristian Díaz</li>
</ul>

<div class="partners__lead-place"></div>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(yk, il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item></channel></rss>