Back to experiments
Solid Ripple Button
- ui
- solid.js
- tailwind
Ripple button in Solid.js with Tailwind
Tailwind configtsx{theme: {extend: {animation: {ripple: "ripple 0.8s ease-out forwards",},keyframes: {ripple: {"0%": {transform: "scale(1)",opacity: 1},"60%": {transform: "scale(var(--scale))"},"100%": {transform: "scale(var(--scale))",opacity: 0}}}}}};
tsxconst RIPPLE_RADIUS = 8;const Button: Component<ComponentProps<"button">> = (props) => {const [local, others] = splitProps(props, ["children"]);let ref: HTMLButtonElement | undefined;const [ripples, setRipples] = createStore<{[key: string]: { x: number; y: number; scale: number; visible: boolean };}>({});function createRipple(event: MouseEvent) {if (!ref) return;const rect = ref.getBoundingClientRect();let x: number, y: number;// Keyboardif ((event as PointerEvent).pointerType === "") {x = rect.width / 2;y = rect.height / 2;} else {x = event.clientX - rect.left;y = event.clientY - rect.top;}const radius = Math.sqrt(Math.max(x, rect.width - x) ** 2 + Math.max(y, rect.height - y) ** 2);const scale = radius / RIPPLE_RADIUS;setRipples(createUniqueId(), { x, y, scale, visible: true });}function rippleEnd(event: AnimationEvent) {if (event.animationName !== "ripple" || !event.target) return;const target = event.target as HTMLSpanElement;setRipples(produce((draft) => {delete draft[target.id];}));}return (<buttonclass="relative overflow-hidden bg-zinc-900 px-12 py-3 font-brand font-medium ring-1 ring-inset ring-cyan-100/5 transition hover:bg-zinc-800 hover:ring-cyan-100/10"ref={ref}onClick={createRipple}{...others}>{local.children}<For each={Object.keys(ripples)}>{(key) => {const ripple = () => ripples[key];return (<Show when={ripple().visible}><spanid={key}onAnimationEnd={rippleEnd}style={{top: `${ripple().y - RIPPLE_RADIUS}px`,left: `${ripple().x - RIPPLE_RADIUS}px`,width: `${RIPPLE_RADIUS * 2}px`,height: `${RIPPLE_RADIUS * 2}px`,"--scale": ripple().scale,}}class="absolute animate-ripple rounded-full bg-cyan-500/30 mix-blend-screen"/></Show>);}}</For></button>);}