I try to create a HTML/CSS/JS funnel graph, with a simple front-end structure, in order to do so, I place funnel segments as a flex row:
document.addEventListener( 'DOMContentLoaded', function() {
let funnels = document.getElementsByClassName( 'funnel' );
for( let i = 0; i < funnels.length; i++ ) {
let segments = funnels[i].children[1].children;
for( let j = 0; j < segments.length; j++ ) {
const from = parseFloat( segments[j].getAttribute( 'data-from' ) );
const to = parseFloat( segments[j].getAttribute( 'data-to' ) );
const difference = ( from - to ) / 2;
const d = `M 0 ${50 - from * 50} Q 25 ${50 - from * 50} 50 ${50 - ( ( to + difference ) * 50 )} T 100 ${50 - to * 50} V 100 ${50 + to * 50} Q 75 ${50 + to * 50} 50 ${50 + ( ( to + difference ) * 50 )} T 0 ${50 + from * 50} V ${50 - from * 50} Z`;
const svg = '<svg id="fcm-' + i + '-' + j + '" viewBox="0 0 100 100" preserveAspectRatio="none" style="width: 100%; height: 100%;"><path d="' + d + '" fill="black"></path></svg>';
segments[j].style.setProperty( '--mask', ( 'url( #fcm-' + i + '-' + j + ' )' ) );
segments[j].innerHTML = svg;
}
}
} );
.funnel > div:last-child {
display: flex;
flex-direction: row;
height: 300px;
}
.funnel > div:last-child div {
background: green;
height: 100%;
position: relative;
width: 100%;
}
.funnel:not( .no-mask ) > div:last-child div {
mask-image: var( --mask );
mask-size: cover;
mask-repeat: no-repeat;
}
<div class="funnel">
<div>
Funnel (Mask)
</div>
<div>
<div data-from="1" data-to="0.66667"></div>
<div data-from="0.66667" data-to="0.25"></div>
<div data-from="0.25" data-to="0.1"></div>
</div>
</div>
<div class="funnel no-mask">
<div>
Funnel (No Mask)
</div>
<div>
<div data-from="1" data-to="0.66667"></div>
<div data-from="0.66667" data-to="0.25"></div>
<div data-from="0.25" data-to="0.1"></div>
</div>
</div>
For each segment in the funnel, a script will draw the relevant SVG path for this segment, and add it (with a generated ID) into the DIV. The new ID will be passed to the DIV as a CSS variable (an attribute like style="--mask: url( #fcm-0-0 );"
).
The issue I’m facing, is that when I try using the black area of the SVG to mask the parent DIV, it seems not working and all colors are disappearing.
My goal is to mask the green background of my DIV with the SVG shape for now, so later I can replace the background with a gradient.
I’m adding in my snippet 2x examples:
- The first one, is my issued code.
- The second one is an example of my graph result (without masking) so you can see the SVG funnel segments.
Can you help with this please?
2
Answers
I would define the SVG inside the mask variable instead of creating an SVG element then reference it.
TL:DR: Converting the mask to a data URL as suggested by Temani Afif is probably the easiest workaround.
You current code can’t work because your mask is not valid:
When referencing inlined SVG masks (appended to your HTML DOM) you need to wrap the mask shape in a
<mask>
element – currently you’re referencing the whole SVG element – won’t work.There is another catch:
mask-size
property is ignored for inlined mask elements. See also "How to properly scale an SVG mask within a container element?"As an alternative you may also opt for a responsive
<clipPath>