DEV Community

Cover image for Nice custom Svelte animations
Dimon
Dimon

Posted on • Originally published at dimonb19a.hashnode.dev

Nice custom Svelte animations

Svelte ships fade, fly, slide, scale, blur, … in svelte/transition so you can just import those ones and use them out of the box. But you also can create your own actions and transitions, and use them via use: directive, or with in:/out:, - very convenient across elements. Let me share several actions that i use the most often in our design system.

What Svelte gives you natively is the ergonomics: a one-line use:/in:/out: directive on any element, with teardown handled for you. React has no native directive and no built-in enter/exit system — exit animations in particular usually mean a library like AnimatePresence. (Vue, fair to note, has both natively — custom directives and <Transition>.)

Materialize / Dematerialize

Visual-only enter/exit for overlays (modals, tooltips, toasts) — opacity + scale + blur + Y, no layout shift. Custom alternative to Svelte's built-in fade.


{#if open}
  <div in:materialize out:dematerialize>
    Modal / tooltip / toast content
  </div>
{/if}
Enter fullscreen mode Exit fullscreen mode

Morph

A persistent element smoothly resizes when its content changes - no mount/unmount.


<div use:morph={{ width: false, height: true }}>
  {#if expanded}<LongContent />{:else}<ShortContent />{/if}
</div>
Enter fullscreen mode Exit fullscreen mode

Emerge / Dissolve

The counterpart to morph. Morph animates an existing element's size; emerge/dissolve mount and unmount elements - and because they animate the new card's height and margin up from zero, the card below slides down to make room instead of snapping.


{#each cards as card (card.id)}
  <div in:emerge out:dissolve>{card.title}</div>
{/each}
Enter fullscreen mode Exit fullscreen mode

Tooltip

Hover or focus a button - a tooltip appears and Floating UI flips / shifts it to stay on screen. This is the one action that imports a third-party package, for the collision-aware positioning math.

Imports @floating-ui/dom (computePosition + flip/shift + autoUpdate)

<button use:tooltip={'Saved to your library'}>Hover me</button>
Enter fullscreen mode Exit fullscreen mode

Gradient Border

The CTA's rotating comet border stops spinning on hover and locks onto your cursor.


<button class="btn-cta" use:laserAim>Get started</button>
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
nnutnonn profile image
Nutchapon Makelai

that a nice custom transition, would like to know more how you implement on that custom transition 🤔

Collapse
 
dimonb19a profile image
Dimon

A custom Svelte transition is just a function that receives the DOM node and returns a config object. The key part is css(t, u) - Svelte calls it ahead of time to generate a real CSS keyframe animation (so it runs off the main thread), where t goes 0 -> 1 on entry (and 1 -> 0 on exit) and u = 1 - t.

Here's materialize from the post, trimmed to its standalone core:

import { cubicOut } from 'svelte/easing';

export function materialize(node, { delay = 0, duration = 400, y = 15 } = {}) {
  // respect users who opt out of motion
  if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
    return { delay, duration, css: (t) => `opacity: ${t};` };
  }

  return {
    delay,
    duration,
    easing: cubicOut,
    css: (t, u) => `
      transform: translateY(${u * y}px) scale(${0.96 + 0.04 * t});
      opacity: ${t};
      filter: blur(${Math.max(0, 8 * (u * 2 - 1))}px);
    `,
  };
}

<div in:materialize out:materialize={{ y: -20 }}>…</div>
Enter fullscreen mode Exit fullscreen mode