你可以使用JS从-SVG导出所有帧到光栅图像-JPG,PNG等吗?

3
众所周知,我们可以使用方法 SVGElement.pauseAnimations() 暂停 SVG 动画,并且我们也可以使用方法 SVGElement.setCurrentTime() 设置动画的当前时间-第一个参数是以秒为单位的时间。一切都很顺利,但我的问题是,您能否将暂停的帧导出为光栅图像-JPG、PNG。
示例(在此,我们创建了一个带有动画的 SVG,并在时间-0.958s 处暂停了动画)。

let svg = document.getElementById('testSvg');
svg.pauseAnimations();
svg.setCurrentTime(0.958); // keyframes times: 0s, 0.458s, 0.958s
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>

<svg id="testSvg" image-rendering="auto" baseProfile="basic" version="1.1" x="0px" y="0px" width="550" height="400" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
   <g id="Scene-1" overflow="visible" transform="translate(-56 -145.5)">
    <g display="none" id="Layer3_0_FILL">
     <path fill="#F00" stroke="none" d="M116.3 159.95L116.3 220.95 232.8 220.95 232.8 159.95 116.3 159.95Z" test="Scene 1"/>
     <animate attributeName="display" repeatCount="indefinite" dur="1s" keyTimes="0;.958;1" values="none;inline;inline"/>
   </g>
   <g display="none" id="Layer2_0_FILL">
     <path fill="#0F0" stroke="none" d="M116.3 159.95L116.3 220.95 232.8 220.95 232.8 159.95 116.3 159.95Z" test="Scene 1"/>
     <animate attributeName="display" repeatCount="indefinite" dur="1s" keyTimes="0;.458;.958;1" values="none;inline;none;none"/>
   </g>
   <g id="Layer1_0_FILL">
     <path fill="#0F0" stroke="none" d="M78.05 139.95L78.05 240.95 271 240.95 271 139.95 78.05 139.95Z" test="Scene 1"/>
     <animate attributeName="display" repeatCount="indefinite" dur="1s" keyTimes="0;.458;1" values="inline;none;none"/>
   </g>
 </g>
</svg>
 
</body>
</html>

现在,如果我们使用XMLSerializer将svg设置为图像的src。
let image = document.createElement( "image" );
let xml = new XMLSerializer().serializeToString(svg);
let data = "data:image/svg+xml;base64," + btoa(xml);
image.src = data;
document.body.appendChild();

图片已设置,但动画正常播放,现在已暂停。那么有没有一种方法可以暂停图像标签中的动画?或者使用已暂停的SVG元素并从中导出光栅图像。
1个回答

4
我能想到的最好方法是读取动画属性并为动画图像的克隆设置这些属性。这并不容易。SMIL动画可以针对XML和CSS属性进行操作,两者的语法不同。此外,您还需要仔细查看XML属性名称是否与属性名称匹配。这种情况并非总是如此。然后,基本过程如下:
  • While it is stated nowhere that pausing the animation is done asynchronuously, I was suprised to find it seems to be. You have to delay the reading of the attributes, for example with a setTimeout.

  • For CSS presentation attributes,

    window.getComputedStyle(element)[attribute]
    
  • For XML attributes,

    element[attribute].animVal.valueAsString || element[attribute].animVal
    

    This has another complexity: SVG has a dedicated interface for handling units. When you fetch a value that, for example, is a length, you need to get the attribute string with animVal.valueAsString. For number or string lists, or for transforms, it gets even more complicated, because these lists do not implement the Iterable interface.

这是一个与属性相关的示例,以下属性有效。我在<animate>元素上设置了attributeType以便于识别,并在目标元素上设置了id,因为您需要在原始元素(处于暂停动画状态)和其克隆体(不处于暂停状态)中都能识别它。我已经绘制到画布上,所以您立即可以获得光栅图像。

const svg = document.getElementById('testSvg');
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext("2d");

svg.pauseAnimations();
svg.setCurrentTime(0.35);

setTimeout(() => {
  // clone svg
  const copy = svg.cloneNode(true);
  // remove the animations from the clone
  copy.querySelectorAll('animate').forEach(animate => animate.remove());

  // query all animate elements on the original
  svg.querySelectorAll('animate').forEach(animate => {
    // target element in original
    const target = animate.targetElement;
    const attr = animate.getAttribute('attributeName');
    const type = animate.getAttribute('attributeType');
    // target element in copy
    const copyTarget = copy.getElementById(target.id);
    // differentiate attribute type
    if (type === 'XML') {
      const value = target[attr].animVal.valueAsString || target[attr].animVal;
      copyTarget.setAttribute(attr, value);
    } else if (type === 'CSS') {
      const value = window.getComputedStyle(target)[attr];
      copyTarget.style[attr] = value
    }
  });
  svg.unpauseAnimations();

  const xml = new XMLSerializer().serializeToString(copy);
  const data = "data:image/svg+xml;base64," + btoa(xml);
  const image = new Image();
  // image load is asynchronuous
  image.onload = () => ctx.drawImage(image, 0, 0);
  image.src = data;
},0);
<svg id="testSvg" width="200" height="200">
  <rect id="animationTarget" x="50" y="50" width="100" height="100"
        rx="0" fill="red">
    <animate attributeType="XML" attributeName="rx"
      dur="1s" keyTimes="0;0.5;1" values="0;50;0" />
    <animate attributeType="CSS" attributeName="fill"
      dur="1s" keyTimes="0;0.25;0.75" values="red;green;red" calcMode="discrete" />
  </rect>
</svg>
<canvas width="200" height="200"></canvas>


1
非常感谢,你帮了我很多!!!为了使用 animateTransform 标签来管理动画,当我获取它的-animVal时,我会收到 SVGTransformList,然后我遍历列表中的所有 SVGTransform 元素,然后获取其 SVGMatrix。然后将所有矩阵值-a、b、c、d、e、f组合成一个字符串,这样我就可以使用属性-additive="sum"进行多个动画。 - slaviboy
令人惊讶的是,执行此操作有多么复杂,毕竟浏览器已经可以搜索和暂停内置的SVG动画,并将外部SVG图像导出为画布,只是不能同时进行。 - Jacopofar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接