Lens Text
loomui/lens-textFreeComponent
Text held out of focus until the pointer passes over it like a magnifying glass.
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/lens-text.jsonSource
1"use client"23import * as React from "react"45import { cn } from "@/lib/utils"67export interface LensTextProps extends React.ComponentProps<"span"> {8 /** Blur radius of the resting text, in pixels. */9 blur?: number10 /** Diameter of the lens in pixels. */11 size?: number12 /** How much of the lens fades out at its edge, from `0` to `1`. */13 feather?: number14 /** Scale applied to the text under the lens. `1` is a plain focus window. */15 magnify?: number16 /** Corner radius of the frosted panel. Any CSS length; `em` scales with the type. */17 radius?: string18 /**19 * Milliseconds the lens takes to catch up to the pointer, and the pace it20 * opens and closes at. `0` locks it to the pointer.21 */22 follow?: number23 /** Draw a hairline ring around the lens. */24 ring?: boolean25 /** Render the text sharp, with no blur and no lens. */26 disabled?: boolean27}2829/**30 * A hovering pointer is the only way to aim the lens, so anything else (touch,31 * pen, keyboard-only) gets the text sharp rather than never getting it at all.32 */33const HOVER_QUERY = "(hover: hover) and (pointer: fine)"34const MOTION_QUERY = "(prefers-reduced-motion: reduce)"3536/** The lens opens slower than it tracks, or it pops on the way in. */37const OPEN_FACTOR = 1.63839/**40 * Room for the blur to fall off inside the panel rather than at its edge.41 * In `em` so it tracks the type it is wrapped around.42 */43const PANEL_PADDING = "0.28em 0.45em"4445function useMediaQuery(query: string, fallback: boolean) {46 // Server and first client paint agree on `fallback`. The effect corrects it47 // before anyone can interact, so there is no hydration mismatch.48 const [matches, setMatches] = React.useState(fallback)4950 React.useEffect(() => {51 const list = window.matchMedia(query)52 const sync = () => setMatches(list.matches)5354 sync()55 list.addEventListener("change", sync)56 return () => list.removeEventListener("change", sync)57 }, [query])5859 return matches60}6162function clamp01(value: number) {63 return Math.min(Math.max(value, 0), 1)64}6566export function LensText({67 children,68 className,69 blur = 8,70 size = 110,71 feather = 0.5,72 magnify = 1,73 radius = "0.35em",74 follow = 130,75 ring = false,76 disabled = false,77 style,78 ...props79}: LensTextProps) {80 const ref = React.useRef<HTMLSpanElement>(null)81 const frame = React.useRef(0)82 const running = React.useRef(false)83 const hasHover = useMediaQuery(HOVER_QUERY, true)84 const reduceMotion = useMediaQuery(MOTION_QUERY, false)8586// … truncated
What it pulls in
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 |
