Play Button
ericts-ui/play-buttonFreeComponent
A circular media toggle whose glyph morphs between a play triangle and pause bars, with a ghost or frosted over-video surface.
Live preview
live · rendered by the registry
Rendered by ericts-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.ericts.com/r/play-button.jsonSource
1"use client";23import * as React from "react";4import {5 animate,6 motion,7 useMotionValue,8 useReducedMotion,9 type Transition,10} from "motion/react";1112import { cn } from "@/lib/utils";1314// YouTube's play button is one `<path>` whose `d` is rewritten frame by frame,15// and the shape of that data is the whole trick: the play triangle is split16// down x=17 into two sub-paths, each written as a four-corner rounded quad.17// The pause icon is the same two quads with different corners, so the morph is18// a straight lerp of eight points instead of a path diff — the triangle's left19// half straightens into the first bar while its tip unfolds into the second.20//21// The play numbers below are recovered from the player's own path data: the22// corner vertices sit at (7.5, 3) / (7.5, 33) with a 3.93 tangent length, and23// the tip is a degenerate corner pair at (33, 18).2425const ICON_VIEW_BOX = 36;26const EPSILON = 1e-4;2728type Point = readonly [number, number];29type Corner = { readonly point: Point; readonly radius: number };30/** Corners run top-left → bottom-left → bottom-right → top-right. */31type Shape = readonly Corner[];3233const PLAY_SHAPES: readonly [Shape, Shape] = [34 [35 { point: [7.5, 3], radius: 3.93 },36 { point: [7.5, 33], radius: 3.93 },37 { point: [17, 27.4], radius: 0 },38 { point: [17, 8.6], radius: 0 },39 ],40 [41 { point: [17, 8.6], radius: 0 },42 { point: [17, 27.4], radius: 0 },43 { point: [33, 18], radius: 0 },44 { point: [33, 18], radius: 0 },45 ],46];4748const PAUSE_SHAPES: readonly [Shape, Shape] = [49 [50 { point: [8.5, 4.5], radius: 2.5 },51 { point: [8.5, 31.5], radius: 2.5 },52 { point: [15.5, 31.5], radius: 2.5 },53 { point: [15.5, 4.5], radius: 2.5 },54 ],55 [56 { point: [20.5, 4.5], radius: 2.5 },57 { point: [20.5, 31.5], radius: 2.5 },58 { point: [27.5, 31.5], radius: 2.5 },59 { point: [27.5, 4.5], radius: 2.5 },60 ],61];6263// A morph is only a lerp while both icons keep the same corner count.64if (65 process.env.NODE_ENV !== "production" &&66 PLAY_SHAPES.some((shape, index) => shape.length !== PAUSE_SHAPES[index].length)67) {68 throw new Error("play-button: play and pause shapes must share a corner count.");69}7071function lerp(from: number, to: number, progress: number) {72 return from + (to - from) * progress;73}7475function round(value: number) {76 return Math.round(value * 100) / 100;77}7879function coords([x, y]: Point) {80 return `${round(x)} ${round(y)}`;81}8283/**84// … truncated
What it pulls in
npm packages
Files it writes
More from ericts-ui
All 33 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Adaptive Draweradaptive-drawer | ericts-ui | Components | Free | 2 deps | |
| Check Animationcheck-animation | ericts-ui | Components | Free | no deps · 2 files | |
| Context Cursorcontext-cursor | ericts-ui | Components | Free | 1 dep | |
| Copy Buttoncopy-button | ericts-ui | Components | Free | 2 deps | |
| Expandable Dialogexpandable-modal | ericts-ui | Components | Free | 1 dep | |
| Expandable Tabsexpandable-tabs | ericts-ui | Components | Free | 1 dep | |
| Expandable Toolbarexpandable-toolbar | ericts-ui | Components | Free | 1 dep | |
| Expanding Panelexpanding-panel | ericts-ui | Components | Free | 2 deps |
