CSS Mask Tutorial — Rotating Image Gallery

November 24th, 2011 :: Web Development

Gallery Wheel

I’ve been trying out different things that you can do with the CSS mask property lately. With a CSS mask, you can mask or clip any element to a desired shape. The mask is either a PNG image, or an SVG image.

I coupled this with the CSS transform and transition properties, and came up with a pretty cool concept for an image gallery.

I’ve put together a demo for Chrome and Webkit nightly builds. Firefox also supports the CSS mask property, but doesn’t seem to play well with rotated masked images, so I’ve only used -webkit prefixes for this demo.

I’ll run through all the different steps in making this gallery wheel below, starting with the markup.

The HTML

<div id="gallery">
   <div id="gallery-wheel">
      <img class="active" src="pic1.png" alt="" />
      <img src="pic2.png" alt="" />
      <img src="pic3.png" alt="" />
      <img src="pic4.png" alt="" />
      <img src="pic5.png" alt="" />
      <img src="pic6.png" alt="" />
   </div>
</div>

The structure is pretty straightforward. We have a wrapper element which contains the rotating outer wheel, and the centerpiece which remains still. The images are random shots from Dribbble.

The Mask

 

<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300">
  <path d="M150,300 L25,83 A250,250 0 0,1 275,83 z"/>
</svg>

The SVG mask is saved in it’s own .svg file, and is the same size as the images we want to apply it to.

a happy pie

Since we have six images in the wheel, our mask will be a sixth of a circle. We draw this out by using the path svg tag. The shape of the path is defined by the d attribute, which takes a number of options. I’m not going to go into exactly what the parameters mean, just know that this draws a 60° segment of a circle, with a radius of 250 pixels. This article has a good explanation on how to draw pie shapes in SVG images.

Why choose SVG over PNG masking?

You might think that it would be a lot simpler to just draw your desired shape in an image editor, save it as a PNG image, and use that as the CSS mask instead. This might be true, but what if you wanted to quickly change the number of images in the wheel? If we instead wanted to have 12 images, we would have to go back into our image editor and make a new PNG. This could become a lot more cumbersome than just changing some parameters in the SVG.

And what if you would like your gallery to be completely dynamic? There might be cases when you cannot determine beforehand how many pictures the gallery will contain. Our SVG mask could just as easily be generated and put in the DOM by Javascript code that takes into account how many images the gallery contains.

That said, the only browser that at the moment supports embedded/inline SVG images as CSS masks is Firefox which, as I stated earlier, doesn’t seem to rotate masked images correctly.

The CSS

#gallery {
	margin: 0 auto;
	position: relative;
	background: white;
	width: 520px;
	height: 520px;
	border-radius: 50%;
	border: 5px solid white;
}

#gallery-wheel {
	position: relative;
	width: 100%;
	height: 100%;
}

#gallery-wheel img {
	position: absolute;
	-webkit-mask-box-image: url("../mask.svg") round;
	-webkit-transform-origin: 50% 100%;
	padding: 5px;
	top: 0;
	margin-top: -50px;
	left: 50%;
	margin-left: -155px;
	cursor: pointer;
}
#gallery-wheel img:hover { opacity: 0.5; }

#gallery-wheel img:nth-child(2) { -webkit-transform: rotate(60deg); }
#gallery-wheel img:nth-child(3) { -webkit-transform: rotate(120deg); }
#gallery-wheel img:nth-child(4) { -webkit-transform: rotate(180deg); }
#gallery-wheel img:nth-child(5) { -webkit-transform: rotate(240deg); }
#gallery-wheel img:nth-child(6) { -webkit-transform: rotate(300deg); }

#gallery-center {
	position: absolute;
	background-image: url("../pic1.png");
	background-position: center;
	top: 50%;
	left: 50%;
	margin-left: -160px;
	margin-top: -160px;
	width: 300px;
	height: 300px;
	border-radius: 50%;
	border: 10px solid white;
}

.fade-overlay {
	position: absolute;
	background-position: center;
	opacity: 0.0;
	width: 100%;
	height: 100%;
	border-radius: 50%;
}

Most of this is all pretty basic styling, so let’s just look at the CSS mask and transform parts, which I’ve highlighted above.

The -webkit-mask-box-image: url(../mask.svg) round rule on line 19 sets our CSS mask to the SVG that we created, stretching it to cover the whole image.

The next line, -webkit-transform-origin: 50% 100%, puts the pivot point of the rotation to the bottom center of the CSS mask. This will be the exact center of the gallery wheel.

Lines 30 to 34 rotate the 5 other images around the wheel. For every image, we add 60 degrees to the rotation. This fills the entire circle with our pie-slices.

The fade-overlay class is used by the Javascript code, which I’ll explain next.

The Javascript — Making it Rotate

$(document).ready(init);

var galleryWheel;
var galleryCenter;
var galleryItems;
var animating;

function init() {
	galleryWheel = $("#gallery-wheel");
	galleryCenter = $("#gallery-center");
	galleryItems = $("#gallery img");

	galleryItems.click(clickImage);
}

function clickImage(e) {
	var target = $(e.target);
	if (!target.hasClass("active") && !animating) {
		animating = true;
		var activeElement = galleryWheel.find(".active");

		// Calculate the number of elements between the active (top) image and the clicked
      // one, and multiply that by the inner angle that each slice has.
		var rotateBy = -360/galleryItems.length * (target.index() - activeElement.index());

		// Make sure the shortest path is taken, the maximum length the wheel should spin
      // is 180 degrees.
		if (rotateBy >= 180) {
			rotateBy -= 360;
		} else if(rotateBy < -180) {
			rotateBy += 360;
		}

		// Create a temporary overlay element that fades in over the center image.
		// When the opacity is full, change the image of the center element behind,
		// and remove the overlay.
		$('<div class="fade-overlay">')
          .css("backgroundImage", "url(" + target.attr("src") + ")")
              .appendTo(galleryCenter).animate({opacity: 1.0}, 500, function() {
                   galleryCenter.css("backgroundImage", "url(" + target.attr("src") + ")");
                   $(this).remove();
               });

      activeElement.removeClass("active");
      target.addClass("active");

      galleryWheel.animate({rotate: "+=" + rotateBy + 'deg'}, 500, function() { animating = false; } );
   }
}

To get the gallery to rotate on user input, we use jQuery coupled with the jQuery 2D Transformation Plugin by Grady Kuhnline.

hamster in wheel
When the user clicks on an image in the wheel, we calculate the distance in degrees to that image from the current active image. We can make sure that the wheel always rotates the shortest path to the new position, by limiting the rotation values between -180 and 180 degrees.

While rotating the wheel to the new position, the new image fades in. Since we can’t fade between background images on a single element, we need to overlay a temporary element over the center. This temporary element contains the image we want to display next, and fades in above the center. When the temporary image is at full opacity, we switch the image of the underlying center element and remove the overlay element. This creates the illusion of a seamless transition between background images.

Conclusion

A small annoyance with the final product is that the bounding box of the center image blocks the hover events of the images below. I’m almost certain I’ve seen some CSS property that enables you to hover behind elements, but I just can’t seem to find it now.

UPDATE: The property I was looking for is pointer-events: none;.
Although using that makes hovering over the center more confusing, since the hover areas of the images overlap eachother in weird ways.

This gallery is rather gimmicky, so I doubt there’s any real use for this. My intention has mainly been to show off some cool things you can do with modern CSS properties. I guess you could make some kind of fancy menu using this technique, but with the current lack of browser support, it probably shouldn’t be used on any real world website. Anyhow, I hope someone will be able to learn something new from what I’ve shown today.

Below is a link to the final demo for the gallery wheel. Note that this only works in Chrome and Webkit nightly builds. I might add Firefox support once they fix the rotated masks issue.

Demo

  • Simon Johnstone

    Nice tutorial, keep it up. Will be having a little play around with this

  • http://aniketpant.com Aniket Pant

    Really amazing tutorial!

    Can you try replicating this for Mozilla! It is working on webkit only.

    Nice work :D

    • http://www.joelb.me Joel Besada

      Mozilla seems to have some issues with rotating masked images, so I’m only using Webkit prefixes in the demo for now. Once the issue gets fixed, I’ll see if I can update it.

  • miss

    that’s great. Thanks for sharing. Please also update when you get it for all browsers.

  • http://www.bybe.net/ Web Design Bournemouth

    Awesome, Shared on this on my wall – I needed something like this for my project, thanks very much.

  • Pingback: CSS маски

  • http://joer.fr/ Joer

    Hey joel, 

    Nice job !

  • http://profile.yahoo.com/HAKEBNKGCILFKISNPZ3Y4KXKI4 fer

    Great, nice job, but is not working in IE9

    • Boss

      did you even read any of the code or description?  ”webkit” means it works in webkit browsers like chrome, safari, mobile-safari, chromium, iCab… etc…..

    • Webdevguru

      IE9 cannot read good code.

  • http://www.facebook.com/profile.php?id=100001349945158 Muhammad Luthfie

    Nice Post….

  • http://www.facebook.com/profile.php?id=100001349945158 Muhammad Luthfie

    Nice Post…

  • User

     Doesn’t work well on FF 18.0.1

  • Niall Mac Giolla Rua

    Beautiful!

  • Alan Bridge

    How do i make it to work with 8 image, it will only work with 6 as i can see and the moment. Or will it work with 8 and i’m just being stupid and not seeing the answer in front of me

  • masiha

    you are best

  • jaya

    any update to work on mozilla..?

  • porizm

    The middle circle is still acting as a square even if you are using border-radius.

    And it’s covering a space of the items behind.

  • Aamir Yasin

    really nice data and info , so i am feeling good to see , , feeling much happay and solve provlem that i have :)recover my file crack