Documentation menu
Packages
React hooks
Motion values hold numbers and strings that change without re-rendering. The hooks create them, derive them, follow the scroll and start animations by hand.
Motion values
A motion value holds a number or string that changes without re-rendering the component. Pass it in style and the renderer writes it each frame.
import { animate, motion, useMotionValue, useTransform } from "@animaly/react";
import { useEffect } from "react";
export default function Slider() {
const x = useMotionValue(0);
const opacity = useTransform(x, [0, 200], [0.3, 1]);
useEffect(() => {
const controls = animate(x, 200, { type: "spring", stiffness: 120 });
return () => controls.stop();
}, [x]);
return <motion.div style={{ x, opacity, width: 40, height: 40, background: "#7c3aed" }} />;
}import { animate, useMotionValue, useMotionValueEvent } from "@animaly/react";
import { useEffect, useState } from "react";
export default function Counter() {
const count = useMotionValue(0);
const [label, setLabel] = useState("0");
useMotionValueEvent(count, "change", (latest) => setLabel(String(Math.round(latest))));
useEffect(() => {
const controls = animate(count, 1280, { duration: 1.2, ease: "easeOut" });
return () => controls.stop();
}, [count]);
return <span>{label} downloads</span>;
}import { motion, useMotionValue, useSpring } from "@animaly/react";
export default function Follower() {
const x = useMotionValue(0);
const smooth = useSpring(x, { stiffness: 200, damping: 20 });
return (
<div onPointerMove={(event) => x.set(event.clientX)} style={{ height: 120 }}>
<motion.div style={{ x: smooth, width: 24, height: 24, borderRadius: 12, background: "#db2777" }} />
</div>
);
}import { animate, motion, useMotionTemplate, useMotionValue } from "@animaly/react";
import { useEffect } from "react";
export default function Glow() {
const blur = useMotionValue(0);
const filter = useMotionTemplate`blur(${blur}px)`;
useEffect(() => {
const controls = animate(blur, 4, { duration: 0.6 });
return () => controls.stop();
}, [blur]);
return <motion.div style={{ filter, width: 80, height: 80, background: "#7c3aed" }} />;
}import { motion, useMotionValue, useTransform, useVelocity } from "@animaly/react";
export default function Skew() {
const x = useMotionValue(0);
const velocity = useVelocity(x);
const skewX = useTransform(velocity, [-1000, 0, 1000], [-15, 0, 15]);
return (
<div onPointerMove={(event) => x.set(event.clientX)} style={{ height: 120 }}>
<motion.div style={{ x, skewX, width: 60, height: 60, background: "#7c3aed" }} />
</div>
);
}Scroll
useScroll returns scrollX, scrollY, scrollXProgress and scrollYProgress as motion values, with the same target, container and offset options as scroll.
import { motion, useScroll } from "@animaly/react";
export default function ReadingProgress() {
const { scrollYProgress } = useScroll();
return (
<motion.div
style={{ scaleX: scrollYProgress, transformOrigin: "left", position: "fixed", top: 0, left: 0, right: 0, height: 3, background: "#7c3aed" }}
/>
);
}import { motion, useScroll, useTransform } from "@animaly/react";
import { useRef } from "react";
export default function Reveal() {
const ref = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: ref, offset: ["start end", "end end"] });
const opacity = useTransform(scrollYProgress, [0, 1], [0, 1]);
const y = useTransform(scrollYProgress, [0, 1], [60, 0]);
return (
<div style={{ height: "200vh", paddingTop: "120vh" }}>
<motion.div ref={ref} style={{ opacity, y, height: 200, background: "#ede9fe" }} />
</div>
);
}import { useInView } from "@animaly/react";
import { useRef } from "react";
export default function Lazy() {
const ref = useRef<HTMLDivElement>(null);
const visible = useInView(ref, { once: true, margin: "200px" });
return <div ref={ref}>{visible ? "Loaded" : "Waiting"}</div>;
}Imperative animations
useAnimate returns a scope ref and an animate whose selectors only match inside that scope. Its animations stop when the component unmounts.
import { useAnimate } from "@animaly/react";
export default function Shake() {
const [scope, animate] = useAnimate();
const shake = () => animate(scope.current, { x: [0, -8, 8, -6, 6, 0] }, { duration: 0.4 });
return (
<div ref={scope}>
<button type="button" onClick={shake}>
Wrong password
</button>
</div>
);
}import { useAnimate } from "@animaly/react";
import { useEffect } from "react";
export default function Intro() {
const [scope, animate] = useAnimate();
useEffect(() => {
animate([
["h2", { opacity: [0, 1], y: [16, 0] }],
["p", { opacity: [0, 1] }, { at: "-0.1" }],
]);
}, [animate]);
return (
<div ref={scope}>
<h2 style={{ opacity: 0 }}>Hello</h2>
<p style={{ opacity: 0 }}>Selectors only match inside this component.</p>
</div>
);
}import { motion, useAnimationControls } from "@animaly/react";
export default function Bell() {
const controls = useAnimationControls();
return (
<div>
<motion.span animate={controls} style={{ display: "inline-block" }}>
🔔
</motion.span>
<button type="button" onClick={() => controls.start({ rotate: [0, -15, 15, -10, 10, 0] })}>
Ring
</button>
</div>
);
}import { motion, useCycle } from "@animaly/react";
export default function Shape() {
const [shape, next] = useCycle({ borderRadius: 0 }, { borderRadius: 40 });
return <motion.div animate={shape} onTap={() => next()} style={{ width: 80, height: 80, background: "#7c3aed" }} />;
}