【问题标题】:Convert SVG animation to image sequence将 SVG 动画转换为图像序列
【发布时间】: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”!

标签: javascript html svg svg-animate


【解决方案1】:

所有的 css 都不适用于 svg 元素,它们具有有效的特定属性。 尝试使用 gsap 动画引擎。

  1. 尝试使用 SVG

  2. 使用 gsap 为 svg 对象设置动画

  3. 在 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());
    
  4. 上面会下载svg文件

  5. 使用 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 属性轻松解决。

    【讨论】:

      猜你喜欢
      • 2010-11-03
      • 2016-09-18
      • 2018-03-06
      • 1970-01-01
      • 2012-12-04
      • 1970-01-01
      • 1970-01-01
      • 2023-03-04
      • 1970-01-01
      相关资源
      最近更新 更多