将 SVG 动画转换为图像序列
Posted
技术标签:
【中文标题】将 SVG 动画转换为图像序列【英文标题】:Convert SVG animation to image sequence 【发布时间】:2020-03-15 05:52:18 【问题描述】:我正在尝试从 SVG 动画渲染图像。 这是我的代码。https://jsfiddle.net/d3bhfa1L/2/
它生成图像序列,但无法从 SVG 捕获动画。
我实际上想将 SVG 动画转换为 mp4 格式,但找不到任何直接的解决方案。所以尝试将SVG动画转换为图像序列,然后将图像序列转换为mp4视频。
/* uniformly named URL object */
var DOMURL = window.URL || window.webkitURL || window;
/* our snapshotting class */
function svg_snapshot(svg_ref, fps, seconds)
/* DOM object element */
this.svg_ref = svg_ref;
/* svg xml root */
// this.svg_root = svg_ref.contentDocument.documentElement;
this.svg_root = svg_ref;
/* frames per second */
this.fps = fps;
/* total animation duration in seconds */
this.seconds = seconds;
this.svg_root.pauseAnimations();
this.make_step = function (step, time)
if (time > this.seconds * 1000)
// animation ended
return false;
/* pause for snapshot */
this.svg_root.pauseAnimations();
/* save actual svg state as XML */
var svg_xml = this.svg_root.outerhtml;
/* disable animation elements with simple replacing */
svg_xml = svg_xml.replace(new RegExp('<animate', 'g'), '<not_anim');
/* save as blob */
var svg_data = new Blob([svg_xml],
type: 'image/svg+xml'
);
/* create data url (creates browsers interal blob: data link) */
var data_url = DOMURL.createObjectURL(svg_data);
/* create bitmap */
var img = new Image();
/* save class reference */
var self = this;
/* mount load process */
img.onload = function ()
self.make_step_next(step, time, this);
;
/* set image url */
img.src = data_url;
;
this.make_step_next = function (step, time, img)
/* create canvas */
var canvas = document.createElement("canvas");
canvas.setAttribute("width", this.svg_ref.clientWidth);
canvas.setAttribute("height", this.svg_ref.clientHeight);
canvas.style.border = "1px solid black";
/* get canvas 2d contextr */
var ctx = canvas.getContext('2d');
/* drav loaded image onto it */
ctx.drawImage(img, 0, 0);
/* here we can get dataURL (base64 encoded url with image content) */
var dataURL = canvas.toDataURL('image/png');
/*
and here you can do whatever you want - send image
by ajax (that base64 encoded url which you can decode
on serverside) or draw somewhere on page
*/
var finalImg = document.createElement("IMG");
finalImg.src = dataURL;
finalImg.style.border = "1px solid black";
document.body.appendChild(finalImg);
/*
let animation continue - before image is loaded, the
animation is paused - by this is achieved perfect
timing in this serial process
*/
this.svg_root.unpauseAnimations();
var self = this;
var interval = 1000 / this.fps; // one frame interval
setTimeout(function ()
self.make_step(step + 1, time + interval);
, interval);
;
/* usage - parameters: SVG DOM ref, frames per second, duration in seconds */
var item_ref = new svg_snapshot(document.getElementById('Content-R'), 30, 1);
/* start snapshotting */
item_ref.make_step(0, 0);
【问题讨论】:
如果你想把SVG转成mp4,可以不用svg to mp4之类的吗? @tmach 我想通过编码转换。 svg_to_mp4 不起作用,或者至少似乎不适用于包含 CSS 动画的 SVG。我自己试了两个。其中包括我自己的动画引擎生成的 CSS 动画命令。该网站刚刚制作了静态图片。是的,我确实选择了“MP4”,而不是“JPG”! 【参考方案1】:所有的 css 都不适用于 svg 元素,它们具有有效的特定属性。 尝试使用 gsap 动画引擎。
尝试使用 SVG
使用 gsap 为 svg 对象设置动画
在 gsap 的更新事件中,导出 svg 帧(您可以使用 SVG.js 库) 可以使用以下函数导出:
function saveSvg(svgData)
var svgBlob = new Blob([svgData], type: "image/svg+xml;charset=utf-8");
console.log(svgBlob);
var svgUrl = URL.createObjectURL(svgBlob);
var downloadLink = document.createElement("a");
downloadLink.href = svgUrl;
downloadLink.download = "newesttree.svg";
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
// Call it like this(USING SVG.JS LIBRARY):
let main_svg = SVG('#id_of_your_svg_root');
saveSvg(main_svg.svg());
上面会下载svg文件
使用 ffmpeg 等 svg 编码器将文件组合成视频
【讨论】:
【参考方案2】:我知道这已经一岁了,但现在我需要解决这个问题,因为实际的 chrome 在动画期间停止更新 dom 中的 SVG 元素属性(路径数据等),所以代码没有记录器工作。 (顺便说一句,我是示例代码的作者)
现在可能没有办法从 SVG SMIL 动画中捕捉帧。但是在 SVG CSS 动画中,window.getComputedStyle 是可能的。
我看到你的动画是这样完成的 - 所以我用它来解决这个问题,这里是更新的工作示例(在 Chrome 中):
/* uniformly named URL object */
var DOMURL = window.URL || window.webkitURL || window;
/* our snapshotting class */
function svg_snapshot(svg_ref, fps, seconds, correction)
var self = this;
/* DOM object element */
this.svg_ref = svg_ref;
/* svg xml root */
//this.svg_root = svg_ref.contentDocument.documentElement;
this.svg_root = svg_ref;
/* frames per second */
this.fps = fps;
/* total animation duration in seconds */
this.seconds = seconds;
/* frame msec correction (to fix getComputedStyle bug which shifts a little animation) */
this.correction = correction;
/* list of possible elements (it probably not complete) */
this.elements = ['path', 'circle', 'g'];
/* list of possible styles */
this.styleKeys = ['d', 'fill', 'fill-opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-misterlimit', 'stroke-opacity', 'stroke-width', 'opacity', 'transform'];
this.svg_element_list = ;
/* load specific element lists from svg */
this.elements.forEach(function(elementName)
var sf, els = self.svg_root.getElementsByTagName(elementName);
self.svg_element_list[elementName] = [];
for (sf = 0; sf < els.length; sf++)
self.svg_element_list[elementName].push(
"ref": els.item(sf),
"style": null
);
);
this.toggle_pause = function(flag)
var self = this;
/* pause and save computed style when paused */
this.elements.forEach(function(elementName)
self.svg_element_list[elementName].forEach(function(c)
c["ref"].style.animationPlayState = flag ? "paused" : "";
c["style"] = flag ? getComputedStyle(c["ref"]) : null;
);
);
;
this.toggle_pause(true);
this.make_step = function(step)
/* pause for snapshot */
this.toggle_pause(true);
if (step > this.fps * this.seconds)
// animation ended
return false;
/* save object reference */
var self = this;
/* capture snapped svg xml string */
var xml_data = this.svg_root.outerHTML;
/* parse snapped svg frame as object DOM structure */
var svg_xml = (new DOMParser()).parseFromString(xml_data, "text/xml");
/* remove style elements (it may damage snapshotted svg and will be rewritten imidietly by inline styles) */
var sf, styleEls = svg_xml.getElementsByTagName('style');
for (sf = styleEls.length - 1; sf >= 0; sf--)
styleEls[sf].parentNode.removeChild(styleEls[sf]);
/* loop trough possible elements and copy */
this.elements.forEach(function(elementName)
/* capture the processed style */
/* paths in snapped svg */
var elsSnap = svg_xml.getElementsByTagName(elementName);
for (var sf = 0; sf < self.svg_element_list[elementName].length; sf++)
var cStyle = self.svg_element_list[elementName][sf]["style"];
/* enforce computed styles state */
self.styleKeys.forEach(function(stKey)
elsSnap[sf].style[stKey] = cStyle.getPropertyValue(stKey);
);
);
/* save as blob with a help of XMLserializer (using object DOM structure) */
var svg_data = new Blob([(new XMLSerializer()).serializeToString(svg_xml)], type: 'image/svg+xml');
/* create data url (creates browsers interal blob: data link) */
var data_url = DOMURL.createObjectURL(svg_data);
/* create bitmap */
var img = new Image();
/* mount load process */
img.onload = function()
self.make_step_next(step, this);
;
/* set image url */
img.src = data_url;
;
this.make_step_next = function(step, img)
var self = this;
/* create canvas */
var canvas = document.createElement("canvas");
canvas.setAttribute("width", this.svg_ref.clientWidth);
canvas.setAttribute("height", this.svg_ref.clientHeight);
canvas.style.border = "1px solid black";
/* get canvas 2d contextr */
var ctx = canvas.getContext('2d');
/* drav loaded image onto it */
ctx.drawImage(img, 0, 0);
/* here we can get dataURL (base64 encoded url with image content) */
var dataURL = canvas.toDataURL('image/png');
/* and here you can do whatever you want - send image by ajax (that base64 encoded url which you can decode on serverside) or draw somewhere on page */
var finalImg = document.createElement("IMG");
finalImg.style.border = "1px solid black";
/* add frame number to image alt */
/* it might represent file name etc. */
var frameId = "000" + step;
finalImg.alt = "frame" + frameId.substr(frameId.length - 3);
finalImg.onload = function()
document.getElementById("image_stack").appendChild(finalImg);
self.make_step_go(step + 1);
;
finalImg.src = dataURL;
;
this.make_step_go = function(step)
var self = this;
var interval = 1000 / this.fps - this.correction; // one frame interval
setTimeout(function()
self.make_step(step);
, interval);
/* let animation continue - before image is loaded, the animation is paused - by this is achieved perfect timing in this serial process */
this.toggle_pause(false);
;
Jsfiddle:https://jsfiddle.net/p275rmah/
注意:window.getComputedStyle 方法有一个错误或意外行为,它会在每次读取属性值时以某种方式向前移动动画。除了对时间间隔进行一些更正之外,我没有找到任何其他方法来防止这种情况发生 - 这将针对每个不同的 SVG。
代码可能不适用于所有 SVG 动画 - 但有些情况可以通过更新 this.styleKeys 和 this.elements 属性轻松解决。
【讨论】:
以上是关于将 SVG 动画转换为图像序列的主要内容,如果未能解决你的问题,请参考以下文章
在 SVG 和画布之间,哪个更适合操作/动画多个图像?也许两者都没有,只使用 css3 转换?
将 SVG 转换为 PNG,并将应用的图像作为 svg 元素的背景