Photo Stamp
loomui/photo-stampFreeComponent
A photo that lifts off the page at full size over a blurred backdrop, the same element the whole way.
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/photo-stamp.jsonSource
1"use client"23import * as React from "react"4import { createPortal } from "react-dom"56import { cn } from "@/lib/utils"7import { useSpring, type SpringOptions } from "@/registry/lib/use-spring"89export interface PhotoStampProps extends Omit<10 React.ComponentProps<"figure">,11 "children"12> {13 /** The photo. Ship the full-size file: the open state is its real size. */14 src: string15 /** Describes the photo. It is also the trigger's accessible name. */16 alt: string17 /** Sits under the photo, in both states. */18 caption?: React.ReactNode19 /** Corner radius. Held steady in screen pixels through the lift. */20 radius?: number21 /** How much of the viewport the open photo fills, `0` to `1`. */22 fill?: number23 /** How the photo travels. */24 spring?: SpringOptions25 /** Sizes the photo at rest. The open size is worked out from it. */26 imageClassName?: string27}2829interface Box {30 x: number31 y: number32 width: number33 height: number34}3536interface Geometry {37 /** Where the photo sits on the page. */38 from: Box39 /** Where it is going. */40 to: Box41 /** `from` over `to`. One number, because the two boxes share an aspect. */42 scale: number43}4445/** Room kept under the open photo for the caption. */46const CAPTION_ROOM = 4447/** An exit wants to be quicker than the entrance it undoes. */48const EXIT_RATE = 0.849/** Keys that scroll the page out from under an open photo. */50const SCROLL_KEYS = new Set([51 " ",52 "PageUp",53 "PageDown",54 "Home",55 "End",56 "ArrowUp",57 "ArrowDown",58 "ArrowLeft",59 "ArrowRight",60])6162/**63 * The open box, fitted to the viewport at the aspect the photo already has on64 * the page. Matching the rendered aspect rather than the source file's means65 * the crop never changes on the way up, so one scale covers both axes and66 * nothing has to be un-distorted afterwards.67 */68function fit(from: Box, fill: number, room: number): Box {69 const maxWidth = window.innerWidth * fill70 const maxHeight = window.innerHeight * fill - room71 const aspect = from.width / from.height7273 let width = maxWidth74 let height = width / aspect7576 if (height > maxHeight) {77 height = maxHeight78 width = height * aspect79 }8081 return {82 x: (window.innerWidth - width) / 2,83 y: (window.innerHeight - room - height) / 2,84 width,85 height,86 }87}8889function measure(node: HTMLElement): Box {90 const rect = node.getBoundingClientRect()91 return { x: rect.x, y: rect.y, width: rect.width, height: rect.height }92}9394function usePrefersReducedMotion() {95// … 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 |
