SecretoCho
menu

component 24 / 28WORKING

SC Image Lightbox

type
Code Component
platform
Framer
license
SecretoCho Design
needs JavaScript

demo · runs with JavaScript

fig 00 · live demo, ported from ScImageLightbox.tsx

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

  • lightbox
  • image
  • zoom
  • portal
  • pinch
  • fullscreen
  • dialog
  • framer-motion

Code comment

ScImageLightbox.tsx · lines 1–3 · the file’s header comment
// ScImageLightbox_Optimized.tsx - SecretoCho™ Image Lightbox Component// Optimized version with simplified layout and better performance// ready_to_ship=true

Dependencies

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

  1. Drop ScImageLightbox into a frame and set the Image (with alt text).
  2. Pick an Aspect Ratio (Auto, 1:1, 16:9, 2:1, 3:4), Border Radius, Box Shadow and Container Background.
  3. Overlay Icon: show, position (corners or centre) and icon (expand, zoom, eye, plus, arrows).
  4. 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

nametypedefaultdescription
imageResponsiveImagea framerusercontent.com image, alt “SecretoCho Design”Image. src, srcset and alt.
aspectRatioenumautoAspect Ratio. Auto | 1:1 (square) | 16:9 (video) | 2:1 (wide) | 3:4 (tall).
borderRadiusnumber8Border Radius 0–50 px on the thumbnail.
boxShadowstring0 4px 14px rgba(0,0,0,.08)Box Shadow. A CSS shadow string.
containerBackgroundcolor#FAFAFAContainer Background behind the thumbnail.
overlayobjectshow=true, position=top-right, icon=expandOverlay Icon. Show Icon; Top Right | Top Left | Bottom Right | Bottom Left | Center; Expand | Zoom | Eye | Plus | Arrows.
lightboxobjectbackgroundColor=rgba(0, 0, 0, 0.95), backdropBlur=true, showAltText=true, controls.close=true, controls.zoom=true, controls.fullscreen=true, animation=scaleLightbox 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.tsx · lines 1–3 · header comment
// ScImageLightbox_Optimized.tsx - SecretoCho™ Image Lightbox Component// Optimized version with simplified layout and better performance// ready_to_ship=true

props [REF]

Key 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.

ScImageLightbox.tsx · lines 243–271 · tsx
    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

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

read the CV · LinkedIn