CSS clip-path
is awesome, but it has two significant drawbacks in comparison to the SVG feature from which it is derived:
- CSS clipping paths must be single closed polygon or bezier curve shapes; adding more than one path causes the clipping process to fail.
clip-path
is only supported in browsers with a Webkit inheritance (primarily Chrome, Safari and Opera).
The SVG original does not suffer from any of these limitations: multiple shapes in an SVG <clipPath>
element are simply added together to form a single, unified clipping element, and the SVG method is supported in all modern browsers.
While I expect that it will be fairly common for designers to develop their own clipping paths for images and other elements, there are a few variations, such as checkerboard effects, that are so common that it would be nice to generate them automatically. With a little JavaScript, we can do just that.
First, the markup:
<div id="clipit">
<img src="sunset.jpeg" alt>
</div>
The image is placed in a <div>
element so that we can easily add a background image to it via CSS:
div#clipit {
background: url(fern.jpeg);
background-size: cover;
font-size: 0;
overflow: hidden;
}
div#clipit img {
-webkit-clip-path: url(#clipper);
clip-path: url(#clipper);
width: 100%;
height: auto;
}
overflow: hidden;
takes care of any clipping shapes that may fall outside the area of the <div>
; clip-path
references an SVG element that will be created with JavaScript (Note that Webkit-based browsers will require the same property with an appropriate vendor prefix).
First, we’ll write a few functions to help us create the SVG and its elements on the page:
var NS="http://www.w3.org/2000/svg";
var SVG=function(){
var svg=document.createElementNS(NS,"svg");
svg.id="shapeclipper";
var defs=document.createElementNS(NS,"defs");
var clipper = document.createElementNS(NS,"clipPath");
clipper.id="clipper";
clipper.setAttributeNS(null,"clipPathUnits","objectBoundingBox");
defs.appendChild(clipper);
svg.appendChild(defs);
return svg;
}
var rect=function(x,y,w,h){
var cliprect = document.createElementNS(NS,"rect");
cliprect.setAttribute("height",h);
cliprect.setAttribute("width",w);
cliprect.setAttribute("x",x);
cliprect.setAttribute("y",y);
return cliprect;
}
var ellipse=function(x,y,chordX,chordY){
var clipellipse = document.createElementNS(NS,"ellipse");
clipellipse.setAttribute("cx",x);
clipellipse.setAttribute("cy",y);
clipellipse.setAttribute("rx",chordX/2);
clipellipse.setAttribute("ry",chordY/2);
return clipellipse;
}
function coinFlip() {
if (clipDistribution == "regular") {
return (1);
} else {
return (Math.floor(Math.random() * 2) == 0);
}
}
var svg=SVG();
document.body.appendChild(svg);
The functions create a root SVG element, and offer two possible shapes to draw inside it: rectangles and ellipses.
The example at the top of this article allows for various properties of the clipping area to be altered via form elements, for which you can find the complete code on CodePen; in the simplified example below, the options are all hard-wired.
var cols = 3;
rows = 2,
clipPattern = "square",
clipArray = "checker",
clipDistribution = "regular",
clipElWidth = (1/cols),
clipElHeight = (1/rows),
for (var rowCount = 0; rowCount < rows; rowCount++) {
if (rowCount%2 && clipArray == "checker") {
colCount = 1;
} else {
colCount = 0;
}
for (colCount; colCount < cols; clipArray == ("checker") ? (colCount = colCount+2) : (colCount++)) {
if (clipPattern == "square") {
var xPos = colCount/cols;
var yPos = rowCount/rows;
if (coinFlip() == 1) {
var r = rect(xPos,yPos,clipElWidth,clipElHeight);
}
}
if (clipPattern == "circle") {
var xPos = colCount/cols + (clipElWidth/2);
var yPos = rowCount/rows + (clipElHeight/2);
if (coinFlip() == 1) {
var r = ellipse(xPos,yPos,clipElWidth,clipElHeight);
}
}
if (r) { clipper.appendChild(r); }
}
}
In short, the script divides the area of the image into rows and columns and applies a rectangle or circle to each area; the coinFlip
option determines whether a clipping shape should be down in a particular area or not, creating a random to semi-random pattern, depending on the other options used.
For the simplest possibility - an image divided into two rows and three columns - the SVG generated would look like this:
<svg id="shapeclipper">
<defs>
<clipPath id="clipper" clipPathUnits="objectBoundingBox">
<rect height="0.5" width="0.3" x="0" y="0"></rect>
<rect height="0.5" width="0.3" x="0.6" y="0"></rect>
<rect height="0.5" width="0.3" x="0.3" y="0.5"></rect>
</clipPath>
</defs>
</svg>
When applied to an image with the appropriate CSS, the result has a see-through effect to whatever is underneath; in this case, another image.
Possible Improvements
Right now the script relies on the designer to determine the “correct” number of rows and columns for an image (as a general rule, smaller numbers work better). It would be nice to have the script automatically determine the “right” numbers for the checkerboard effect by comparing the image’s height and width and any common divisors. It would also make sense that the number of clip shapes be reduced and redrawn at smaller screen sizes for a responsive site.
There are many other possibilities for clip shapes and patterns, some of which can be generated in this example: try a row count of 1 with multiple columns, for example. The obvious pattern that the script is missing right now is a grid of squares evenly spaced across the image.
I’m also interested in animating the results: it would be easy enough to delay the rendering of each clip shape with JavaScript. In an advanced example, the clip shapes could move like puzzle patterns or participants in Conway’s “Game of Life”.
What About Canvas?
There’s an argument to be made at this point that this degree of complex masking should be done via an HTML5 <canvas>
element. While I’m interested in comparing the performance of this script to a pure JavaScript solution, I think this SVG method will likely retain several advantages:
- accessibility and progressive enhancement: the images are already on the page, and can be provided with
alt
text, so if the SVG or JavaScript fails to work for any reason, nothing is lost except the checkerboard effect. - speed I suspect that writing elements to the DOM may be faster than blittering the pixels of each segment from the image onto a canvas.
- code weight: this SVG solution likely requires fewer lines of code than a canvas-based equivalent.
Personally I think there’s a lot of potential here, and I look forward to exploring the possibilities in future iterations.
Images by lancegfx and Karim Hamm, licensed under Creative Commons
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/XbJpjg