Friday, January 17, 2025

Using a Different Technique for CSS3 Photo Stacking

A while back, about when Google+ first went into beta, I found some code that showed how to recreate Google’s photo stacking technique. That turned out to be a pretty popular article.

As a side note, if you’ve been paying attention at home, Google has scaled back the use of the photo stacking fan effect on Google+. It looks like they only use it for pictures from your posts, and that’s only if you have more than one photo per post. I’m not sure what that says about the feedback Google received on the fan effect, or if they thought a tiled approach was more useful.

Regardless, today I found another photo stacking technique that you might like. It’s a different take from Google’s original photo stacking in that it does not have the spreading fan effect, but it does allow for a nice flip through. This is especially helpful if you have limited real estate in which to present your images and a tiled effect is not your first choice. After you test the demo, you’ll see that you can flip to the next image, but you can’t flip back, and when you reach the end of the photo stack, that’s it, you’re done.

Also, when you click to enlarge a specific photo, it only increases in size by a small margin. However, that is the type of feedback that other developers can take to heart and build upon, for the code in this article should provide an excellent starting point. The original developer who wrote the code is Ryan Collins and the demo is located here.

Show Me the jQuery Code!

Ryan starts off with some jQuery:

$(document).ready(function() {
    $(‘.photo:nth-child(7)’).addClass(‘num4’);
    $(‘.photo:nth-child(8)’).addClass(‘num3’);
    $(‘.photo:nth-child(9)’).addClass(‘num2’);
    $(‘.photo:nth-child(10)’).addClass(‘num1’);
    var count = 0;
    $(‘#nextbutton’).click(function() {
        count++;
        $(‘.photo:nth-child(1)’).addClass(‘num’ + (-count + 10));
        $(‘.photo:nth-child(2)’).addClass(‘num’ + (-count + 9));
        $(‘.photo:nth-child(3)’).addClass(‘num’ + (-count + 8));
        $(‘.photo:nth-child(4)’).addClass(‘num’ + (-count + 7));
        $(‘.photo:nth-child(5)’).addClass(‘num’ + (-count + 6));
        $(‘.photo:nth-child(6)’).addClass(‘num’ + (-count + 5));
        $(‘.photo:nth-child(7)’).addClass(‘num’ + (-count + 4));
        $(‘.photo:nth-child(8)’).addClass(‘num’ + (-count + 3));
        $(‘.photo:nth-child(9)’).addClass(‘num’ + (-count + 2));
        $(‘.photo:nth-child(10)’).addClass(‘num’ + (-count + 1));
        if(count == 10) {
            $(‘#nextbutton’).addClass(‘hidden’);
            $(‘.photos’).addClass(‘complete’);
       }
    });
    $(‘.photo’).click(function() {
        $(this).toggleClass(‘zoom’);
        $(‘.photos’).toggleClass(‘hidden’);
        $(‘#nextbutton’).toggleClass(‘hidden’);
    });
});
 
From the looks of this code, the jQuery accounts for the number of images in the stack. In this case, there are 10 images. If you were to do more or less, you will have to adjust the code. I believe there is room for improvement here as well in that a developer could set this up so that the code does not have to be tweaked per the number of images and that could be automated in some fashion. But this should give you a good start.

Next, the CSS3 Code

And here’s the CSS3 code in its entirety:

body {
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#3d3d3d), color-stop(100%,#1c1c1c));
    -webkit-font-smoothing: antialiased;
}
 
.photos {
    position: relative;
    height: 525px;
    width: 350px;
    top: 50%;
    margin: -310px auto;
    z-index: 1;
    -webkit-perspective: 1000;
    -webkit-perspective-origin: 50% 20%;
}
 
.photos.complete:before,
.photos.complete:after {
    display: block;
}
 
.photos:after {
    position: absolute;
    height: 414px;
    width: 274px;
    top: 107px;
    left: 35px;
    border: 3px rgba(0,0,0,.3) dashed;
    display: block;
    border-radius: 15px;
    content: “”;
    display: none;
}
 
.photos:before {
    position: absolute;
    height: 414px;
    width: 274px;
    top: 108px;
    left: 35px;
    border: 3px rgba(255,255,255,.05) dashed;
    display: block;
    content: “CSS3 Photo Stack”;
    border-radius: 15px;
    line-height: 414px;
    color: rgba(0,0,0,.3);
    text-shadow: 0 1px 0 rgba(255,255,255,.05);
    font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
    font-weight: bold;
    font-size: 22px;
    text-align: center;
    display: none;
}
 
.photo {
    height: 525px;
    width: 350px;
    position: absolute;
    top: 0;
    border: 1px solid rgba(0,0,0,.9);
    -webkit-transform-origin: 50% 100%;
    -webkit-box-shadow: inset 0 0 0 1px rgba(255,255,255,.1);
    -webkit-transition: -webkit-transform .5s ease;
    -webkit-transition-delay: 0s;
    -webkit-backface-visibility: hidden;
    overflow: hidden;
    background: #aaa;
    -webkit-transform: rotateX(10deg) translateY(-16px) scale(.8);
    pointer-events: none;
    z-index: 2;
}
 
.photos.hidden .photo {
    -webkit-transform: rotateX(0deg) translateZ(-70px) scale(.8);
}
 
.zoom {
    -webkit-transform: rotateX(0deg) translateZ(0px) scale(1) translateY(50px) !important;
}
 
.photo:first-of-type {
    -webkit-box-shadow: 0 0 20px 5px rgba(0,0,0,.5), inset 0 0 0 1px rgba(255,255,255,.1);
}
 
.photo.num4 {
    -webkit-transform: rotateX(5deg) translateY(-11px) scale(.8);
}
 
.photo.num3 {
    -webkit-transform: rotateX(0deg) translateY(-6px) scale(.8);
}
 
.photo.num2 {
    -webkit-transform: rotateX(-6deg) translateY(-2.5px) scale(.8);
}
 
.photo.num1 {
    -webkit-transform: rotateX(-11deg) translateY(.2px) scale(.8);
    pointer-events: visible;
    cursor: pointer;
}
 
.photo.num1:after {
    content: “+”;
    position: relative;
    margin: auto;
    line-height: 50px;
    height: 50px;
    width: 50px;
    border-radius: 10px;
    background: rgba(0,0,0,.75);
    display: block;
    top: 238px;
    font-size: 30px;
    font-weight: bold;
    color: #fff;
    text-shadow: 0 2px 5px #000;
    font-family: Arial;
    text-align: center;
    opacity: 0;
}
 
.photo.num1:hover:after {
    opacity: 1;
}
 
.zoom:hover:after {
    content: “×” !important;
}
 
.zoom:before {
    content: attr(caption);
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    display: block;
    font-size: 14px;
    line-height: 44px;
    font-weight: light;
    color: #222;
    background: rgba(255,255,255,.75);
    text-shadow: 0 1px 0 #fff;
    font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
    text-align: center;
    opacity: 1;
    padding: 10px;
}
 
.photo.num0 {
    -webkit-transition: -webkit-transform 1s ease-in;
    -webkit-transform: rotateX(-80deg) translateZ(300px) scale(.8) !important;
    pointer-events: none;
}
 
.photo li {
    display: block;
    width: 100%;
    height: 100%;
    position: absolute;
    z-index: 10;
    background: -webkit-gradient(linear,0% 0%,100% 67%,from(rgba(255,255,255,.8)),to(rgba(255,255,255,0)),color-stop(.5,rgba(255,255,255,.1)),color-stop(.5,rgba(255,255,255,.0))) no-repeat;
    pointer-events: none;
    border-radius: 0;
    -webkit-transition: -webkit-transform .5s ease;
    -webkit-transform: translateY(-15px);
}
 
.zoom li {
    -webkit-transform: translateY(0px) !important;
}
 
.photo.num4 li {
    -webkit-transform: translateY(-25px);
}
 
.photo.num3 li {
    -webkit-transform: translateY(-50px);
}
 
.photo.num2 li {
    -webkit-transform: translateY(-75px);
}
 
.photo.num1 li {
    -webkit-transform: translateY(-100px);
}
 
.photo.num0 li {
    -webkit-transform: translateY(-525px);
    -webkit-transition: -webkit-transform 1s ease-in;
}
 
#nextbutton {
    -webkit-appearance: none;
    border-radius: 5px;
    padding: 0;
    display: block;
    width: 100px;
    height: 30px !important;
    line-height: 30px;
    position: absolute;
    border: none;
    background: rgba(34, 35, 37, .97) -webkit-gradient(linear,100% 100%,100% 0%,from(rgba(255, 255, 255, .05)),to(rgba(255, 255, 255, .1)));
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05), inset 0 -1px 0 rgba(255, 255, 255, .02), 0 0 2px rgba(0, 0, 0, .6);
    top: 50%;
    left: 50%;
    margin: 225px 0 0 -50px;
    color: #fff;
    font-weight: bold;
    text-shadow: 0 1px 1px #000;
    text-align: center;
    font-size: 14px;
    z-index: 0;
    cursor: pointer;
    -webkit-transition: .5s opacity ease;
    -webkit-font-smoothing: antialiased;
}
 
#nextbutton.hidden {
    opacity: 0;
    pointer-events: none;
}
 
#nextbutton:hover {
    background: rgba(34, 35, 37, .97) -webkit-gradient(linear,100% 100%,100% 0%,from(rgba(255, 255, 255, .1)),to(rgba(255, 255, 255, .2)));
}
 
#nextbutton:active{
    box-shadow: inset 0 -1px 6px rgba(0,0,0,.7), inset 0 0 0 1px rgba(0,0,0,.7), 0 1px 0 rgba(255,255,255,.08);
    background: rgba(34, 35, 37, .1) -webkit-gradient(linear,100% 100%,100% 0%,from(rgba(255, 255, 255, .05)),to(rgba(255, 255, 255, .1)));

The beauty of this CSS3 code is the attention to detail. It’s really wonderfully done. Obviously, the code does contain quite a bit of webkit elements, so it only works in Chrome. Again, savvy developers who want to use this in Firefox or Internet Explorer will have to make their own tweaks. I did notice that this code makes good use of the perspective element. This allows for the image to have a 3D effect without manipulating the original photo.

Finally, the HTML Code

Per the norm, the HTML is the least amount of code and looks like this:

<body>
    <div class=”photos”>
        <div class=”photo” style=”background-image:url(10.jpg)” caption=”Vivamus luctus laoreet magna, vel lobortis lorem fringilla vel.”><li><li><div>
        <div class=”photo” style=”background-image:url(9.jpg)”><li><li><div>
        <div class=”photo” style=”background-image:url(8.jpg)”><li><li><div>
        <div class=”photo” style=”background-image:url(7.jpg)”><li><li><div>
        <div class=”photo” style=”background-image:url(6.jpg)”><li><li><div>
        <div class=”photo” style=”background-image:url(5.jpg)” caption=”Nullam adipiscing sapien non mi mollis sit amet mattis magna semper.”><li><li><div>
        <div class=”photo” style=”background-image:url(4.jpg)”><li><li><div>
        <div class=”photo” style=”background-image:url(3.jpg)” caption=”The next photo will have no caption.”><li><li><div>
        <div class=”photo” style=”background-image:url(2.jpg)” caption=”Lorem ipsum.”><li><li><div>
        <div class=”photo” style=”background-image:url(1.jpg)” caption=”Hello World!”><li><li><div>
    <div>
    <button id=”nextbutton”>Next<button>
<body>

Again, each photo is accounted for with its own line of code. So, if you don’t have exactly 10 images, you’ll have to tweak the code.

Overall, this code is good for sites with a static amount of images that won’t be updated on a regular basis. If so, you’ll be constantly updating the jQuery and the HTML. With that said though, this photo stacking effect is aesthetically pleasing and with some tweaks, enhancements and modifications it would make an excellent addition to any site that needs a photo stacking feature.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Popular Articles

Featured