Creating Image Galleries with Clipped Images Using CSS Pointer Events and SVG

svg-header

By Zoltan Hawryluk

If you click on the TV in the image below, you will see that it contains an image gallery created using the jQuery Cycle Plugin. You may ask, “I’ve used this awesome plugin before, so what’s the big deal?” Although this looks deceptively simple, the big deal about this is that the gallery is sitting behind a DOM object and mouse events are being passed through it. The image of Mr. Disney that you see here is a JPEG with the television screen clipped out. Underneath that clipped area is the image gallery. When you click, hover, or do any other actions with your mouse, these events are passing through the image and are captured by the image gallery.

View this on JSFiddle

There are a few ways to do this using the CSS pointer-event property and any number of different image formats that supports clipping (e.g. SVG, canvas, PNGs with alpha-transparency). Unfortunately, each one of these clipping technologies has it’s advantages and deal-breaking disadvantages. So after quite a bit of research, I came up with a method that is cross-browser (yes, it works in IE7), has been optimized for fast animations in all browsers (not always easy, as we will see later in this article) and works well in mobile.

Click here to see a clean-room version of the above example.

What Does the pointer-event Property Do?

In HTML documents, setting a DOM object’s pointer-event property to none will result in any mouse event to pass through that object to the DOM object positioned underneath. This goes for both JavaScript events (e.g. click, mouseover, etc.) as well as CSS-related pseudo-class events (e.g. :hover, :active, etc.) The only other value that the pointer-event property can have in the HTML world is visible, which allows the node to capture mouse events again.

Originally, the pointer-event property was meant to be used for SVG, which allows more fine-grained control of mouse events. More information about this can be seen on the Mozilla Developer Network‘s page on SVG pointer-events.

Method #1: SVG

The first method is to use SVG to clip the image. Developers can embed the SVG directly into their HTML markup like this:

<svg xmlns="http://www.w3.org/2000/svg"
   width="495px" height="417px"
   viewBox="0 0 495 417"
   xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>   
    <pattern id="disney-embed" patternUnits="userSpaceOnUse" 
       width="495" height="417">
          <image xlink:href="/tests/pointerEvents/images/disney-tv.jpg" 
          x="0" y="0" width="495" height="417" />
    </pattern>
</defs>  

<path id="walt-embed"
      fill="url(#disney-embed)" stroke="none"
      d="M 495.00,0.00
         C 495.00,0.00 495.00,417.00 495.00,417.00
           495.00,417.00 0.00,417.00 0.00,417.00
           0.00,417.00 0.00,0.00 0.00,0.00
           0.00,0.00 495.00,0.00 495.00,0.00 Z
         M 81.18,219.39
         C 75.28,224.66 73.26,233.55 71.61,241.00
           68.81,256.26 68.66,271.70 71.61,287.00
           72.76,294.37 74.47,301.98 79.68,307.70
           85.29,313.85 91.52,314.81 99.00,316.73
           99.00,316.73 126.00,321.96 126.00,321.96
           126.00,321.96 134.00,321.96 134.00,321.96
           134.00,321.96 144.00,323.00 144.00,323.00
           156.04,323.14 168.13,322.58 180.00,320.39
           187.27,319.04 193.58,317.17 198.20,310.91
           202.27,305.40 200.54,300.30 201.28,294.00
           201.28,294.00 202.00,244.00 202.00,244.00
           201.98,234.15 201.61,228.06 192.91,221.85
           187.58,218.04 176.56,216.51 170.00,215.41
           153.07,212.57 126.99,210.70 110.00,212.28
           101.11,213.56 88.05,213.25 81.18,219.39 Z" />
</svg>

Note the two bolded areas. The image we are masking with is included in a <pattern> tag. The clipping path is included in the <path> tag. Note that the <path> refers to the <pattern> via its id in the fill attribute.

How do we come up with the path? You can use an SVG tool like Inkscape (which, incidentally, is open-source) or use another vector tool like Adobe Illustrator to do the job. However, for those of you like me who want something cheap and simple, take a look at my article on how to create SVG paths easily using The GIMP.

One can also save the SVG block as an .svg file and point to it using an <object> tag, like in the example below.

<object id="object" type="image/svg+xml" data="/tests/pointerEvents/images/tv-path.svg">
   Your browser does not support SVG
</object>

Here is a live example so you can see the result:

View this on JSFiddle

The good thing about using SVG is that the it works as expected — pointer-event was originally developed to be an SVG only property. As a result, it passes the clicks through to the gallery underneath in all browsers except in IE8 and below (and also in IE9-10 if you embed your SVG with the <object> tag like the example above). You can make it degrade gracefully by putting alternative markup in the <object> tag where I currently have “Your browser does not support SVG” but that doesn’t help you if you want the end result to look like the example at the beginning of the article.

Using the embedded SVG is even worse, as there is no alternative markup for IE7 and 8 to show. Older IE will also show a broken image because, as Lea Verou mentioned in her blog post about standards based text-masking, IE interprets the <image> tag as an HTML <img> tag, and  since there is no src property, we get a broken image.

There is one last issue with using SVG for this use-case: performance. In Firefox, the animations underneath the transparent part of an SVG element will slow down. If you compare the animation in the SVG example compared to the others in Firefox, you will see a the SVG version drops a few frames. This is true with Firefox 20 and under, which was the current production version at the time of the writing of this article.

Method #2: PNG

Developers can also use their favorite graphics editor to clip out the areas they want transparent and save the result as a PNG. Here is a live example so you can see how this works:

View this on JSFiddle

In the above example, I use CSS to set the image’s pointer-event property to none. Click on the gallery using Firefox, Safari and Chrome and this will work as expected. Unfortunately, Opera and IE (including 10) do not support the pointer-event property for HTML nodes, so this is even worse that the SVG solution. Furthermore, PNG files are generally larger than JPGs for photographs, so this solution will not be optimal for really large images.

Method #3: polyClip.js

For more information about polyClip.js, please read my introductory blog post on clipping JPEG images into non-rectangular polygons using polyClip.js.

As I have mentioned on previous blog posts, I have used polyClip.js to take a JPEG image and clip parts out and place the result inside of an HTML5 <canvas> element. Not only does this give you a better download speed than the PNG solution, but it has the added side-effect of letting mouse events through the transparent part of the resultant image in all versions of IE. This is because:

  • In IE 7-8, polyClip.js uses the excanvas polyfill to convert HTML5 Canvas API calls to VML. The parts of the <canvas> that aren’t painted with VML allow mouse events through by default.
  • In IE9+, polyClip.js will render the clipped image as an SVG. As long as the CSS pointer-event property is set to none for all [data-polyclip] selectors, mouse events will pass through.

 

Here’s how to do the polyClip.js method:

  1. Download the latest copy of polyclip.js from GitHub
  2. In the page you want to include the image in, insert the following <script> tags in your document:
    <script src="/path/to/js/jquery-1.9.1.min.js"></script>
    
    <!--[if lt IE 9 ]>
     <script src="/path/to/js/polyClip/js/excanvas/excanvas.compiled.js"></script>
    <![endif]-->
    
    <script src="/path/to/js/canvg.js"></script>
    <script src="/path/to/js/polyClip/js/polyclip-p.js" 
               data-polyclip-clippreference="svg" 
               data-polyclip-forcepointerevents="true"></script>

    The polyClip.js library requires jQuery in order to run. The excanvas library is included in conditional comments since it is only needed in IE8 and lower — it is used to convert basic HTML5 canvas api calls to VML (more on this later). The final library, canvg.js, is used by polyClip.js to clip out an SVG path from an image.

    Note the data-polyclip-clippreference=”svg” and data-polyclip-forcepointerevents=”true” attributes in polyClip.js’s <script> tag. The data-polyclip-clippreference=”svg” tells polyClip.js to “generate SVG when you can”. Usually, this would always happen in all SVG browsers, but when combined with data-polyclip-clippreference=”svg” it will only render SVG when the pointer-event CSS property is not supported in HTML (IE9-10 and Opera). Browsers that do support HTML pointer-events will use Canvas (Firefox, Safari and Chrome), while older IE will use VML. This will ensure the optimum performance for any animations that appear below the clipped image in all browsers. You can use the<body> tag instead of polyClip.js’s <script> tag to set these attributes, in the event you change the name of polyClip.js or incorporate it into a larger, compressed script to optimize load times.

  3. Next, insert the original image into your document, and place the SVG path inside the <img> tag as below:
    <img src="images/disney-tv.jpg" 
         data-polyclip-width="495" 
         data-polyclip-height="417" 
         data-polyclip="path:M 495.00,0.00
           C 495.00,0.00 495.00,417.00 495.00,417.00
             495.00,417.00 0.00,417.00 0.00,417.00
             0.00,417.00 0.00,0.00 0.00,0.00
             0.00,0.00 495.00,0.00 495.00,0.00 Z
           M 81.18,219.39
           C 75.28,224.66 73.26,233.55 71.61,241.00
             68.81,256.26 68.66,271.70 71.61,287.00
             72.76,294.37 74.47,301.98 79.68,307.70
             85.29,313.85 91.52,314.81 99.00,316.73
             99.00,316.73 126.00,321.96 126.00,321.96
             126.00,321.96 134.00,321.96 134.00,321.96
             134.00,321.96 144.00,323.00 144.00,323.00
             156.04,323.14 168.13,322.58 180.00,320.39
             187.27,319.04 193.58,317.17 198.20,310.91
             202.27,305.40 200.54,300.30 201.28,294.00
             201.28,294.00 202.00,244.00 202.00,244.00
             201.98,234.15 201.61,228.06 192.91,221.85
             187.58,218.04 176.56,216.51 170.00,215.41
             153.07,212.57 126.99,210.70 110.00,212.28
             101.11,213.56 88.05,213.25 81.18,219.39 Z"/>
  4. If you want pointer events to pass through the clipped image that remains, use the following CSS rule.
    [data-polyclip] {
        pointer-event: none;
    }

To see a clean-room version of this method in action, click here.

Are More Complicated Effects Possible?

Remember, all pointer events can pass through the image, including mouseovers, mouseouts and CSS :hover events! This can result in some interesting effects with just a few additional bits of CSS. Here is a great example that could be used for a great landing-page navigational item.

As an example, click on the image below to see a demo about my favorite video game of all time, Pac-Man!

pacman

Caveats

I have noticed that sometimes, inserting the <script> tags at the end of the body will cause errors in IE8. The workaround is to put them in the <body>.

This article was originally published at http://www.useragentman.com/blog/2013/04/26/clicking-through-clipped-images-using-css-pointer-events-svg-paths-and-vml/

Modern Web Newsletter

Subscribe to receive the Modern Web tutorials, sent out every second Wednesday.

Top