Transitions
We can make more appealing user interfaces by gracefully transitioning elements into and out of the DOM. Solivelte makes this very easy with the transition directive.
import { createSignal, Show, type Component } from "solid-js";import { TransitionGroup, fade, transition } from "solivelte";
const TransitionExample: Component = () => { const [isVisible, setVisible] = createSignal(true);
return ( <> <button onClick={() => setVisible((prev) => !prev)}> {isVisible() ? "Hide" : "Show"} </button> <TransitionGroup> <Show when={isVisible()}> <div use:transition={fade}>fades in and out</div> </Show> </TransitionGroup> </> );};
Adding parameters
Transition functions can accept parameters. Let’s replace the fade
transition with fly
and apply it along with some options:
import { createSignal, Show, type Component } from "solid-js";import { TransitionGroup, fly, transition } from "solivelte";
const TransitionExample: Component = () => { const [isVisible, setVisible] = createSignal(true);
return ( <> <button onClick={() => setVisible((prev) => !prev)}> {isVisible() ? "Hide" : "Show"} </button> <TransitionGroup> <Show when={isVisible()}> <div use:transition={(node) => fly(node, { y: 200 })}>flies in and out</div> </Show> </TransitionGroup> </> );};
In and out
Instead of the transition
directive, an element can have an inTransition
or an outTransition
directive, or both together. Let’s replace the transition
directive with inTransition
and outTransition
directives:
import { createSignal, Show, type Component } from "solid-js";import { TransitionGroup, slide, fade, inTransition, outTransition } from "solivelte";
const TransitionExample: Component = () => { const [isVisible, setVisible] = createSignal(true);
return ( <> <button onClick={() => setVisible((prev) => !prev)}> {isVisible() ? "Hide" : "Show"} </button> <TransitionGroup> <Show when={isVisible()}> <div use:inTransition={slide} use:outTransition={fade}>slides in, fades out</div> </Show> </TransitionGroup> </> );};
Custom CSS transitions
Solivelte has a handful of built-in transitions, but it’s very easy to create your own. By way of example, this is the source of the fade
transition:
function fade(node, { delay = 0, duration = 400 }) { const o = +getComputedStyle(node).opacity;
return { delay, duration, css: (t) => `opacity: ${t * o}` };}
The function takes two arguments — the node to which the transition is applied, and any parameters that were passed in — and returns a transition object which can have the following properties:
delay
— milliseconds before the transition beginsduration
— length of the transition in millisecondseasing
— ap => t
easing functioncss
— a(t, u) => css
function, whereu === 1 - t
tick
— a(t, u) => {...}
function that has some effect on the node
The t
value is 0
at the beginning of an intro or the end of an outro, and 1
at the end of an intro or beginning of an outro.
Most of the time you should return the css
property and not the tick
property, as CSS animations run off the main thread to prevent jank where possible. Solivelte ‘simulates’ the transition and constructs a CSS animation, then lets it run.
For example, the fade
transition generates a CSS animation somewhat like this:
0% { opacity: 0 }10% { opacity: 0.1 }20% { opacity: 0.2 }/* ... */100% { opacity: 1 }
We can get a lot more creative though. Let’s make something truly gratuitous:
import { createSignal, Show, type Component } from "solid-js";import { TransitionGroup, elasticOut, fade, inTransition, outTransition, type TransitionConfig } from "solivelte";
function spin(node: Element, { duration = 2000 }): TransitionConfig { return { duration, css: (t) => { const eased = elasticOut(t);
return ` transform: scale(${eased}) rotate(${eased * 1080}deg); color: hsl( ${Math.trunc(t * 360)}, ${Math.min(100, 1000 * (1 - t))}%, ${Math.min(50, 500 * (1 - t))}% );`; }, };}
const TransitionExample: Component = () => { const [isVisible, setVisible] = createSignal(true);
return ( <> <button onClick={() => setVisible((prev) => !prev)}> {isVisible() ? "Hide" : "Show"} </button> <TransitionGroup> <Show when={isVisible()}> <div use:inTransition={spin} use:outTransition={fade}>transitions!</div> </Show> </TransitionGroup> </> );};
Custom JS transitions
While you should generally use CSS for transitions as much as possible, there are some effects that can’t be achieved without JavaScript, such as a typewriter effect:
Solivelte
import { createSignal, Show, type Component } from "solid-js";import { TransitionGroup, transition, type TransitionConfig } from "solivelte";
function typewriter(node: Element, { speed = 1 }): TransitionConfig { const valid = node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE;
if (!valid) { throw new Error(`This transition only works on elements with a single text node child`); }
const text = node.textContent; const duration = text!.length / (speed * 0.01);
return { duration, tick: (t) => { const i = Math.trunc(text!.length * t); node.textContent = text!.slice(0, i); } };}
const TransitionExample: Component = () => { const [isVisible, setVisible] = createSignal(true);
return ( <> <button onClick={() => setVisible((prev) => !prev)}> {isVisible() ? "Hide" : "Show"} </button> <TransitionGroup> <Show when={isVisible()}> <p use:transition={typewriter}>Solivelte</p> </Show> </TransitionGroup> </> );};
Transition events
It can be useful to know when transitions are beginning and ending. Solivelte dispatches events that you can listen to like any other DOM event:
status:
import { createSignal, Show, type Component } from "solid-js";import { TransitionGroup, fade, transition } from "solivelte";
const TransitionExample: Component = () => { const [isVisible, setVisible] = createSignal(true); const [status, setStatus] = createSignal("");
return ( <> <button onClick={() => setVisible((prev) => !prev)}> {isVisible() ? "Hide" : "Show"} </button> <p>status: {status()}</p> <TransitionGroup> <Show when={isVisible()}> <div use:transition={(node) => fade(node, { duration: 1000 })} on:introstart={() => setStatus("intro started")} on:outrostart={() => setStatus("outro started")} on:introend={() => setStatus("intro ended")} on:outroend={() => setStatus("outro ended")} > fades in and out </div> </Show> </TransitionGroup> </> );};