Magnetic
loomui/magneticFreeComponent
A wrapper that pulls its child toward the pointer as the pointer gets close.
Live preview
live · rendered by the registry
Rendered by loomui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://loomui.design/r/magnetic.jsonSource
1"use client"23import * as React from "react"4import { Slot } from "@radix-ui/react-slot"56import { cn } from "@/lib/utils"78export interface MagneticProps extends React.ComponentProps<"div"> {9 /** How far the child follows the pointer, as a fraction of the offset. */10 strength?: number11 /** Pixels of slack around the element that still count as near. */12 radius?: number13 /** Largest pull in pixels, whatever the radius says. */14 max?: number15 /** Merge onto the single child instead of rendering a wrapper div. */16 asChild?: boolean17 /** Render the child with no pull. */18 disabled?: boolean19}2021export function Magnetic({22 children,23 className,24 strength = 0.35,25 radius = 80,26 max = 16,27 asChild = false,28 disabled = false,29 style,30 ...props31}: MagneticProps) {32 const ref = React.useRef<HTMLDivElement>(null)33 const frame = React.useRef(0)34 const Comp = asChild ? Slot : "div"3536 const reset = React.useCallback(() => {37 cancelAnimationFrame(frame.current)38 const node = ref.current39 if (node) {40 node.style.translate = "0px 0px"41 }42 }, [])4344 React.useEffect(() => {45 if (disabled) {46 reset()47 return48 }4950 const reduced = window.matchMedia("(prefers-reduced-motion: reduce)")51 if (reduced.matches) {52 return53 }5455 // Listening on the window rather than the element itself means the pull56 // starts before the pointer arrives, which is the whole effect.57 const onPointerMove = (event: PointerEvent) => {58 cancelAnimationFrame(frame.current)59 frame.current = requestAnimationFrame(() => {60 const node = ref.current61 if (!node) {62 return63 }6465 const rect = node.getBoundingClientRect()66 const dx = event.clientX - (rect.left + rect.width / 2)67 const dy = event.clientY - (rect.top + rect.height / 2)68 const near =69 Math.abs(dx) < rect.width / 2 + radius &&70 Math.abs(dy) < rect.height / 2 + radius7172 if (!near) {73 node.style.translate = "0px 0px"74 return75 }7677 const clamp = (value: number) =>78 Math.max(-max, Math.min(max, value * strength))79 node.style.translate = `${clamp(dx)}px ${clamp(dy)}px`80 })81 }8283 window.addEventListener("pointermove", onPointerMove, { passive: true })84 return () => {85 window.removeEventListener("pointermove", onPointerMove)86 cancelAnimationFrame(frame.current)87 }88 }, [disabled, radius, max, strength, reset])8990// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from loomui
All 91 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Aurora Backdropaurora-backdrop | loomui | Components | Free | no deps | |
| Bento Gridbento-grid | loomui | Components | Free | no deps | |
| Card Stackcard-stack | loomui | Components | Free | no deps | |
| Compare Slidercompare-slider | loomui | Components | Free | no deps | |
| Confetti Buttonconfetti-button | loomui | Components | Free | no deps | |
| Count Upcount-up | loomui | Components | Free | no deps | |
| Credit Cardcredit-card | loomui | Components | Free | no deps | |
| Drawerdrawer | loomui | Components | Free | 1 dep |
