Morphing Images with Lenticular Printing: Illusions on the Web Part 4

This is the fourth in an article series discussing different optical illusions & mechanical toys and how we can recreate them on the web (and learn from them). The end result of this article can be seen as a standalone, fullscreen demo or with the code and result side by side.

Perhaps you’ve seen them when you collected trading cards in the 90s, more recently seen them on Bluray covers, or have them on that Best Buy gift card that you never used. As your viewing angle changes (or you physically rotate the item), an image morphs into a new one, a 3D effect might be achieved, or a small animation may occur. Sometimes referred as tilt cards or holograms, the approach we are recreating today is more formally associated with Lenticular printing.

Lenticular Printing 

The way these physical cards are made involves a special printing surface with thin rows of lenses that affect what you see from a given angle. When you see one up close it is clear there are a series of lines that create the magic, and depending on your angle you might be able to see chopped versions of multiple images at a given time.

When moving this to the digital realm, we don’t really have a concept of the lens to affect what you see. But the web does have ways to split an image into smaller chunks and layers of 3d transforms to affect our viewing angle.

See the Pen Lenticular Card: Noninteractive by Dan Wilson (@danwilson) on CodePen.

Splitting the images 

I am exploring the morphing between two images in this example. Starting with two images on top of each other, we then split both of them into tiny strips three pixels wide. I usually go with viewport units for something like this so that the central demo will scale nicely, but in keeping track of the width of the strips, I opted for fixed pixels everywhere. Let’s call this container that holds the two images our "card."

We can split each image a number of ways, but here it is structured as an unordered list and each strip is a list item. Since there are only two images, we can take advantage of each list item’s pseudoelements. Each list item in the ::before sets one of the images as its background, and the other image is similarly set in the ::after. Custom Properties (CSS Variables) then allow us to offset the background-position for each list item so that the result is the full image.

See the Pen Lenticular Card: Split Images by Dan Wilson (@danwilson) on CodePen.

This is a fairly manual approach to show what all is happening. To keep things cleaner we could use Pug or another templating engine to simplify the markup and assign an index as a Custom Property that we could use to drive the background-position. Also, there is the new Splitting.js library that generates elements and helpful Custom Properties from a single element with an image.

Showing both images at the same time 

Now that we have two images split into thin columns and on top of each other we can start working with angles and perspective. With a perspective set on the body, and a transform-style: preserve-3d down through the ul and each li we can know that all our 3D rotations will work in relation to each other (inside the same 3D space).

Since we do not have a direct concept of the fancy thin lenses in our browser, we will try to achieve something similar by rotating each strip 60 degrees around the Y axis away from us (with each pseudoelement rotating from opposite sides of the strip). We can make a blended photo with little triangles where a little of each image strip is seen at a given time.

See the Pen Lenticular Cards: Zoomed by Dan Wilson (@danwilson) on CodePen.

li::before, li::after {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
}
li::before {
background-image: url(https://images.unsplash.com/photo-1473580044384-7ba9967e16a0?ixlib=rb-0.3.5&q=85&fm=jpg&crop=entropy&cs=srgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ&s=53a6022c048f8595d90636e94b7fabf3);
transform: rotateY(-60deg);
transform-origin: 100% 50%;
}
li::after {
background-image: url(https://images.unsplash.com/photo-1522735555435-a8fe18da2089?ixlib=rb-0.3.5&q=85&fm=jpg&crop=entropy&cs=srgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ&s=d404d2ca601ddb2b3b33c4e3f41291b4);
transform: rotateY(60deg);
transform-origin: 0% 50%;
}

Tilting the image 

Now that we have our base card constructed (such that a little of each image is shown when we look at the card straight on) we can talk about perspective. Our perspective-origin is set to be in the center of the body, and as we rotate the card around the Y axis at its center we can achieve our morphing image effect. As we rotate in one direction more of image A is covered up, revealing more of image B. As we switch and rotate in the other direction image B gets more covered up, showing more of image A.

We are only moving the card itself. There are many transforms used to create the structure of the card, but the only element that moves is our card (the unordered list).

In between we even get a kind of sheen effect because of all the strips fighting for attention and the renderer trying to get the right pixels to display. Our final images are not perfect (you can see artifacts and some lines between strips), but the same is true when printed in the physical world.

See the Pen Lenticular Card by Dan Wilson (@danwilson) on CodePen.

You can use mouse/stylus/touch if your browser supports Pointer Events (sorry, Safari users) to rotate the card by dragging horizontally.

You can also load the full demo up outside of an iframe to use on a device supporting deviceorientation events (you are welcome, Safari users). It is best to lock the device orientation.

What are the more... straightforward options? 

There are several ways to swap between two images. One could overlay two img elements and fade the top one in and out with an opacity animation.

See the Pen Lenticular Card: But Not by Dan Wilson (@danwilson) on CodePen.

Or in certain browsers you can explore doing something similar directly with layered background images. There is a cross-fade function that can let you have two images mixed together such that the top one has a partial opacity, but its support is limited to earlier specs and Webkit currently.