I've simplified an SVG animation I'm working on (which should look like a container filling up) to the example below which runs smoothly in Chrome, but is choppy/stutters in Firefox. It's an SVG with three layers: The first layer is a <mask>
for the last layer which is a red circle. The middle layer of the SVG is a grey circle. So the red circle sits on top of the grey circle and is made visible by the mask which gets animated via CSS:
#color-mask {
fill: white;
}
#color-mask path {
animation: waves .75s infinite linear;
}
@keyframes waves {
from {
transform: translateX(17rem);
}
to {
transform: translateX(-17rem);
}
}
#color-mask g {
animation: raise 6s infinite ease-in-out;
animation-direction: alternate;
}
@keyframes raise {
from {
transform: translateY(11rem);
}
to {
transform: translateY(-18rem);
}
}
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="400px" height="400px" viewBox="0 0 400 400">
<mask id="color-mask">
<g>
<path d="m 909.1,353.4 0,-67.6 c -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 -71,0 -107.2,14.8 -142.1,29 -34.8,14.2 -70.9,29 -141.7,29 -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 -71.1,0 -107.2,14.8 -142.1,29 -34.8,14.2 -70.9,29 -141.7,29 -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 l 0,632.2 1419,0 z"/>
</g>
</mask>
<g id="grey">
<circle id="top_grey" style="fill: rgb(180, 180, 180);" cx="200" cy="200" r="200"></circle>
</g>
<g id="color" mask="url(#color-mask)">
<circle id="top_color" style="fill: rgb(196, 3, 3);" cx="200" cy="200" r="200"></circle>
</g>
</svg>
The CSS animation translates the <mask>
both horizontally and vertically but at different rates.
I've also tried using a <clipPath>
instead of a <mask>
and get the same results. I get the same choppy/stuttering results in Firefox on Windows and Linux.
One very odd quirk I noticed in Firefox is that if I have the dev tools open, the animation occasionally will run smoothly. Firefox's dev tools also don't seem to indicate any problems, but I'm not an expert in SVG animations. Why is Firefox choking on this while Chrome isn't?
A different idea using CSS mask where you will not have the issue. I used the path your provided as mask. Simply make sure to set the correct value for the viewBox
.box {
display:inline-flex;
width:300px;
background: rgb(180, 180, 180);
border-radius:50%;
position:relative;
overflow:hidden;
}
.box:after {
content:"";
padding-top:100%;
}
.box:before {
content:"";
position:absolute;
left:0%;
width:200%;
height:30%;
bottom:-10%;
background:rgb(196, 3, 3);
-webkit-mask:url('data:image/svg+xml;utf8,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="-500 0 1100 900"><path d="m 909.1,353.4 0,-67.6 c -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 -71,0 -107.2,14.8 -142.1,29 -34.8,14.2 -70.9,29 -141.7,29 -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 -71.1,0 -107.2,14.8 -142.1,29 -34.8,14.2 -70.9,29 -141.7,29 -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 l 0,632.2 1419,0 z"/></svg>') top/100% auto;
mask:url('data:image/svg+xml;utf8,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="-500 0 1200 900"><path d="m 909.1,353.4 0,-67.6 c -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 -71,0 -107.2,14.8 -142.1,29 -34.8,14.2 -70.9,29 -141.7,29 -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 -71.1,0 -107.2,14.8 -142.1,29 -34.8,14.2 -70.9,29 -141.7,29 -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 l 0,632.2 1419,0 z"/></svg>') top/100% auto;
animation:raise 6s infinite ease-in-out alternate,waves .75s infinite linear;
}
@keyframes waves {
to {
transform: translateX(-50%);
}
}
@keyframes raise {
to {
height:160%;
}
}
<div class="box">
</div>
<div class="box" style="width:200px;">
</div>
To use only transformation you can add an extra element:
.box {
width:300px;
display:inline-block;
background: rgb(180, 180, 180);
border-radius:50%;
overflow:hidden;
}
.box div {
padding-top:100%;
position:relative;
animation: raise 6s infinite ease-in-out alternate;
}
.box div:before {
content:"";
position:absolute;
left:0%;
width:200%;
height:160%;
bottom:-10%;
background:rgb(196, 3, 3);
-webkit-mask:url('data:image/svg+xml;utf8,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="-500 0 1100 900"><path d="m 909.1,353.4 0,-67.6 c -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 -71,0 -107.2,14.8 -142.1,29 -34.8,14.2 -70.9,29 -141.7,29 -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 -71.1,0 -107.2,14.8 -142.1,29 -34.8,14.2 -70.9,29 -141.7,29 -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 l 0,632.2 1419,0 z"/></svg>') top/100% auto;
mask:url('data:image/svg+xml;utf8,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="-500 0 1200 900"><path d="m 909.1,353.4 0,-67.6 c -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 -71,0 -107.2,14.8 -142.1,29 -34.8,14.2 -70.9,29 -141.7,29 -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 -71.1,0 -107.2,14.8 -142.1,29 -34.8,14.2 -70.9,29 -141.7,29 -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 l 0,632.2 1419,0 z"/></svg>') top/100% auto;
animation:waves .75s infinite linear;
}
@keyframes waves {
to {
transform: translateX(-50%);
}
}
@keyframes raise {
from {
transform:translateY(120%);
}
}
<div class="box">
<div></div>
</div>
<div class="box" style="width:200px;">
<div></div>
</div>
Here is the SVG solution. Simply need to adjust the value of the first animation to get a perfect repeat:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="400px" height="400px" viewBox="0 0 400 400">
<mask id="color-mask" fill="white">
<g>
<path d="m 909.1,353.4 0,-67.6 c -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 -71,0 -107.2,14.8 -142.1,29 -34.8,14.2 -70.9,29 -141.7,29 -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 -71.1,0 -107.2,14.8 -142.1,29 -34.8,14.2 -70.9,29 -141.7,29 -70.8,0 -106.9,-14.7 -141.7,-29 -34.9,-14.3 -71,-29 -142.1,-29 l 0,632.2 1419,0 z">
<animateTransform attributeName="transform" attributeType="XML" type="translate" from="495" to="0" dur="0.75s" repeatCount="indefinite"/>
</path>
<animateTransform attributeName="transform" attributeType="XML" type="translate" values="0,150; 0,-300; 0,150" keyTimes="0; 0.5; 1" dur="12s" repeatCount="indefinite"/>
</g>
</mask>
<g id="grey">
<circle id="top_grey" style="fill: rgb(180, 180, 180);" cx="200" cy="200" r="200"></circle>
</g>
<g id="color" mask="url(#color-mask)">
<circle id="top_color" style="fill: rgb(196, 3, 3);" cx="200" cy="200" r="200"></circle>
</g>
</svg>
I spent way more time digging into this than I would have liked, the issue I had could not easily be solved by rethinking the animation itself. It turns out the culprit is that firefox will throttle animations on off-screen elements, and this includes masks and clipPaths. Unfortunately you can't position clipPaths or elements within them on their own, so the best solution that I have found is to split the svg into
two: one for the clipPath, that you position with position: fixed
so that it's always on screen (it's not rendered) and one for the actual content that will reference the clipPath.
Unfortunately you also have to account for the origin of the SVG being transformed by your animation, if your animation would transform it off screen then you're going to experience the problem again. In your particular example, if you position the SVG at least 18rems down from the top of the page and 17rem right from the left edge, as long as the top-left corner of the page is visible, the animation will play smoothly.
Testcase demonstrating that animated masks stutter in Firefox (apparently) stars when effective top-left mask element coordinate crosses actual page viewport's boundary. (View in "Full page" or better as attachment from bugreport 1693016):
<!doctype html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SVG mask animation crossing viewport boundary Firefox stutter test</title>
<style>
html { background: dimgray; color: snow; height: 1000px; width: 1000px; }
/*
https://stackoverflow.com/questions/62578638/why-is-this-svg-mask-animation-choppy-in-firefox-but-smooth-in-chrome
*/
.raise {
animation: raise 3s infinite ease-in-out alternate;
}
@keyframes raise {
from {
transform: translateY(50%);
}
to {
transform: translateY(-50%);
}
}
svg { border: 2px solid lime;}
.stutters-in-fx { border-color: red; }
text { fill: #000; stroke: #fff; stroke-width: .5; }
</style>
<script>
</script>
<svg class="stutters-in-fx" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200px" height="400px" viewBox="0 0 200 400">
<mask id="color-mask">
<rect class="raise" fill="white" width="400" height="400" />
</mask>
<g id="color" mask="url(#color-mask)">
<rect width="400" height="400" />
</g>
<text dx="10%" dy="10%">↓</text>
</svg>
<svg transform="rotate(180)" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200px" height="400px" viewBox="0 0 200 400">
<mask id="color-mask3">
<rect class="raise" fill="white" width="400" height="400" />
</mask>
<g id="color" mask="url(#color-mask3)">
<rect width="400" height="400" />
</g>
<text dx="10%" dy="10%">↓</text>
</svg>
<br>
<svg class="stutters-in-fx" transform="rotate(-90)" transform-origin="100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200px" height="400px" viewBox="0 0 200 400">
<mask id="color-mask2">
<rect class="raise" fill="white" width="400" height="400" />
</mask>
<g id="color" mask="url(#color-mask2)">
<rect width="400" height="400" />
</g>
<text dx="10%" dy="10%">↓</text>
</svg>
<br>
<svg transform="rotate(90) translate(-400 0)" transform-origin="100 300" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200px" height="400px" viewBox="0 0 200 400">
<mask id="color-mask4">
<rect class="raise" fill="white" width="400" height="400" />
</mask>
<g id="color" mask="url(#color-mask4)">
<rect width="400" height="400" />
</g>
<text dx="10%" dy="10%">↓</text>
</svg>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With