Question

Is it possible to disable svg's animate element for prefers-reduced-motion media query?

I've got an svg with animation done using animateTransform element

<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
  <g>
    <circle cx="50" cy="50" r="30" fill="red"/>
    <animateTransform
      attributeName="transform"
      dur="1s"
      type="translate"
      repeatCount="indefinite"
      from="100 -25"
      to="-100 25"
    />
  </g>
</svg>

I would like to disable this animation if the user has prefers-reduced-motion turned on

I tried disabling a few properties via css but it didn't work

<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
  <g>
    <circle cx="50" cy="50" r="30" fill="red"/>
    <animateTransform
      attributeName="transform"
      dur="1s"
      type="translate"
      repeatCount="indefinite"
      from="100 -25"
      to="-100 25"
    />
  </g>
  <style>
    animateTransform {
      display: none;
      dur: 0s;
      repeatCount: 0;
    }
  </style>
</svg>

 3  57  3
1 Jan 1970

Solution

 4

CSS can't access/manipulate any SMIL animation related properties
(as specified in an <animate> element).

But you could check the current preferred motion configuration via JavaScript.

let reducedMotion = window.matchMedia(`(prefers-reduced-motion: reduce)`) === true || window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;
console.log('reducedMotion', reducedMotion)

if (reducedMotion) {
  stopSmilAnimations();
} 


function stopSmilAnimations(){
  let svgs = document.querySelectorAll("svg");
  svgs.forEach(svg=>{
      svg.pauseAnimations();
  })
}
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
  <g>
    <circle cx="50" cy="50" r="30" fill="red"/>
    <animateTransform
      attributeName="transform"
      dur="1s"
      type="translate"
      repeatCount="indefinite"
      from="100 -25"
      to="-100 25"
    />
  </g>
</svg>

If prefers-reduced-motion is detected you can run a function pausing all svg animations via svg.pauseAnimations().

Disabling animations via JS may also be a good approach in terms of accessibility or selective preferences:
Some users may not have set their browser correctly to avoid/minimize animations. A user defined configuration similar to privacy settings or something like a Dark-Mode toggle could immediately change the preferred settings.

Disable animations via display:content

As commented by Kaiido we could disable this animation targeting the <g> element by applying display:content to the group.

The rendering will behave as if we had removed the <g> element from the DOM but keeping it's children. The <animateTransform> element will lose its target.

svg{
  border: 1px solid red
}


g {
  display: contents;
}
<svg width="100" height="100" viewBox="0 0 200 100" fill="none" xmlns="http://www.w3.org/2000/svg">
  <g>
    <circle cx="50" cy="50" r="30" fill="red"/>
        <animateTransform
      attributeName="transform"
      dur="1s"
      type="translate"
      repeatCount="indefinite"
      from="100 -25"
      to="-100 25"
    />
  </g>
  
  <g>
    <circle  cx="0" cy="50" r="15" fill="blue" stroke="black" stroke-width="1">
    <animate
      attributeName="cx"
      from="0"
      to="500"
      dur="5s"
      repeatCount="indefinite" />
  </circle>
    </g>
  
</svg>

As you can see, the first animation is stopped. The 2. is unaffected (still playing) because the <animate> element is targeting the <circle> element. So you can't apply this CSS-only approach to all kind of SMIL animations.

2024-07-11
herrstrietzel