sdxc

[ @sdxc/u/animation ]

animation

Emits an @keyframes rule under name together with the host animation-* declarations that reference it.

import { animation } from "@sdxc/u/animation";

Quick reference

Every documented call, beside the CSS it emits.

CallCSS
u.animation("fade-in", { keyframes: { from: { opacity: 0 }, to: { opacity: 1 }, }, duration: "150ms", easing: "ease-out", });css({ "@keyframes fade-in": { from: { opacity: 0 }, to: { opacity: 1 } }, animationName: "fade-in", animationDuration: "150ms", animationTimingFunction: "ease-out", })
u.animation("fade-in", { keyframes: { from: { opacity: 0 }, to: { opacity: 1 } }, duration: "150ms", delay: "150ms", });css({ "@keyframes fade-in": { from: { opacity: 0 }, to: { opacity: 1 } }, animationName: "fade-in", animationDuration: "150ms", animationDelay: "150ms", })

Examples

Basic example

Use animation(name, config) with keyframes and a duration to animate an element; iterationCount: "infinite" keeps it running:

spin
<div mix={[...]}>
	<div
		mix={[
			animation("spin", {
				keyframes: { from: { rotate: "0deg" }, to: { rotate: "360deg" } },
				duration: "1s",
				easing: "linear",
				iterationCount: "infinite",
			}),
			...,
		]}
	>
		spin
	</div>
</div>

Generating the name

Leave the name out and pass only the config, and a stable name is generated from the keyframes:

pulse
<div mix={[...]}>
	<div
		mix={[
			animation({
				keyframes: { "0%, 100%": { opacity: 1 }, "50%": { opacity: 0.4 } },
				duration: "2s",
				easing: "ease-in-out",
				iterationCount: "infinite",
			}),
			...,
		]}
	>
		pulse
	</div>
</div>

Alternating direction

Set direction: "alternate" to play every other cycle in reverse, so keyframes written one way swing back and forth:

sway
<div mix={[...]}>
	<div
		mix={[
			animation("sway", {
				keyframes: { from: { translate: "-2rem 0" }, to: { translate: "2rem 0" } },
				duration: "800ms",
				easing: "ease-in-out",
				iterationCount: "infinite",
				direction: "alternate",
			}),
			...,
		]}
	>
		sway
	</div>
</div>

Delaying and holding frames

Set delay and fillMode: "both" for an entrance that stays hidden while it waits and keeps its final frame. Reload the frame to replay it:

enter
<div mix={[...]}>
	<div
		mix={[
			animation("enter", {
				keyframes: {
					from: { opacity: 0, translate: "0 2rem" },
					to: { opacity: 1, translate: "0 0" },
				},
				duration: "600ms",
				easing: "ease-out",
				delay: "500ms",
				fillMode: "both",
			}),
			...,
		]}
	>
		enter
	</div>
</div>

Driving it with scroll

Set timeline: "view()" to drive the animation by the element's position in its scroll container instead of time. Scroll the box to reveal each card:

Card One
Card Two
Card Three
Card Four
Card Five
Card Six
<div mix={[scroll("y"), bs(64), ...]}>
	<div mix={[...]}>
		{["One", "Two", "Three", "Four", "Five", "Six"].map((label) => (
			<div
				mix={[
					animation("reveal", {
						keyframes: {
							from: { opacity: 0, scale: "0.8" },
							to: { opacity: 1, scale: "1" },
						},
						duration: "auto",
						easing: "linear",
						fillMode: "both",
						timeline: "view()",
						range: "entry 0% entry 100%",
					}),
					...,
				]}
			>
				Card {label}
			</div>
		))}
	</div>
</div>

Applying on a state

Wrap the call in a state utility to scope it to one selector.

u.hover(u.animation("fade-in", {
  keyframes: {
    from: { opacity: 0 },
    to: { opacity: 1 },
  },
  duration: "150ms",
  easing: "ease-out",
});)

Responsive design

Wrap the call in a container query to scope it to one width.

u.at("md", u.animation("fade-in", {
  keyframes: {
    from: { opacity: 0 },
    to: { opacity: 1 },
  },
  duration: "150ms",
  easing: "ease-out",
});)

Signature

u.animation(name: string, config: AnimationConfig)

Written by Sergio Xalambrí. Follow @sergiodxa for new packages, or sponsor the work.