Convert animateTransform SVG 的转换为动画 .gif 图像

问题描述 投票:0回答:1

tl;dr 是否可以将 SVG 转换“转换”为动画 GIF 图像?


我有这个简单的加载器,一个动画齿轮,它使用

animateTransform
转换来“实现其目标”(旋转齿轮):

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.1" baseProfile="full" width="213" height="180" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <image id="gear1" x="0" y="0" width="92" height="92" xlink:href="gear1.svg"/>
    <image id="gear2" x="57.5" y="57.5" width="122" height="122" xlink:href="gear2.svg" transform="rotate(16.875 118.5 118.5)"/>
    <image id="gear3" x="149" y="24" width="64" height="64" xlink:href="gear3.svg"/>

    <animateTransform xlink:href="#gear1" attributeName="transform" attributeType="XML" type="rotate" from="0 46 46" to="360 46 46" dur="6s" repeatCount="indefinite"/>
    <animateTransform xlink:href="#gear2" attributeName="transform" attributeType="XML" type="rotate" to="16.875 118.5 118.5" from="376.875 118.5 118.5" dur="8s" repeatCount="indefinite"/>
    <animateTransform xlink:href="#gear3" attributeName="transform" attributeType="XML" type="rotate" from="0 181 56" to="360 181 56" dur="4s" repeatCount="indefinite"/>
</svg>

有没有办法将这段XML代码(SVG文件)“转换”成动画.gif?

到目前为止我尝试过的服务:

我想为它提供开发人员的方法,但使用代码(控制台/Javascript 脚本)将此 SVG 代码转换为一个图像或一系列图像 - 如果可能的话。

animation svg transformation gif file-conversion
1个回答
2
投票

对 SVG 进行一些修改后,您可以使用以下代码生成一系列图像。然后可以使用 ImageMagick 等工具下载图像并将其转换为 Gif(您需要类似以下命令:如何使用 imagemagick 制作高质量的动画图像 - Stack Overflow)。

你可以看到我对 SVG 做了一些更改。因此,只有当 animateTransform 元素是需要动画的元素的子元素时,此脚本才有效(因此请去掉 ids 和 href 属性)。在你的情况下, animateTransform 元素可能(我没有测试,它在这里不起作用)是图像元素的子元素,但由于跨源问题,所有文件都需要从同一个网络服务器运行。另一种方法是将 gear1、2 和 3 SVG 嵌入到主 SVG 中(然后让 animateTransform 元素成为每个 svg 元素的子元素(是的,将 SVG 元素直接嵌入到主 SVG 中)。

该脚本生成大量链接。每个链接都引用一个图像。右键单击该链接并选择“链接另存为...”并将所有图像保存在一个目录中。现在你可能不需要所有这些,因为齿轮不需要完全旋转来制作无限循环 gif。

var svgcontainer, svg, canvas, ctx, output, interval;
var num = 101;

const nsResolver = prefix => {
  var ns = {
    'svg': 'http://www.w3.org/2000/svg',
    'xlink': 'http://www.w3.org/1999/xlink'
  };
  return ns[prefix] || null;
};

const takeSnap = function() {
  // get all animateTransform elements
  let animateXPath = document.evaluate('//svg:*[svg:animateTransform]', svg, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

  // store all animateTransform animVal.matrix in a dataset attribute
  Object.keys([...Array(animateXPath.snapshotLength)]).forEach(i => {
    let node = animateXPath.snapshotItem(i);
    let mStr = [...node.transform.animVal].map(animVal => {
      let m = animVal.matrix;
      return `matrix(${m.a} ${m.b} ${m.c} ${m.d} ${m.e} ${m.f})`;
    }).join(' ');
    node.dataset.transform = mStr;
  });

  // get all animate elements
  animateXPath = document.evaluate('//svg:animate', svg, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

  // store all animate properties in a dataset attribute on the target for the animation
  Object.keys([...Array(animateXPath.snapshotLength)]).forEach(i => {
    let node = animateXPath.snapshotItem(i);
    let propName = node.getAttribute('attributeName');
    let target = node.targetElement;
    let computedVal = getComputedStyle(target)[propName];
    target.dataset[propName] = computedVal;
  });

  // create a copy of the SVG DOM
  let parser = new DOMParser();
  let svgcopy = parser.parseFromString(svg.outerHTML, "application/xml");

  // find all elements with a dataset attribute
  animateXPath = svgcopy.evaluate('//svg:*[@*[starts-with(name(), "data")]]', svgcopy, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

  // copy the animated property to a style or attribute on the same element
  Object.keys([...Array(animateXPath.snapshotLength)]).forEach(i => {
    let node = animateXPath.snapshotItem(i);
    // for each data-
    for (key in node.dataset) {
      if (key == 'transform') {
        node.setAttribute(key, node.dataset[key]);
      } else {
        node.style[key] = node.dataset[key];
      }
    }
  });

  // find all animate and animateTransform elements from the copy document
  animateXPath = svgcopy.evaluate('//svg:*[starts-with(name(), "animate")]', svgcopy, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

  // remove all animate and animateTransform elements from the copy document
  Object.keys([...Array(animateXPath.snapshotLength)]).forEach(i => {
    let node = animateXPath.snapshotItem(i);
    node.remove();
  });

  // create a File object
  let file = new File([svgcopy.rootElement.outerHTML], 'svg.svg', {
    type: "image/svg+xml"
  });
  // and a reader
  let reader = new FileReader();

  reader.addEventListener('load', e => {
    /* create a new image assign the result of the filereader
    to the image src */
    let img = new Image();
    // wait got load
    img.addEventListener('load', e => {
      // update canvas with new image
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.fillStyle = 'white';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(e.target, 0, 0);
      // create PNG image based on canvas
      //let img = new Image();
      //img.src = canvas.toDataURL("image/png");
      //output.append(img);
      let a = document.createElement('A');
      a.textContent = `Image-${num}`;
      a.href = canvas.toDataURL("image/png");
      a.download = `Image-${num}`; 
      num++;
      output.append(a);
    });
    img.src = e.target.result;
  });
  // read the file as a data URL
  reader.readAsDataURL(file);
};

document.addEventListener('DOMContentLoaded', e => {
  svgcontainer = document.getElementById('svgcontainer');
  canvas = document.getElementById('canvas');
  output = document.getElementById('output');
  ctx = canvas.getContext('2d');

  let parser = new DOMParser();
  let svgdoc = parser.parseFromString(svgcontainer.innerHTML, "application/xml");
  canvas.width = svgdoc.rootElement.getAttribute('width');
  canvas.height = svgdoc.rootElement.getAttribute('height');

  //svgcontainer.innerHTML = svgdoc.rootElement.outerHTML;
  svg = svgcontainer.querySelector('svg');
  //console.log(svg);

  // set interval
  interval = setInterval(takeSnap, 50);

  // get all 
  let animateXPath = document.evaluate('//svg:*[starts-with(name(), "animate")]', svg, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

  let animationArr = Object.keys([...Array(animateXPath.snapshotLength)]).map(i => {
    let node = animateXPath.snapshotItem(i);
    return new Promise((resolve, reject) => {
      node.addEventListener('endEvent', e => {
        resolve();
      });
    });
  });
  Promise.all(animationArr).then(value => {
    clearInterval(interval);
  });
});
<div style="display:flex">
  <div id="svgcontainer">
    <svg width="213" height="180" xmlns="http://www.w3.org/2000/svg">
      <rect width="92" height="92" fill="orange">
        <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 46 46" to="360 46 46" dur="6s" repeatCount="1"/>
      </rect>
      <rect x="57.5" y="57.5" width="122" height="122" fill="green">
        <animateTransform attributeName="transform" attributeType="XML" type="rotate" to="16.875 118.5 118.5" from="376.875 118.5 118.5" dur="8s" repeatCount="1"/>
      </rect>
      <rect x="149" y="24" width="64" height="64" fill="navy">
        <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 181 56" to="360 181 56" dur="4s" repeatCount="1"/>
      </rect>
    </svg>
  </div>
  <canvas id="canvas" width="200" height="200"></canvas>
</div>
<p>Exported PNGs:</p>
<div id="output"></div>

© www.soinside.com 2019 - 2024. All rights reserved.