将 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 转换为图像的库? [关闭]

将 SVG 转换为 PNG,并将应用的图像作为 svg 元素的背景

如何将 PNG 图像转换为 SVG? [关闭]

如何将带有 SVG 的 div#WareHouse 转换为图像

坎夫 |将 SVG 转换为 Canvas 以输出为图像