<?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: Dhruvil Chauhan</title>
    <description>The latest articles on DEV Community by Dhruvil Chauhan (@dhruvil_chauhan_eccb4a1eb).</description>
    <link>https://dev.to/dhruvil_chauhan_eccb4a1eb</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3946803%2Fc6663bb0-f2fb-4426-b8f6-7ead625abdaf.png</url>
      <title>DEV Community: Dhruvil Chauhan</title>
      <link>https://dev.to/dhruvil_chauhan_eccb4a1eb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dhruvil_chauhan_eccb4a1eb"/>
    <language>en</language>
    <item>
      <title>I built a CLI that tells you why your Next.js components became client components</title>
      <dc:creator>Dhruvil Chauhan</dc:creator>
      <pubDate>Thu, 04 Jun 2026 07:49:36 +0000</pubDate>
      <link>https://dev.to/dhruvil_chauhan_eccb4a1eb/i-built-a-cli-that-tells-you-why-your-nextjs-components-became-client-components-11d1</link>
      <guid>https://dev.to/dhruvil_chauhan_eccb4a1eb/i-built-a-cli-that-tells-you-why-your-nextjs-components-became-client-components-11d1</guid>
      <description>&lt;p&gt;React Server Components are great — until &lt;code&gt;"use client"&lt;/code&gt; starts spreading through your codebase like a virus.&lt;/p&gt;

&lt;p&gt;You added it to one file months ago. Now half your component tree is running on the client and you have no&lt;br&gt;
  idea which import dragged it there. Your bundle is massive, your Lighthouse score is suffering, and nobody&lt;br&gt;
  can explain why.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;client-creep&lt;/strong&gt; to solve this.&lt;/p&gt;

&lt;p&gt;## What it does&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  npx client-creep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero setup. No install. Runs on any Next.js 13/14/15/16 project.&lt;/p&gt;

&lt;p&gt;It answers three questions no existing tool answered together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Why is this a client component?&lt;/strong&gt; — which import chain caused it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Did it need to be?&lt;/strong&gt; — or is it client purely by accident, with zero hooks or browser APIs?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is it costing you?&lt;/strong&gt; — estimated KB being shipped to the browser&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;## Real output on a 534-file Next.js app&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ────────────────────────────────────────────────────────────
    client-creep  Next.js client component analysis
  ────────────────────────────────────────────────────────────

    Files scanned:            534
    Client components:        418  (182 boundaries)
    Estimated client JS:      2.29 MB
    Potentially recoverable:  237.4 KB  (113 creep candidates)

  ────────────────────────────────────────────────────────────
    ⚠  Accidental Client Creep
  ────────────────────────────────────────────────────────────

    ⚠ src/app/chat-insights/components/EmptyStates.tsx  19.7 KB recoverable
      No hooks, event handlers, or browser APIs detected
      Why client:
      ⚡ src/app/chat-insights/page.tsx ← use client
        └─ src/app/chat-insights/components/index.ts
             └─ src/app/chat-insights/components/EmptyStates.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last section is the key one — &lt;strong&gt;accidental creep candidates&lt;/strong&gt;. Components that are only client because&lt;br&gt;
  something upstream imported them through a barrel file. No hooks. No browser APIs. Just an accident.&lt;/p&gt;

&lt;p&gt;## The full ecosystem&lt;/p&gt;

&lt;p&gt;The CLI is the core, but there's more:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ESLint plugin&lt;/strong&gt; — catches creep as you write code, not after:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; eslint-plugin-client-creep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// eslint.config.js&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;clientCreep&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eslint-plugin-client-creep&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;clientCreep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommended&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GitHub Action&lt;/strong&gt; — posts a PR comment showing new creep introduced by a branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DhruvilChauahan0210/client-creep@main&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ci&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;budget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;  &lt;span class="c1"&gt;# fail if &amp;gt; 500 KB client JS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Dashboard&lt;/strong&gt; — track creep trend over time across PRs:&lt;br&gt;
  &lt;a href="https://client-creep-dashboard.vercel.app" rel="noopener noreferrer"&gt;client-creep-dashboard.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VS Code extension&lt;/strong&gt; — inline diagnostics as you type (Marketplace publish coming soon).&lt;/p&gt;

&lt;p&gt;## Interactive HTML graph&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  npx client-creep &lt;span class="nt"&gt;--html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generates a D3 force-directed graph of your entire import graph, color-coded by component type. Click any&lt;br&gt;
  node to see its import chain and why it's client. Useful for showing the team where the real problems are.&lt;/p&gt;

&lt;p&gt;## CI usage&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="c"&gt;# Fail if any accidental creep exists&lt;/span&gt;
  npx client-creep &lt;span class="nt"&gt;--ci&lt;/span&gt;

  &lt;span class="c"&gt;# Fail if client JS exceeds 500 KB&lt;/span&gt;
  npx client-creep &lt;span class="nt"&gt;--budget&lt;/span&gt; 500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;## How it works&lt;/p&gt;

&lt;p&gt;It's a static analyzer — no need to run your app.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Globs all &lt;code&gt;.ts/.tsx&lt;/code&gt; files, skipping &lt;code&gt;node_modules&lt;/code&gt; and &lt;code&gt;.next&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Parses each file with a Babel AST, detects &lt;code&gt;"use client"&lt;/code&gt; and extracts imports&lt;/li&gt;
&lt;li&gt;Resolves imports including &lt;code&gt;tsconfig&lt;/code&gt; path aliases and monorepo workspace packages&lt;/li&gt;
&lt;li&gt;Builds a directed import graph and propagates client boundaries via BFS&lt;/li&gt;
&lt;li&gt;Flags nodes with no detected client signals (hooks, browser globals, event handlers) as accidental creep&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Works in monorepos too — auto-detects pnpm/turbo/yarn workspaces and resolves cross-package imports.&lt;/p&gt;




&lt;p&gt;GitHub: &lt;a href="https://github.com/DhruvilChauahan0210/client-creep" rel="noopener noreferrer"&gt;github.com/DhruvilChauahan0210/client-creep&lt;/a&gt;&lt;br&gt;
  npm: &lt;code&gt;npx client-creep&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Would love feedback — especially if you run it on your codebase and find something unexpected.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I replaced GSAP DrawSVG with 4.4 KB (and it uses native CSS where it can)</title>
      <dc:creator>Dhruvil Chauhan</dc:creator>
      <pubDate>Mon, 01 Jun 2026 16:33:27 +0000</pubDate>
      <link>https://dev.to/dhruvil_chauhan_eccb4a1eb/i-replaced-gsap-drawsvg-with-44-kb-and-it-uses-native-css-where-it-can-3mdg</link>
      <guid>https://dev.to/dhruvil_chauhan_eccb4a1eb/i-replaced-gsap-drawsvg-with-44-kb-and-it-uses-native-css-where-it-can-3mdg</guid>
      <description>&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;You know the pattern. You have an SVG — a logo, a path, a hand-drawn illustration — and you want it to draw itself as the user scrolls. It looks incredible. Every design agency uses it. Every portfolio has it.&lt;/p&gt;

&lt;p&gt;So you reach for GSAP DrawSVG.&lt;/p&gt;

&lt;p&gt;Then you see the pricing page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GSAP DrawSVG requires a Club GreenSock license for commercial use.&lt;/strong&gt; That's fine if you're at an agency billing $200/hr. It's annoying if you're shipping a SaaS or an open-source project. And either way, you just imported &lt;strong&gt;~40 KB&lt;/strong&gt; of runtime for one visual effect.&lt;/p&gt;

&lt;p&gt;Framer Motion does scroll-triggered SVG animation too. It's &lt;strong&gt;~35 KB&lt;/strong&gt;, React-only, and your Vue teammates will look at you funny.&lt;/p&gt;

&lt;p&gt;I got tired of the options, so I built the missing one.&lt;/p&gt;




&lt;h2&gt;
  
  
  svg-scroll-draw — what it is
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i svg-scroll-draw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;~4.4 KB gzipped. Zero dependencies. MIT license.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Works in React, Next.js, Vue 3, Svelte, Solid.js, Angular, Nuxt, Astro, Web Component, and vanilla JS — same library, nine thin adapters.&lt;/p&gt;

&lt;p&gt;The simplest possible usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// React&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ScrollDraw&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="s1"&gt;svg-scroll-draw/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;Hero&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="nc"&gt;ScrollDraw&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="na"&gt;viewBox&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0 0 200 100"&lt;/span&gt; &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"none"&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;path&lt;/span&gt; &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"M10 50 Q100 10 190 50"&lt;/span&gt; &lt;span class="na"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"black"&lt;/span&gt; &lt;span class="na"&gt;strokeWidth&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"2"&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;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&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;That's it. The engine discovers every &lt;code&gt;path&lt;/code&gt;, &lt;code&gt;line&lt;/code&gt;, &lt;code&gt;rect&lt;/code&gt;, &lt;code&gt;circle&lt;/code&gt;, &lt;code&gt;polyline&lt;/code&gt;, and &lt;code&gt;polygon&lt;/code&gt; inside the container, measures total stroke length, and animates &lt;code&gt;stroke-dashoffset&lt;/code&gt; as the element enters the viewport. No configuration required.&lt;/p&gt;




&lt;h2&gt;
  
  
  The part that makes it different: native CSS
&lt;/h2&gt;

&lt;p&gt;Here's what I didn't expect when building this: for the common case, you don't need JavaScript at all.&lt;/p&gt;

&lt;p&gt;When the browser supports &lt;code&gt;animation-timeline: view()&lt;/code&gt; (Chrome, Edge, Firefox — about 75% of global traffic today), &lt;strong&gt;svg-scroll-draw hands the animation to the compositor entirely.&lt;/strong&gt; Zero per-frame JS. No scroll listeners. No &lt;code&gt;requestAnimationFrame&lt;/code&gt;. The GPU does it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This runs on the compositor in supporting browsers —&lt;/span&gt;
&lt;span class="c1"&gt;// no scroll event, no rAF, no JS per frame&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&lt;/span&gt; &lt;span class="na"&gt;easing&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ease-out"&lt;/span&gt; &lt;span class="na"&gt;fade&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;&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;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The library detects support at runtime and falls back to the full JS engine automatically. You never change your code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What triggers the JS fallback?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Any feature CSS can't express declaratively: &lt;code&gt;onProgress&lt;/code&gt; / &lt;code&gt;onComplete&lt;/code&gt; callbacks, &lt;code&gt;stagger&lt;/code&gt;, &lt;code&gt;morphTo&lt;/code&gt;, &lt;code&gt;velocityScale&lt;/code&gt;, &lt;code&gt;autoReverse&lt;/code&gt;, &lt;code&gt;once&lt;/code&gt;, &lt;code&gt;repeat&lt;/code&gt;, custom scroll containers, &lt;code&gt;speed ≠ 1&lt;/code&gt;, &lt;code&gt;spring&lt;/code&gt; easing, or animated &lt;code&gt;strokeColor&lt;/code&gt; / &lt;code&gt;strokeWidth&lt;/code&gt; / &lt;code&gt;fillOpacity&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The escape hatch if you need it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Force the JS engine regardless&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&lt;/span&gt; &lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;easing&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"spring"&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;&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;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full instance API — &lt;code&gt;pause()&lt;/code&gt;, &lt;code&gt;resume()&lt;/code&gt;, &lt;code&gt;seek()&lt;/code&gt;, &lt;code&gt;replay()&lt;/code&gt;, &lt;code&gt;destroy()&lt;/code&gt; — works identically on both paths.&lt;/p&gt;




&lt;h2&gt;
  
  
  It's not just stroke-dashoffset
&lt;/h2&gt;

&lt;p&gt;I've been thinking about scroll-driven SVG animation for a while, and there are more patterns than just "draw a line":&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stroke color animation&lt;/strong&gt; — the stroke interpolates between two colors as the path draws:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&lt;/span&gt; &lt;span class="na"&gt;strokeColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ff6b9d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ffc900&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;easing&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ease-out"&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;&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;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fill opacity flood&lt;/strong&gt; — the fill appears as the outline traces itself. Useful for logo reveals where the shape "fills in" after the stroke completes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&lt;/span&gt; &lt;span class="na"&gt;fillOpacity&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;easing&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ease-out"&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt; &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#ff90e8"&lt;/span&gt; &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#ff90e8"&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;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Path morphing&lt;/strong&gt; — the shape interpolates from its original &lt;code&gt;d&lt;/code&gt; to a target on scroll:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&lt;/span&gt; &lt;span class="na"&gt;morphTo&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"M 130 40 L 220 130 L 130 220 L 40 130 Z"&lt;/span&gt; &lt;span class="na"&gt;easing&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ease-in-out"&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt; &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"M 130 40 C 220 40 220 220 130 220 C 40 220 40 40 130 40 Z"&lt;/span&gt; &lt;span class="na"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"black"&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;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Clip-path reveal&lt;/strong&gt; — reveal any content (not just SVGs — images, divs, text) using CSS &lt;code&gt;clip-path&lt;/code&gt; instead of stroke animation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Works on images, headings, anything&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&lt;/span&gt; &lt;span class="na"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"left"&lt;/span&gt; &lt;span class="na"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.8&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;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/hero.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&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="nc"&gt;ScrollDraw&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stagger&lt;/strong&gt; — multiple paths draw in sequence, each starting after the previous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&lt;/span&gt; &lt;span class="na"&gt;stagger&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;easing&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ease-out"&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="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* chart bars, each draws in sequence */&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;rect&lt;/span&gt; &lt;span class="err"&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;rect&lt;/span&gt; &lt;span class="err"&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;rect&lt;/span&gt; &lt;span class="err"&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;svg&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="nc"&gt;ScrollDraw&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Velocity scale&lt;/strong&gt; — draw speed scales with how fast the user is scrolling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&lt;/span&gt; &lt;span class="na"&gt;velocityScale&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;1.5&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;svg&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;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ScrollDraw&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Framework usage
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Vue 3:&lt;/strong&gt;&lt;br&gt;
&lt;/p&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;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ScrollDraw&lt;/span&gt; &lt;span class="na"&gt;easing=&lt;/span&gt;&lt;span class="s"&gt;"ease-out"&lt;/span&gt; &lt;span class="na"&gt;:speed=&lt;/span&gt;&lt;span class="s"&gt;"1.2"&lt;/span&gt; &lt;span class="na"&gt;fade&lt;/span&gt; &lt;span class="na"&gt;once&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 200 100"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M10 50 Q100 10 190 50"&lt;/span&gt; &lt;span class="na"&gt;stroke=&lt;/span&gt;&lt;span class="s"&gt;"black"&lt;/span&gt; &lt;span class="na"&gt;stroke-width=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ScrollDraw&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ScrollDraw&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="s1"&gt;svg-scroll-draw/vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Svelte:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;scrollDraw&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="s1"&gt;svg-scroll-draw/svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;use:scrollDraw=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;easing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spring&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 200 100"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M10 50 Q100 10 190 50"&lt;/span&gt; &lt;span class="na"&gt;stroke=&lt;/span&gt;&lt;span class="s"&gt;"black"&lt;/span&gt; &lt;span class="na"&gt;stroke-width=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Vanilla JS:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;scrollDraw&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="s1"&gt;svg-scroll-draw&lt;/span&gt;&lt;span class="dl"&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;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scrollDraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#hero-svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;easing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ease-out&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onComplete&lt;/span&gt;&lt;span class="p"&gt;:&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all paths drawn!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Full imperative control&lt;/span&gt;
&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// jump to 50%&lt;/span&gt;
&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replay&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// cleanup on unmount&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Astro:&lt;/strong&gt;&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;div
  data-scroll-draw
  data-scroll-draw-options='{"easing":"ease-out","fade":true,"once":true}'
&amp;gt;
  &amp;lt;svg viewBox="0 0 200 100" fill="none"&amp;gt;
    &amp;lt;path d="M10 50 Q100 10 190 50" stroke="black" stroke-width="2" /&amp;gt;
  &amp;lt;/svg&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
  import { initScrollDraw } from 'svg-scroll-draw/astro';
  initScrollDraw();
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Bundle size comparison (minified + gzipped)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Frameworks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;svg-scroll-draw&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~4.4 KB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MIT (free)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;React, Vue, Svelte, Solid, Angular, Nuxt, Astro, Web Component, Vanilla&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Framer Motion&lt;/td&gt;
&lt;td&gt;~35 KB&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;React only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GSAP DrawSVG&lt;/td&gt;
&lt;td&gt;~40 KB&lt;/td&gt;
&lt;td&gt;Commercial license required&lt;/td&gt;
&lt;td&gt;Any (with adapter)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Under the hood — how the JS engine works
&lt;/h2&gt;

&lt;p&gt;For the curious: the core engine uses &lt;code&gt;IntersectionObserver&lt;/code&gt; to start a &lt;code&gt;requestAnimationFrame&lt;/code&gt; loop when the element enters the viewport, and stops it when the element exits. This avoids scroll listeners entirely and keeps idle cost near zero.&lt;/p&gt;

&lt;p&gt;The progress value is derived from &lt;code&gt;getBoundingClientRect()&lt;/code&gt; relative to the viewport, clamped to [0, 1], and mapped through the easing function. &lt;code&gt;stroke-dasharray&lt;/code&gt; is set once (to the total path length), and only &lt;code&gt;stroke-dashoffset&lt;/code&gt; changes per frame — a cheap, compositable property.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;prefers-reduced-motion&lt;/code&gt; check is handled in the engine: if the user has reduced motion enabled, the draw completes immediately at full progress without animation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live playground:&lt;/strong&gt; &lt;a href="https://svg-scroll-draw.vercel.app/playground" rel="noopener noreferrer"&gt;https://svg-scroll-draw.vercel.app/playground&lt;/a&gt; — paste any SVG, tweak options, get a shareable link&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Examples:&lt;/strong&gt; &lt;a href="https://svg-scroll-draw.vercel.app/examples" rel="noopener noreferrer"&gt;https://svg-scroll-draw.vercel.app/examples&lt;/a&gt; — 10 production-ready demos (logo reveal, revenue chart, delivery route, API architecture diagram, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://svg-scroll-draw.vercel.app/docs" rel="noopener noreferrer"&gt;https://svg-scroll-draw.vercel.app/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;code&gt;npm i svg-scroll-draw&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/DhruvilChauahan0210/ink-scroll" rel="noopener noreferrer"&gt;https://github.com/DhruvilChauahan0210/ink-scroll&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;221 tests passing. 7 suites. MIT.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you've been reaching for GSAP or Framer just to draw an SVG on scroll, give this a shot. The playground makes it easy to test with your own SVG before adding it to your project.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>react</category>
      <category>css</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Dhruvil Chauhan</dc:creator>
      <pubDate>Sat, 23 May 2026 03:21:00 +0000</pubDate>
      <link>https://dev.to/dhruvil_chauhan_eccb4a1eb/-4p1d</link>
      <guid>https://dev.to/dhruvil_chauhan_eccb4a1eb/-4p1d</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/dhruvil_chauhan_eccb4a1eb/i-built-react-components-that-run-qwen25-in-the-browser-via-webgpu-no-server-no-api-key-works-7e0" class="crayons-story__hidden-navigation-link"&gt;I built React components that run Qwen2.5 in the browser via WebGPU – no server, no API key, works offline&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/dhruvil_chauhan_eccb4a1eb" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3946803%2Fc6663bb0-f2fb-4426-b8f6-7ead625abdaf.png" alt="dhruvil_chauhan_eccb4a1eb profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/dhruvil_chauhan_eccb4a1eb" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Dhruvil Chauhan
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Dhruvil Chauhan
                
              
              &lt;div id="story-author-preview-content-3728349" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/dhruvil_chauhan_eccb4a1eb" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3946803%2Fc6663bb0-f2fb-4426-b8f6-7ead625abdaf.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Dhruvil Chauhan&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/dhruvil_chauhan_eccb4a1eb/i-built-react-components-that-run-qwen25-in-the-browser-via-webgpu-no-server-no-api-key-works-7e0" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 22&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/dhruvil_chauhan_eccb4a1eb/i-built-react-components-that-run-qwen25-in-the-browser-via-webgpu-no-server-no-api-key-works-7e0" id="article-link-3728349"&gt;
          I built React components that run Qwen2.5 in the browser via WebGPU – no server, no API key, works offline
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/dhruvil_chauhan_eccb4a1eb/i-built-react-components-that-run-qwen25-in-the-browser-via-webgpu-no-server-no-api-key-works-7e0" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/dhruvil_chauhan_eccb4a1eb/i-built-react-components-that-run-qwen25-in-the-browser-via-webgpu-no-server-no-api-key-works-7e0#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            1 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>I built React components that run Qwen2.5 in the browser via WebGPU – no server, no API key, works offline</title>
      <dc:creator>Dhruvil Chauhan</dc:creator>
      <pubDate>Fri, 22 May 2026 21:38:38 +0000</pubDate>
      <link>https://dev.to/dhruvil_chauhan_eccb4a1eb/i-built-react-components-that-run-qwen25-in-the-browser-via-webgpu-no-server-no-api-key-works-7e0</link>
      <guid>https://dev.to/dhruvil_chauhan_eccb4a1eb/i-built-react-components-that-run-qwen25-in-the-browser-via-webgpu-no-server-no-api-key-works-7e0</guid>
      <description>&lt;p&gt;Been working on this for a while – open sourced it this week.&lt;br&gt;
&amp;nbsp;It's a React library called Local Ghost that runs Qwen2.5-Coder-0.5B&lt;br&gt;
&amp;nbsp;entirely in the browser via WebGPU (WASM fallback for unsupported&lt;br&gt;
&amp;nbsp;browsers). No server, no API key, no per-query cost.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;Three components:&lt;/p&gt;

&lt;p&gt;&amp;nbsp;SmartDataGrid – natural language filter/sort over any data array.&lt;br&gt;
&amp;nbsp;Type "show engineers older than 30 sorted by salary" and it generates&lt;br&gt;
&amp;nbsp;a sandboxed JS filter and executes it locally.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;SmartForm – paste any unstructured text (receipt, email, bio) and the&lt;br&gt;
&amp;nbsp;model extracts structured JSON to fill form fields.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;SmartAnalytics – describe a chart in plain English, get bar/line/pie/&lt;br&gt;
&amp;nbsp;scatter rendered instantly from your data.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;Tech: Qwen2.5-Coder-0.5B-Instruct via u/huggingface,&lt;br&gt;
&amp;nbsp;WebGPU with automatic device-lost hotswap to WASM, 5-min idle VRAM&lt;br&gt;
&amp;nbsp;purge, IndexedDB query cache, hardened sandbox (nulls window/document/&lt;br&gt;
&amp;nbsp;fetch/localStorage inside generated code).&lt;/p&gt;

&lt;p&gt;&amp;nbsp;Model downloads once (~300MB), cached in CacheStorage forever.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;npm: u/dhruvil0210&lt;br&gt;
&amp;nbsp;Demo: &lt;a href="https://local-ghost-demo.vercel.app" rel="noopener noreferrer"&gt;https://local-ghost-demo.vercel.app&lt;/a&gt;&lt;br&gt;
&amp;nbsp;Docs: &lt;a href="https://local-ghost-docs.vercel.app" rel="noopener noreferrer"&gt;https://local-ghost-docs.vercel.app&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecropdooquxsvej3omoi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecropdooquxsvej3omoi.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48g55kgp4oe4mdadai0a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48g55kgp4oe4mdadai0a.png" alt=" " width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>react</category>
    </item>
  </channel>
</rss>
