Thumbnail to Full Width Image Animation

The other day I stumbled upon this fantastic animation by Akram Khalid which he also coded up as part of a tutorial on page transitions with React Router and Framer Motion. The GitHub repo can be found here. It’s a really beautiful design and I wanted to have a go on experimenting with it and animating the initial thumbnail view to a full image (with article), using only scale transforms.

I also wanted to add some smooth scrolling and on-scroll animations, so I’ve used Locomotive Scroll. The beautiful images are by DeMorris Byrd.

This is highly experimental and it turned out to be a complex process. But I hope it gives you some of sort idea and entry point of how to pull off these kind of animations without touching the width and height of an element.

The main idea behind this technique is to scale an element and then counter-scale the child. Paul Lewis and Stephen McGruer show how to do that on a menu using expand and collapse animations. Avoiding animating the width and height of an element helps keep performance in check.

So what we do is to initially set the scale of the content__intro wrapper to a value that will make it shrink to an exact size. Then we set a counter scale to the image. This will make the image maintain the same size as before. Then, we add another scale to the image, shrinking it also the to the target size.

<div class="content__intro content__breakout">
	<img class="content__intro-img" src="img/1.jpg" alt="Some image" />
</div>

Having the initial width and height of an element and also the target dimensions, we can calculate the scale values of the outer wrapper based on this:

let introTransform = {
    scaleX: imageSettings.imageWidthEnd / imageSettings.imageWidthStart,
    scaleY: imageSettings.imageHeightEnd / imageSettings.imageHeightStart,
    y: (winsize.height/2 - introRect.top) - introRect.height/2
};

We also move the element to be centered on the screen (y).

We define these initial (start) and target (end) dimensions as variable in our CSS:

body {
	...
	--image-height-start: 555px;
	--image-width-end: 260px;
	--image-height-end: 320px;
}

Our starting width is 100% of the viewport width, so we don’t need to set that here. The image will then have the following scale applied:

gsap.set(this.DOM.introImg, {
    scaleX: 1/introTransform.scaleX * imageSettings.imageWidthEnd / this.DOM.introImg.clientWidth,
    scaleY: 1/introTransform.scaleY * imageSettings.imageHeightEnd / this.DOM.introImg.clientHeight
});

1/introTransform.scaleX is the counter scale of the outer wrapper. The second value that we multiply makes sure that we scale the image down to our desired size, just like we did with the outer wrapper before.

And that’s the main idea behind the scaling magic.

I hope this gives you a starting point for these kind of tricky animations! Thank you for checking it out 🙂