Although I've recommended <mask>
as a workaround ...
<mask>
vs. <clipPath>
– don't forget compound paths
Sure compound paths require a better understanding of SVG path commands, but it's worthwhile!
Actually, each "e" and "o" you see in this post – rendered from text, based on a font etc. – relies on compound paths – a path containing an outer and inner shapes that appear "cut out" (like the letter O).
For simple tasks like rendering a "donut" shape, you should prefer compound paths over masks or clip paths.
const container = SVG().addTo('body').size(500, 500)
const bg = container.rect(500,500).fill('green')
let donutPathData = createDonutPathData(20, 30, 100, 80);
const donut = container.path(donutPathData).fill('blue');
// create donut paths
function createDonutPathData(x, y, d1, d2){
let strokeWidth = (d1-d2)
let pathData =
// outer circle
createCirclePathData(x, y, d1, 1) +
// inner circle (cut out)
' '+ createCirclePathData(0, strokeWidth/2, d2, 0, true)
return pathData;
}
// create circle paths
function createCirclePathData(x=0, y=0, d=100, sweep=1, relative=false ){
let M = relative ? `m ${x} ${y}` : `M ${d/2+x} ${y}`;
let pathData = `${M} a ${d/2} ${d/2} 0 0 ${sweep} 0 ${d} a ${d/2} ${d/2} 0 0 ${sweep} 0 -${d}`;
return pathData;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.2.0/svg.min.js"></script>
How it works
We're using a custom helper function createCirclePathData()
generating arcs according to radius and x/y values returning a d
pathData attribute string which can be passed to SVG.js path()
method.
A donut shape could easily be created with one path including a subpath like so – outer diameter=100; inner diameter=80:
M 50 0
A 50 50 0 0 1 50 100
A 50 50 0 0 1 50 0
M 50 10
A 40 40 0 0 0 50 90
A 40 40 0 0 0 50 10
Both outer and inner circle are based on the A
(Arc) command
The above code translates to
/**
* starting point: x = r or diameter/2;
*/
M 50 0
/**
* outer circle:
* radius, radius, angle, long arc flag, sweep flag (clockwise or counterclockwise), final x, final y
*/
A 50 50 0 0 1 50 100
A 50 50 0 0 1 50 0
/** inner circle:
* new starting point according to inner diameter
* radius and final points change accordingly –
* we change the path direction via sweep flag.
* This way the inner circle will define the cut-out shape
*/
M 50 10
A 40 40 0 0 0 50 90
A 40 40 0 0 0 50 10
See also "How to calculate the SVG Path for an arc (of a circle)" (and a lot more posts tackling pie charts in svg)
The additional custom function createDonutPathData()
is just a wrapper to generate a path d
value with outer and inner circle pathData.
The benefits of this approach:
- no need for additional
fill-rule
attributes (that might not be supported by all applications
- more compact output – no need for
<clipPath>
or <mask>
definitions
- no need for legacy syntax references like
xlink:href
– still required by a lot of graphic applications or image viewers.
Benefits of <mask>
- masks are great for complex shapes like dashed strokes
- support semi-transparency
Caveats of <mask>
- more expensive because they introduce an alpha transparency rendering context
- The pointer area is not "clipped" to the visible shape: A masked element responds to pointer events in the same way as the original/unmasked element, even in transparent/masked areas.