component 24 / 28WORKING
SC Image Lightbox
- type
- Code Component
- platform
- Framer
- license
- SecretoCho Design
demo · runs with JavaScript
One-image lightbox: themed thumbnail with overlay icon, portal dialog with zoom, pan, pinch, fullscreen and keyboard, scroll lock that restores position.
ScImageLightbox is a single image that opens full-screen. The thumbnail keeps an aspect ratio, radius and shadow and shows a dashed overlay icon; a click, Enter or Space opens a portal dialog with the alt text, zoom buttons and read-out, fullscreen and close. The image zooms to 3× by buttons, keys, ctrl/cmd + wheel or pinch, pans by drag when zoomed, and the page behind stays put.
- Media
- UI
Main
Overview
- Built With
- React (hooks), react-dom createPortal, framer-motion, Fullscreen API, Framer Code Component API
- Layout Support
- Any size (prefers fixed); optional aspect ratio on the thumbnail
- Orientation
- Thumbnail in place; lightbox fixed over the page with a top bar and bottom hints
- Ideal Use Case
- Blog images, case-study screenshots, any image that deserves a closer look
Tags
Code comment
// ScImageLightbox_Optimized.tsx - SecretoCho™ Image Lightbox Component// Optimized version with simplified layout and better performance// ready_to_ship=trueDependencies
- Technologies
- framer-motion, react-dom (both in the Framer runtime)
- Notes
- Takes a Framer Responsive Image (src, srcset, alt).
Complexity
- Logic Level
- Medium (open/close with scroll lock, zoom and drag maths, pinch, keyboard, fullscreen)
- Runtime Cost
- Low (the dialog renders only while open; transitions by framer-motion)
- Component Size
- ~48 KB TSX (48,171 bytes, 1,129 lines)
- Notes
- Device checks (width < 768, touch) are measured once on mount.
Guide
Instructions
- Drop ScImageLightbox into a frame and set the Image (with alt text).
- Pick an Aspect Ratio (Auto, 1:1, 16:9, 2:1, 3:4), Border Radius, Box Shadow and Container Background.
- Overlay Icon: show, position (corners or centre) and icon (expand, zoom, eye, plus, arrows).
- Lightbox Settings: background, backdrop blur, alt text, the close / zoom / fullscreen buttons, and the fade / scale / slide animation.
Examples / Presets
Default
The source defaults: auto ratio, radius 8, expand icon top right, dark blurred lightbox, scale animation, all three buttons.
As on the live site
- borderRadius
- 0
- overlay.position
- top-left
- lightbox.backdropBlur
- true (the old site’s eight app case-study instances; their W-B 80 % frame and page-colour lightbox background come from the demo module)
Centre zoom icon
- aspectRatio
- video (16:9)
- borderRadius
- 16
- overlay.position
- center
- overlay.icon
- zoom
- lightbox.animation
- fade
Slide, no controls
- aspectRatio
- square
- overlay.position
- bottom-right
- overlay.icon
- plus
- lightbox.animation
- slide
- lightbox.showAltText
- false
- lightbox.backdropBlur
- false
- controls.zoom
- false
- controls.fullscreen
- false
Mechanics
How it Works
- Thumbnail: a motion.div with role="button", tabIndex 0 and an aria-label; hover scales 1.02, tap 0.98; the lazy-loaded <img> covers it and the overlay icon fades in once the image has loaded.
- Open: window.scrollY is stored and the body set to position: fixed, top −scrollY, width 100% and overflow-y: scroll; the dialog (role="dialog", aria-modal) is rendered through createPortal into document.body.
- Close: Esc or the close button resets zoom, drag and the fullscreen flag, clears the body styles and scrolls back to the stored position. The overlay has a click-to-close handler, but the full-size image layer and both bars stop the click, so a click or tap on the backdrop does nothing. Closing is instant: the portal unmounts before the exit fade can run.
- Zoom: 0.5–3× in 0.25 steps (buttons, + / = and − / _ keys, 0 to reset, ctrl/cmd + wheel); pinch sets zoom from the finger distance ratio; above 1× mousedown / touch drags the image within offsets clamped to (zoom − 1) × container / 2.
- Fullscreen: F or the button calls requestFullscreen on the dialog and exitFullscreen to leave.
- Hints: the bottom bar shows “ESC: Close • +/-: Zoom” (plus “Drag to pan” above 1×) on desktop, and “Pinch to zoom • Tap outside to close” (“Drag to pan • Pinch to zoom” above 1×) when the screen is under 768 px or has touch, plus the zoom percentage.
Performance & Tuning
- The thumbnail is lazy-loaded; the dialog mounts only when open and unmounts on close.
- Backdrop Blur off removes only the overlay’s 12 px blur (and softens the image shadow); the top bar (20 px), the bottom bar (10 px) and the buttons (8 px) always blur.
- Drag updates set state on every mousemove; transitions are disabled while dragging to stay smooth.
Compatibility & Limits
- Evergreen browsers; the unprefixed Fullscreen API with no feature check or error handling, so where it is missing or refused the call fails; framer-motion and react-dom come with Framer.
- isMobile (width < 768) and hasTouch are measured once on mount; zoom and fullscreen buttons are hidden on mobile.
- Icon SVGs are inline strings from the file.
Quality
Accessibility
- The thumbnail is keyboard-operable (role="button", Enter / Space) with aria-label “Open lightbox for <alt>”; the dialog has role="dialog" and aria-modal="true"; every button has an aria-label.
- Esc closes; + / = and − / _ zoom, 0 resets, F toggles fullscreen, whenever the dialog is open, even when those buttons are hidden or switched off.
- Focus is not moved into the dialog or trapped, and a click or tap on the backdrop does not close it; the alt text is shown in the top bar when Show Alt Text is on.
Security & Privacy
- Network: only the image’s own src / srcset (the built-in default is a framerusercontent.com image with the alt “SecretoCho Design”). No fetch, no tracking.
- Permissions: the Fullscreen API on a click or the F key; nothing else.
- No storage. dangerouslySetInnerHTML is used only for the file’s own inline SVG icon strings.
- A document keydown listener stays attached while the component is mounted (it ignores keys while closed); mousemove / mouseup / touchmove / touchend listeners are added only while dragging and removed after. The body’s position, top, width and overflow-y are cleared on close, but not if the component unmounts while open.
Reference
Props
| name | type | default | description |
|---|---|---|---|
image | ResponsiveImage | a framerusercontent.com image, alt “SecretoCho Design” | Image. src, srcset and alt. |
aspectRatio | enum | auto | Aspect Ratio. Auto | 1:1 (square) | 16:9 (video) | 2:1 (wide) | 3:4 (tall). |
borderRadius | number | 8 | Border Radius 0–50 px on the thumbnail. |
boxShadow | string | 0 4px 14px rgba(0,0,0,.08) | Box Shadow. A CSS shadow string. |
containerBackground | color | #FAFAFA | Container Background behind the thumbnail. |
overlay | object | show=true, position=top-right, icon=expand | Overlay Icon. Show Icon; Top Right | Top Left | Bottom Right | Bottom Left | Center; Expand | Zoom | Eye | Plus | Arrows. |
lightbox | object | backgroundColor=rgba(0, 0, 0, 0.95), backdropBlur=true, showAltText=true, controls.close=true, controls.zoom=true, controls.fullscreen=true, animation=scale | Lightbox Settings. Background, Backdrop Blur, Show Alt Text, Controls (Close, Zoom, Fullscreen), Animation Fade | Scale | Slide. |
The close button hides only when controls.close is explicitly false; zoom and fullscreen buttons are also hidden on screens under 768 px regardless of the controls.
Notes & Tips
- Always set alt: it labels the thumbnail button and the top bar.
- Auto adds no ratio: the thumbnail fills its frame and crops the image to it (object-fit: cover), so size the frame to the image to avoid cropping; at 1× the lightbox shows the whole image inside the viewport.
- Fade scales 0.95 → 1 over 0.25 s, Scale 0.8 → 1 over 0.3 s, and Slide rises 20 px over 0.3 s; all three fade in.
Troubleshooting / FAQ
The overlay icon never appears
It shows after the thumbnail’s onLoad; a blocked or failed image keeps it hidden.
No zoom buttons on a phone
Hidden under 768 px; pinch zoom works.
Fullscreen does nothing
The browser refused or lacks requestFullscreen: the call is unprefixed, with no fallback or error handling. Under 768 px the button is not shown at all.
The page jumps when the lightbox opens
The body gets position: fixed with overflow-y: scroll to keep the scrollbar; a layout that depends on body position will shift.
The fullscreen icon is wrong after leaving fullscreen
Nothing listens for fullscreenchange, so leaving fullscreen another way (the browser’s own Esc) leaves the exit icon in place.
Source
Header comment
// ScImageLightbox_Optimized.tsx - SecretoCho™ Image Lightbox Component// Optimized version with simplified layout and better performance// ready_to_ship=trueKey excerpt
Opening fixes the body at its current scroll offset so the page cannot scroll behind the dialog; closing clears those styles and scrolls back to the exact position.
const handleOpen = useCallback(() => { if (typeof window === "undefined") return const scrollY = window.scrollY scrollPositionRef.current = scrollY setIsOpen(true) document.body.style.position = "fixed" document.body.style.top = `-${scrollY}px` document.body.style.width = "100%" document.body.style.overflowY = "scroll" }, []) const handleClose = useCallback(() => { setIsOpen(false) setZoomLevel(1) setIsFullscreen(false) setDragOffset({ x: 0, y: 0 }) setIsDragging(false) const scrollY = scrollPositionRef.current document.body.style.position = "" document.body.style.top = "" document.body.style.width = "" document.body.style.overflowY = "" if (typeof window !== "undefined" && scrollY !== undefined) { window.scrollTo(0, scrollY) } }, [])Credits
Credits
© SecretoCho Design
- license
- SecretoCho Design
SecretoCho Design
Disclaimer
Provided “as is” for visual/UX purposes. Validate performance, accessibility, and licensing within your project.
Proves
Hiring a design engineer?
I’m open to senior design engineer roles: remote from Belgrade, or relocation with visa sponsorship. Write to me, or read the CV first. Client work stays selective, one project at a time.
sc@secretocho.design