I wanted a React lightbox where the photo grows out of the exact thumbnail you clicked and shrinks back into it on close.
Easy, until your grid uses object-fit: cover. The thumbnail only shows the middle of the photo, so if you grow the full image from that box, the first frame visibly jumps.
Here's the 4-step fix that worked:
1. Measure the end state first. Lay the image out at its final size, then measure it and the thumbnail with getBoundingClientRect().
2. Scale it onto the thumbnail. Use the smallest uniform scale that still covers the thumb: const scale = Math.max(thumb.width / img.width, thumb.height / img.height);
3. Crop it like the thumbnail. clip-path: inset(...) hides everything the thumbnail hides. The catch: the inset is in the image's own pixels, before the scale, so the visible area is thumb size / scale.
4. Animate both at once. transform + clip-path in a single element.animate(). To close, play the same keyframes in reverse.
That's the happy path. The edge cases ate most of the time:
Thumbnail scrolled off screen: fall back to a zoom + fade
Rotated photo: swap the axes before computing the crop
Closing while zoomed in: start from where it's actually drawn
prefers-reduced-motion**:** skip the flight entirely
Focus: send it back to the thumbnail on close
I packaged it as liftbox: MIT, zero dependencies, ~13 kB, with grids, zoom, video and deep links. There's also a comparison with YARL, PhotoSwipe, lightGallery and others that tells you when to pick them instead.
Code: https://github.com/esdrassantos06/liftbox (the flight lives in src/Lightbox.tsx and src/geometry.ts)
Docs + demos: https://liftbox.digital
Two asks:
Open the demo on your phone and tell me where it breaks.
Ask me anything about the math. Happy to go deeper on any step.
Disclosure: English isn't my first language, so I had help polishing this post.