Build a Svelte Component To Draw SVG On Hover
Tags: svelte, svg, npm, css
Published: 2022-06-03 07:01:51

In this post, I'll share my journey of building a reusable Svelte component to draw SVG on hover. Begin by trying to reduce boilerplate in my work project and publishing my first npm package.

It's great if you've already known below things:

- Svelte Transition
  - [`transition:draw`](https://svelte.dev/docs#run-time-svelte-transition-draw)
- Svelte Component Composition
  - [`let:` directive](https://svelte.dev/docs#template-syntax-slot-slot-key-value)
  - [`--style-props`](https://svelte.dev/docs#template-syntax-component-directives---style-props)

- [SVG animation in CSS](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animate)

> This is my npm package: [Svelte Hover Draw SVG](https://github.com/davipon/svelte-hover-draw-svg)

> Any feedbacks are welcomed!
> ![github cover](https://drive.google.com/uc?id=12iEiQ5pZg5YnW66D38ZUfq-SIcdG5TKJ)

## Let's talk about motivation

In my work project, I have a Tab component with inline SVG in each tab:
![tabs](https://lh3.googleusercontent.com/WPzx9fFfy0PNF9a4KV6tNKm48qNYdOcOgtQXzQiAbcRKgTpwQQxrYR9CLwZGrJeo5ofHm-rUwK5GOnu3ZjP3TZIHrPAc5fobu9cpbWYwz4dXYHHl0A=w1064-v0)

I like to add SVG animation on hover, and the first that came to my mind was `transition:draw` in Svelte:

![example-1](https://drive.google.com/uc?id=1asLscU9aMVeyCSPx-KTJloAlvtopt8Fj)

It works, but here are the problems:

1. Boilerplates like `on:mouseenter`, `{#if cursorOn === 'tab'}`, and `transition:draw`
2. SVG might have multiple `<path>`, `line`, `rect`, `polyline`, `polygon`. We need to add `transition` to each of them manually
3. We can't use it when the SVG is in a component like `<SVGInComponent/>` (`draw` can only be used on `SVGElement` or element which has `getTotalLength()` method.)

## Can we DRY them up?

![DRY](https://media.giphy.com/media/yGxf8XbJHJkPUaJsGg/giphy.gif)

> DRY = don't repeat yourself

I don't want to write boilerplate again and again if I just want this simple effect.
So I started to find a way to **abstract** the hovering event & effect.

First thing we need to know is the `draw` animation is actually a CSS transition:

```js
// Please refer to https://svelte.dev/tutorial/custom-css-transitions
function draw(node, { delay = 0, speed, duration, easing: easing$1 = easing.cubicInOut } = {}) {
  let len = node.getTotalLength();
  const style = getComputedStyle(node);
  if (style.strokeLinecap !== 'butt') {
    len += parseInt(style.strokeWidth);
  }
  if (duration === undefined) {
    if (speed === undefined) {
      duration = 800;
    }
    else {
      duration = len / speed;
    }
  }
  else if (typeof duration === 'function') {
    duration = duration(len);
  }
  return {
    delay,
    duration,
    easing: easing$1,
    css: (t, u) => `stroke-dasharray: ${t * len} ${u * len}`
  };
}
```

This is the source code of `draw`, the key part is:

```js
 css: (t, u) => `stroke-dasharray: ${t * len} ${u * len}
```

When the `transition` is triggered, a CSS animation with custom `@keyframes` would be added to that element:

![dev console](https://drive.google.com/uc?id=1low0OGqR_U_KF2mnINkTkzmgW7u2P5Up)

![dev console2](https://drive.google.com/uc?id=11FWyQVm60wtzJJqgGk9TKwOI9HFbBJsd)

So what we're trying to do is quite simple: toggling a CSS animation on hover!

### A Trick That Makes Drawing SVG Lines Way Easier

I was looking for a way to simplify the complex `@keyframes`, and I found this article: [A Trick That Makes Drawing SVG Lines Way Easier](https://css-tricks.com/a-trick-that-makes-drawing-svg-lines-way-easier/) by Chris Coyier.

By setting the `pathLength="1"` to `SVGElement` (like `<path>`, `line`, `rect`, `polyline`, and `polygon`), set the `stroke-dasharray` to 1, and animate the offset in CSS!

```html
<path class="path" d="M66.039,133.545 ... " pathLength="1" />
```

```css
.path {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: dash 5s linear alternate infinite;
}

@keyframes dash {
  from {
    stroke-dashoffset: 1;
  }
  to {
    stroke-dashoffset: 0;
  }
}
```

I also found that I can put this `path` class to elements that have SVG inside, which means we don't need to add the animation to `SVGElement` **programmatically**:

```html
<div class="tabs">
  <a href="#tab1" class="tab">
    <svg stroke-width="2">
      <!-- This works -->
      <path class="path" pathLength="1" d="..." />
    </svg>
    <span>Tab 1</span>
  </a>
  <!-- This also works -->
  <a href="#tab2" class="tab path">
    <svg stroke-width="2">
      <path pathLength="1" d="..."/>
    </svg>
    <span>Tab 2</span>
  </a>
</div>
```

### Make a wrapper component for abstraction

The goal of creating a wrapper component:

- Let the wrapper component handle the hover event & effect.
- Trigger animation on hover (component level, not just SVG).
- User can just put their components/ SVGs in the wrapper component.


Here is the final approach:

![source-code](https://drive.google.com/uc?id=1km1xsS9L6YxXJxvjNBVbkyf06at_5WtA)

> You can try it out here: [Svelte REPL](https://svelte.dev/repl/d9990fc8a03743d0b03dc95ed50e5893?version=3.48.0)

## Easy to use + less code to write FTW 💪

- Inline SVG:

![inline-svg](https://drive.google.com/uc?id=1LvJnxVku4nCs-ZeVGhlvB7zyUAuC2rHX)

- SVG Component (set draw time to 2s, default is 1s):

![svg-component](https://drive.google.com/uc?id=1I3GM3Wc1c_q8vdrOlxEH8lTbFBzsNclE)

- Nested elements (expose `hovering` status):

![nested-svg](https://drive.google.com/uc?id=1tBDjVnJz5AR3vsPfzuJRr-32qTeiTOWD)

## Wrapping up

There were some technical considerations I didn't mention in this article and finally gave up on, like designing a custom transition and using named slots to validate `SVGElement`, ...etc.

It'd be great to let users customize the transition using the native parameters like `duration` and `easing`, but it's overkill if this simple effect can be achieved by CSS.

That's all. Thank you for your reading! 🙌

You can find me on Twitter: [@davipon](https://twitter.com/davipon)