<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Naved Alam</title>
    <description>The latest articles on DEV Community by Naved Alam (@navedux).</description>
    <link>https://dev.to/navedux</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3924891%2F2b6f224b-c379-4757-b02e-50d37eec5c50.png</url>
      <title>DEV Community: Naved Alam</title>
      <link>https://dev.to/navedux</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/navedux"/>
    <language>en</language>
    <item>
      <title>Animated SVG on a product page, without Lottie</title>
      <dc:creator>Naved Alam</dc:creator>
      <pubDate>Fri, 23 Oct 2026 03:57:56 +0000</pubDate>
      <link>https://dev.to/navedux/animated-svg-on-a-product-page-without-lottie-4mja</link>
      <guid>https://dev.to/navedux/animated-svg-on-a-product-page-without-lottie-4mja</guid>
      <description>&lt;p&gt;A lot of product pages have one small moving illustration near a feature: a queue draining, a cache filling, a chart ticking up. It's usually a GIF, a video or a Lottie file. For most of these, an SVG with a few lines of CSS animation inside it is lighter, sharper and easier to keep on brand.&lt;/p&gt;

&lt;p&gt;Here's how I add them, the three ways to put one on a page, and the two things people skip: reduced motion and pausing offscreen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "animated SVG" means here
&lt;/h2&gt;

&lt;p&gt;An SVG file is code. It can carry its own &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; block, and CSS animations inside that block play wherever the SVG renders. No player script, no video element. It stays sharp at any size because it's vector, and it can use your site's colors.&lt;/p&gt;

&lt;p&gt;A minimal example: three bars that rise in turn.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;svg viewBox="0 0 120 48" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="t"&amp;gt;
  &amp;lt;title id="t"&amp;gt;Three bars rising in turn&amp;lt;/title&amp;gt;
  &amp;lt;style&amp;gt;
    .bar {
      fill: currentColor;
      transform-box: fill-box;
      transform-origin: bottom;
      animation: rise 1.4s ease-in-out infinite;
    }
    .b2 { animation-delay: 0.15s; }
    .b3 { animation-delay: 0.3s; }

    @keyframes rise {
      0%, 100% { transform: scaleY(0.4); }
      50%      { transform: scaleY(1); }
    }

    @media (prefers-reduced-motion: reduce) {
      .bar { animation: none; }
    }
  &amp;lt;/style&amp;gt;
  &amp;lt;rect class="bar b1" x="10" y="8" width="24" height="40" rx="3" /&amp;gt;
  &amp;lt;rect class="bar b2" x="48" y="8" width="24" height="40" rx="3" /&amp;gt;
  &amp;lt;rect class="bar b3" x="86" y="8" width="24" height="40" rx="3" /&amp;gt;
&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details worth copying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;transform-box: fill-box&lt;/code&gt; plus &lt;code&gt;transform-origin: bottom&lt;/code&gt; makes each bar scale from its own bottom edge, not from the corner of the SVG.&lt;/li&gt;
&lt;li&gt;The reduced-motion rule lives inside the file, so it goes wherever the file goes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Three ways to put it on a page
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. As an image
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/illustrations/bars.svg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Three bars rising in turn"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"240"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"96"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the simplest. The CSS animation inside the file still plays. The trade-off: the page can't reach inside it. Your page CSS won't style it, &lt;code&gt;currentColor&lt;/code&gt; won't pick up your text color, and you can't pause it from outside.&lt;/p&gt;

&lt;p&gt;Use this when the illustration has fixed colors and you just need it to show up.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Inline in the HTML
&lt;/h3&gt;

&lt;p&gt;Paste the &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; markup straight into the page. Now it's part of the DOM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;currentColor&lt;/code&gt; follows the color of the parent element, so it matches light and dark themes for free.&lt;/li&gt;
&lt;li&gt;CSS variables from your page work inside it.&lt;/li&gt;
&lt;li&gt;Page scripts can pause it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cost is markup in your HTML, so keep the SVG clean (no editor metadata, no hidden layers).&lt;/p&gt;

&lt;h3&gt;
  
  
  3. As a React component
&lt;/h3&gt;

&lt;p&gt;Same as inline, wrapped in a component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Bars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0 0 120 48"&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"img"&lt;/span&gt; &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Three bars rising in turn"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`
        .bars-bar {
          fill: currentColor;
          transform-box: fill-box;
          transform-origin: bottom;
          animation: bars-rise 1.4s ease-in-out infinite;
        }
        .bars-b2 { animation-delay: 0.15s; }
        .bars-b3 { animation-delay: 0.3s; }
        @keyframes bars-rise {
          0%, 100% { transform: scaleY(0.4); }
          50%      { transform: scaleY(1); }
        }
        @media (prefers-reduced-motion: reduce) {
          .bars-bar { animation: none; }
        }
      `&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;rect&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bars-bar"&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"8"&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"40"&lt;/span&gt; &lt;span class="na"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;rect&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bars-bar bars-b2"&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"48"&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"8"&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"40"&lt;/span&gt; &lt;span class="na"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;rect&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bars-bar bars-b3"&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"86"&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"8"&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"40"&lt;/span&gt; &lt;span class="na"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One gotcha: a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; inside an inline SVG is global to the page. If two illustrations both define &lt;code&gt;.bar&lt;/code&gt; or &lt;code&gt;@keyframes rise&lt;/code&gt;, they collide. Prefix class names and keyframe names per illustration, as above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep it on brand with currentColor and variables
&lt;/h2&gt;

&lt;p&gt;Inline SVG can read your design tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.bars-bar&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--illo-ink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentColor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.bars-b3&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--illo-accent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#14b8a6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt;            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--illo-ink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0f172a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;--illo-accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0d9488&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;dark&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--illo-ink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e2e8f0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;--illo-accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2dd4bf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Switch the theme and the illustration follows, with no second file for dark mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Respect reduced motion
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;@media (prefers-reduced-motion: reduce)&lt;/code&gt; block in the examples turns the animation off for people who asked their OS for less motion. Make sure the still frame still makes sense: pick keyframes where the resting state (here, &lt;code&gt;animation: none&lt;/code&gt; leaves the bars at full height) reads as a finished picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pause it when it scrolls away
&lt;/h2&gt;

&lt;p&gt;An illustration halfway down the page doesn't need to animate while nobody can see it. An &lt;code&gt;IntersectionObserver&lt;/code&gt; can toggle a class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.is-paused&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nc"&gt;.is-paused&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation-play-state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;paused&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;usePauseOffscreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;io&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is-paused&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIntersecting&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// usage&lt;/span&gt;
&lt;span class="c1"&gt;// const ref = usePauseOffscreen();&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;lt;Bars ref={ref} /&amp;gt;   (forward the ref, or wrap Bars in a &amp;lt;div ref={ref}&amp;gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This only works for inline SVG. An &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; is a closed box to the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  SVG or Lottie?
&lt;/h2&gt;

&lt;p&gt;Lottie is good for long, character-style animation exported from After Effects. For a small explanatory loop next to a feature, CSS-animated SVG wins on the basics: no player script on the page, plain text you can diff in a pull request, and colors that follow your theme.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you'd rather not build these by hand
&lt;/h2&gt;

&lt;p&gt;Disclosure: I make Illos, a library of animated SVG illustrations for product sites. Each one works the way this post describes: CSS animation inside the file, React or plain SVG, light and dark, reduced motion handled. There's a free set you can use in commercial work with no account and no credit needed, and Pro adds skills so Claude Code, Cursor, Codex or any coding agent can place illustrations in your codebase in the same style.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The longer guide: &lt;a href="https://illos.dev/guides/animated-svg?ref=devto" rel="noopener noreferrer"&gt;https://illos.dev/guides/animated-svg?ref=devto&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The free set: &lt;a href="https://illos.dev/library/free?ref=devto" rel="noopener noreferrer"&gt;https://illos.dev/library/free?ref=devto&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions about any of the code above are welcome in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>css</category>
      <category>svg</category>
    </item>
  </channel>
</rss>
