【SDU Chart Team - Core】关于前端渲染svg的性能分析

性能

原始svg在浏览器引擎中的呈现可以有如下三种方式:

  1. Decode为DOM对象’<svg>’
  2. 通过base64编码为图像,然后在canvas上绘制
  3. 转化为blob对象,生成url,然后在canvas上绘制

关于选择这三种方式的理论基础:

  • DOM对象

    一种最直观最先能想到的方式。由于原始svg就是基于XML,并且能直接由Html使用,因此前端能够直接将原始svg的代码嵌入网页中。

    这种方法的瓶颈之一是解码,即原始svg转换为DOM对象的过程。使用DOMParser的parseFromString方法可以将一段XML转化为DOM对象,而这个过程就是一个计算热点。

    其二在于DOM的管理。一种我们最希望的方式是直接渲染图像本身,而不进行额外的操作,因为后端及核心已经完成了计算。换句话说,由于我们主要使用本核心来完成对svg的生成及改动,所以我们传入的svg对前端来说是静态的。但是使用DOM对象,就不得不需要对DOM进行维护,因为DOM对象默认是动态的。这既会带来维护的额外开销,也会带来渲染及绘图的额外成本。

  • Canvas + base64

    Canvas是一种目前逐渐流行的渲染方案,避免了DOM的额外开销而直接进行渲染。

    canvas+base64是一种流行的在canvas上渲染svg的方法,它利用的是浏览器支持渲染base64编码的机制。canvas虽然不支持绘制svg,但支持绘制图像。

    base64是一种常见的二进制编码方式,它可以被直接嵌入至Html文档中,便于在网页中表示小附件。浏览器支持将接收到的base64编码渲染为指定形式,例如图像。

    这个方法的思路就是首先将原始svg编码为base64图像,然后再将其嵌入到一个临时图片对象中,最后以canvas渲染。此过程经历了一次编码和解码,即先将svg编码为base64,再由canvas解码为图像。

    整个过程几乎只有计算,存储图像的开销是编码本身,因此十分节省内存。但是编码和解码过程是计算热点,十分耗时。

  • Canvas + blob

    这是另一种流行的在canvas上渲染svg的方法,它利用的是表示二进制图像字节流的Blob对象。

    Blob对象常用于视频播放、图像下载,和base64类似但不是嵌入文档中。Blob优势在于其体量比base64小的多,这也就意味着渲染和绘制的时间很短。使用blob,由于它直接支持svg,我们就不需要显式的对base64进行编码了。Blob对象可以直接生成一个url,所以我们可以直接将临时图像变量的src指向该url。

    但是Blob的问题在于当创建对象后,原始的二进制字节流即使被渲染,仍然会在内存中留下拷贝,这会导致内存开销相当大。随着网页中渲染对象的增多,拷贝也随之增多,一旦浏览器内存达到阈值,随之而来的是频繁的垃圾回收。这带来了额外的成本。

性能测试

使用相同的svg模板进行测试。该svg中除了包含路径、透明填充外,还包含图片。

  • DOM对象

    <input id='bt' type="button" onclick="add_img()" value="Add1"/>
    <div style="height: 800px; width: 1500px;"></div>
    
    <script>
    const new_img = (x, y) => {
        var div = document.querySelector('div');
        var html = '<svg xmlns="http://www.w3.org/2000/svg" width="467" height="462" id="source">'
        +  '<rect x="80" y="60" width="250" height="250" rx="20" style="fill:#ff0000; stroke:#000000;stroke-width:2px;"/>'
        +  '<rect x="140" y="120" width="250" height="250" rx="40" style="fill:#0000ff; stroke:#000000; stroke-width:2px; fill-opacity:0.7;"/>'
        +  '<image xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="data:image/png;base64,<某个图片的base64>" width="230px" x="80" y="180"/>'
        +'</svg>';
        let svg = new DOMParser().parseFromString(html, 'text/html').querySelector('#source');
        svg.style.position = 'absolute';
        svg.style.left = x + 'px';
        svg.style.top = y + 'px';
        svg.style.pointerEvents = 'none';
        div.appendChild(svg);
    }
    
    new_img(0, 0);
    window.cnt = 0;
    
    const add_img = () => {
        x = Math.random() * 1420;
        y = Math.random() * 720;
    
        new_img(x, y);
        var btn = document.querySelector('#bt');
        window.cnt++;
        btn.value = 'Add' + window.cnt;
        console.log(btn);
    }
    </script>
    
  • Canvas + base64

    <input id='bt' type="button" onclick="add_img()" value="Add1"/>
    <canvas height="800px" width="1500px"></canvas>
    
    <script>
    const new_img = (x, y) => {
        var img = document.createElement('img');
        var canvas = document.querySelector('canvas');
        img.style.width  = '462px';
        img.style.height = '467px';
        var html = '<svg xmlns="http://www.w3.org/2000/svg" width="467" height="462" id="source">'
        +  '<rect x="80" y="60" width="250" height="250" rx="20" style="fill:#ff0000; stroke:#000000;stroke-width:2px;"/>'
        +  '<rect x="140" y="120" width="250" height="250" rx="40" style="fill:#0000ff; stroke:#000000; stroke-width:2px; fill-opacity:0.7;"/>'
        +  '<image xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="data:image/png;base64,<某个图片的base64>" width="230px" x="80" y="180"/>'
        +'</svg>';
        img.src = 'data:image/svg+xml;base64,' + window.btoa(html);
        img.onload = function() {
            canvas.getContext('2d').drawImage(img, x, y);
        }
    }
    
    new_img(0, 0);
    
    window.cnt = 1;
    const add_img = () => {
        x = Math.random() * (1500-467);
        y = Math.random() * (800-462);
    
        new_img(x, y);
        var btn = document.querySelector('#bt');
        window.cnt++;
        btn.value = 'Add' + window.cnt;
    }
    </script>
    
  • Canvas + blob

    <input id='bt' type="button" onclick="add_img()" value="Add1"/>
    <canvas height="800px" width="1500px"></canvas>
    
    <script>
    const new_img = (x, y) => {
        var img = document.createElement('img');
        var canvas = document.querySelector('canvas');
        img.style.width  = '462px';
        img.style.height = '467px';
        
        var html = '<svg xmlns="http://www.w3.org/2000/svg" width="467" height="462" id="source">'
        +  '<rect x="80" y="60" width="250" height="250" rx="20" style="fill:#ff0000; stroke:#000000;stroke-width:2px;"/>'
        +  '<rect x="140" y="120" width="250" height="250" rx="40" style="fill:#0000ff; stroke:#000000; stroke-width:2px; fill-opacity:0.7;"/>'
        +  '<image xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="data:image/png;base64,<某个图片的base64>" width="230px" x="80" y="180"/>'
        +'</svg>';
        
        var svg = new Blob([html], {type:"image/svg+xml;charset=utf-8"}),
            domURL = self.URL || self.webkitURL || self,
            url = domURL.createObjectURL(svg),
            img = new Image;
    
        img.onload = function () {
            canvas.getContext('2d').drawImage(this, x, y);
            domURL.revokeObjectURL(url);
        };
        img.src = url;
    }
    
    new_img(0, 0);
    
    window.cnt = 1;
    const add_img = () => {
        x = Math.random() * (1500-467);
        y = Math.random() * (800-462);
    
        new_img(x, y);
        var btn = document.querySelector('#bt');
        window.cnt++;
        btn.value = 'Add' + window.cnt;
    }
    </script>
    

模拟点击事件绘制svg图像,重复500次,得到在压力环境下的性能测试结果:

  • 耗时

    忽略Idle时间,以下是总运算耗时:

    离中心越远则耗时越短。

  • 总耗时、绘图方法耗时

  • 计算热点

    DOMCanvas+base64Canvas+blob
    append: 6972.2msdrawImage: 3005.9msblob: 874.1ms
    parse: 4373.8msbtoa: 2458.8msgc: 579.7ms

结论

方案优点缺点瓶颈
DOM对象
  • 前端代码简单
  • 自动加载
  • 自动渲染
  • 较长的加载时间
  • DOM的维护较为缓慢
  • 渲染缓慢
  • 绘制时间相对较长
DOM维护、decode
Canvas+base64
  • 加载的工作交给btoa
  • 内存消耗小
  • 渲染、绘制时间较短
  • 脚本时间过长
  • 一次encode和一次decode过程
一次encode(btoa)一次encode(drawImage)
Canvas+blob
  • 加载的工作交给blob
  • 渲染、绘制时间最短
  • 脚本时间较短
  • 内存消耗十分大
  • 大量时间用于GC
GC

选择

  1. 渲染规模较小时,使用DOM对象
  2. 渲染规模大,但内存足够时,使用Canvas+blob
  3. 渲染规模打,但内存不足时,使用Canvas+base64
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值