Prompted by a question from a 2nd year student, I was inspired to explore the visual effect you can see above. Variations on this transition have long been a mainstay of JQuery carousels; I was interested in working out if it could be achieved in pure CSS, with the condition that the result should also be responsive: most JavaScript solutions are not.
It’s certainly doable, albeit with a bit of markup: the solution requires as many copies of the image as we would like “shutters” at the bitmap’s maximum dimensions. In this case, I wanted 10 vertical “blinds” for images that would have a maximum width of 1000 pixels. This required 10 copies of each photograph, arranged inside a <figure>
container. At the same time, I needed 10 copies of the alternate image on the reverse side. Each image set would have a different class:
<figure id="blinds">
<img src="autumn-face.jpg" alt class="first">
<img src="autumn-face.jpg" alt class="first">
…
<img src="autumn-face.jpg" alt class="first">
…
<img src="julia.jpg" alt class="second">
<img src="julia.jpg" alt class="second">
…
<img src="julia.jpg" alt class="second">
</figure>
In reality, only two images will be loaded, with copies duplicated in the browser. All images are exactly the same size, in the same absolute position. The <figure>
element will be 100% of the width of its container, with the correct height provided by using the same trick I use for responsive video:
#blinds {
margin: 0;
position: relative;
padding-bottom: 56.5%;
perspective: 1800px;
transform-style: preserve-3d;
max-width: 1000px;
}
#blinds img {
top: 0; left: 0;
position: absolute;
transition: 1s;
}
Every copy of the second image is rotated 180 degrees and placed behind the others by using a slight translate
value:
#blinds img.first {
transform: rotateY(0deg);
}
#blinds img.second {
transform: rotateY(-180deg) translateZ(1px);
}
To create the slices, I use clip
, with nth-child
as a selector. As you can see, both image’s clip
left and right values increase by 100 after each iteration:
#blinds img:nth-child(1),
#blinds img:nth-child(11) {
clip: rect(0px, 100px, 840px, 0px);
}
#blinds img:nth-child(2),
#blinds img:nth-child(12) {
clip: rect(0px, 200px, 840px, 100px);
}
#blinds img:nth-child(3),
#blinds img:nth-child(13) {
clip: rect(0px, 300px, 840px, 200px);
}
…
#blinds img:nth-child(10n) {
clip: rect(0px, 1000px, 840px, 900px);
}
Even after using clip
, the image’s transform-origin
– the point around which the image will be transformed – remains the center of the element. To counter that, we set the transform-origin
for each slice so that it is at the halfway point. Think of the position of the central axes in a set of vertical blinds:
transform-origin
for clipped “venetian blind” images
In CSS:
#blinds img:nth-child(1),
#blinds img:nth-child(11) {
clip: rect(0px, 100px, 840px, 0px);
transform-origin: 50px 0px;
}
#blinds img:nth-child(2),
#blinds img:nth-child(12) {
clip: rect(0px, 200px, 840px, 100px);
transform-origin: 150px 0px;
}
#blinds img:nth-child(3),
#blinds img:nth-child(13) {
clip: rect(0px, 300px, 840px, 200px);
transform-origin: 250px 0px;
}
…
#blinds img:nth-child(10n) {
clip: rect(0px, 1000px, 840px, 900px);
transform-origin: 950px 0px;
}
Then we can rotate the image sets 180° on :hover
, distinguished by their class
:
#blinds:hover img.first {
transform: rotateY(180deg);
}
#blinds:hover img.second {
transform: rotateY(0deg) translateZ(1px);
}
As written, all the “blinds” will rotate at the same time. To create a “ripple” effect, we delay the transition of each slice:
#blinds img:nth-child(1),
#blinds img:nth-child(11) {
clip: rect(0px, 100px, 840px, 0px);
transform-origin: 50px 0px;
}
#blinds img:nth-child(2),
#blinds img:nth-child(12) {
clip: rect(0px, 200px, 840px, 100px);
transform-origin: 150px 0px;
transition-delay: 100ms;
}
#blinds img:nth-child(3),
#blinds img:nth-child(13) {
clip: rect(0px, 300px, 840px, 200px);
transform-origin: 250px 0px;
transition-delay: 200ms;
}
…
#blinds img:nth-child(10n) {
clip: rect(0px, 1000px, 840px, 900px);
transform-origin: 950px 0px;
transition-delay: 900ms;
}
One nice feature of using clip
is that the result is automatically responsive: if the image narrows, the number of slices is reduced also. Try narrowing your browser window: if the viewport is 500px wide, the images are animated as five even slices.
Alternative Approaches
The repetitive nature of the code cries out for a more efficient approach: either a pre-processor, or some JavaScript to automatically create the CSS. I’ll tackle both approaches in the very near future. For right now, this is a good start, although not yet perfect: due to browser rendering issues, it’s possible to see the occasional “kinked” or overlapping image slice during the transition.
Photographs by Sean Archer and Ila PhotoGraphic, used with permission.
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/HrFBx