首页 > 其他分享 >浅析 js 实现网页截图的两种方式

浅析 js 实现网页截图的两种方式

时间:2022-11-15 19:01:24浏览次数:76  
标签:截图 gt svg canvas js lt foreignObject 浅析

参考:https://juejin.cn/post/6844903465756393486

        <p>Web端的截图(生成图片)并不算是个高频的需求,资料自然也不算多,查来查去,也不过Canvas 和 SVG两种实现方案,原理大概相似,都非真正义上的截图而是把DOM转为图片,然而实现方式却截然不同。</p>
        <h3 id="user-content-canvas" data-id="heading-0">Canvas 实现</h3>
        <p>如何将dom转换成canvas图片?自然是要一点点画到canvas里,想想都是件麻烦事。通过分析github的知名截图库<a href="https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fniklasvh%2Fhtml2canvas" target="_blank" title="https://github.com/niklasvh/html2canvas" ref="nofollow noopener noreferrer"> niklasvh/html2canvas</a> (7k+ star)的源码,梳理了其大致的思路:</p>
        <ul>
            <li>
                <p>递归取出目标模版的所有DOM节点,填充到一个<code>rederList</code>,并附加是否为顶层元素/包含内容的容器 等信息</p>
            </li>
            <li>
                <p>通过<code>z-index</code> <code>postion</code> <code>float</code>等css属性和元素的层级信息将<code>rederList</code>排序,计算出一个canvas的renderQueue</p>
            </li>
            <li>
                <p>遍历renderQueue,将css样式转为<code>setFillStyle</code>可识别的参数,依据nodeType调用相对应canvas方法,如文本则调用<code>fillText</code>,图片<code>drawImage</code>,设置背景色的div调用<code>fillRect</code>等 </p>
            </li>
            <li>
                <p>将画好的canvas填充进页面</p>
            </li>
        </ul>
        <p>无论是排序优先级的计算还是从css到canvas的转换,毫无疑问都是些巨麻烦的事,尤其是放在真实的业务场景里,DOM模版中往往会包含复杂的样式与排版,html2canvas 足足用了20多个js来实现这层转换,复杂成度可见一斑。索性,我们不需要再重新造一遍轮子。</p>
        <p>使用canvas转化的话灵活性较高,环境依赖上也只需要确保浏览器支持canvas就可以了,但它有个显著的缺点:慢。原因自然是因为大量的计算与递归调用,这是无可避免的。不过html2canvas代码中大量使用了Promise,所以html2canvas 支持异步操作。</p>
        <p>限制:</p>
        <ul>
            <li>
                <p>无法跨域跨域资源</p>
            </li>
            <li>
                <p>无法渲染iframe,flash等内容,但目前支持svg</p>
            </li>
        </ul>
        <p>值得一提的是,尽管<code>html2canvas</code>主页表示它还处于实验室环境,但自14年起便已经被Twitter 等用在了生产环境,所以虽然有诸多限制,稳定性应该还是保障的。</p>
        <p>canvas如此复杂,那么有没有一种更简单的方法呢?</p>
        <p>自然是有的,那便是SVG</p>
        <h3 id="user-content-svg" data-id="heading-1">SVG实现</h3>
        <p>首先,svg本来就是矢量图形;其次,svg是可以用xml描述的;再其次,用来描述svg的标签里有个 <code>foreignObject</code>标签,这个标签可以加载其它命名空间的xml(xhtml)文档。也就是说,如果使用svg的话,我们不再需要一点点的遍历,转换节点;不用再计算复杂的元素优先级,只需要一股脑的将要渲染的DOM扔进<code>&lt;foreignObject&gt;&lt;/foreignObject&gt;</code>就好了,剩下的就交给浏览器去渲染。</p>
        <p>让我们理一理思路:</p>
        <ul>
            <li>
                <p>首先,我们要声明一个基础的svg模版,这个模版需要一些基础的描述信息,最重要的,它要有<code>&lt;foreignObject&gt;&lt;/foreignObject&gt;</code>这对标签</p>
            </li>
            <li>
                <p>将要渲染的DOM模版模版嵌入<code>foreignObject</code></p>
            </li>
            <li>
                <p>利用<code>Blob</code>构建svg图像</p>
            </li>
            <li>
                <p>取出URL,赋值给<img src="" loading="lazy" class="medium-zoom-image"></p>
            </li>
        </ul> <pre><code class="hljs language-css copyable" lang="css">    &lt;<span class="hljs-selector-tag">div</span> id='text'&gt;
    &lt;<span class="hljs-selector-tag">h1</span> style="<span class="hljs-attribute">background-color</span>: <span class="hljs-number">#ccc</span>;<span class="hljs-attribute">width</span>: <span class="hljs-number">200px</span>;<span class="hljs-attribute">height</span>: <span class="hljs-number">200px</span>;" &gt;Hello World&lt;/<span class="hljs-selector-tag">h1</span>&gt;
&lt;/<span class="hljs-selector-tag">div</span>&gt;

复制代码


复制代码
//此代码仅在chrome测试下通过
function html2Svg (domStr) {
//创建模版字符串
var svgXML=
&lt;svg <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span> width=<span class="hljs-string">"200"</span> height=<span class="hljs-string">"200"</span>&gt; &lt;foreignObject <span class="hljs-attr">width</span>=<span class="hljs-string">"100%"</span> height=<span class="hljs-string">"100%"</span>&gt;<span class="hljs-variable">${generateXML(html)}</span>&lt;/foreignObject&gt; &lt;/svg&gt;
//利用Blob创建svg
var svg = new Blob([svgXML], {type: 'image/svg+xml'})
//利用DOMURL.createObjectURL取出对象
var url = window.URL.createObjectURL(svg);
var img = new Image()
img.src = url
return img
}

// 由于foreignObject只能引用XML文档,
// 所以我们需要对DOM进行格式化
function generateXML (domStr) {
var doc = document.implementation.createHTMLDocument('');
doc.write(html);
doc.documentElement.setAttribute('xmlns', doc.documentElement.namespaceURI);
doc = parseStyle(doc)
console.log(doc)
html = (new XMLSerializer).serializeToString(doc).replace('<!DOCTYPE html>','');
return html
}
复制代码


可以看到按这个思路来实现非常简单,并且没有了复杂的计算和递归,渲染速度自然要优于前者。然而使用svg,需要考虑诸多的限制问题。一个最为严肃的问题在于:SVG无法加载外部资源,也就是说,在svg里面,无论是还是
或者css中的背景图,这些资源都是无法加载的。在使用canvas实现时,因为我们是一个node一个node去画,所以不存在资源引用的问题。但使用svg实现,相当于我们把文档交给SVG再来来渲染一遍,这对于我们来说是其实是无法控制的黑盒操作,是受SVG限制的


万幸,一个昵称为Christoph Burgmer的小哥写了一个名为 rasterizeHTML.js 的库,通过一系列的hack技巧替我们绕过了许多限制。我知道你很好奇他是怎么做到的。 简单来讲,rasterizeHTML.js在我们的基础实现上做了这些hack:




  • <img/>的url 转为 dataURI




  • 将background-color从style中取出,修改url后重新插入样式表




  • 将link的的样式通过ajax down下来然后注入<style></sytle>




  • 详见源码...




当然, rasterizeHTML.js能帮我们做的也不过是处理资源引用问题和浏览器兼容问题,更多的SVG的限制是无法绕过的,该库的文档正式列出了足足一整页的限制,让人读完后心中一凉。比如:




  • 跨域资源无法加载




  • 如lazyload等通过js加载的资源无法加载




  • 内联或js操作background-image无法加载




  • 详见文档




思考下rasterizeHTML.js的原理便可理解这些限制无法避免的原因: rasterizeHTML.js只能对已经存在的静态资源进行处理,而对js动态生成并不能实时处理。


目前rasterizeHTML.js已经被用于知乎-意见反馈功能。


参考


源码 developer.mozilla.org/en-US/docs/…API/DrawingDOMobjectsintoacanvas


标签:截图,gt,svg,canvas,js,lt,foreignObject,浅析
From: https://www.cnblogs.com/kuangke/p/16893529.html

相关文章

  • 淘宝数据采集之js采集
    搜索页面采集,数据在控制台哦!!!搜索页面采集,数据在控制台哦!!!搜索页面采集,数据在控制台哦!!!既然能打到控制台,当然也能传到系统!!!既然能打到控制台,当然也能传到系统!!!既然能打......
  • 淘宝首页数据采集之js采集
    搜索页面采集,数据在控制台哦!!!搜索页面采集,数据在控制台哦!!!搜索页面采集,数据在控制台哦!!!既然能打到控制台,当然也能传到系统!!!既然能打到控制台,当然也能传到系统!!!既然能打......
  • 使用vue-json-editor实现json编辑框
    1、进入到自己的项目,使用npm安装vue-json-editornpminstallvue-json-editor--save2、在vue组件中使用vue-json-editor<template><div><p>vue-json-edito......
  • simpread-(130 条消息) js 中常见的导入导出方式_AntyCoder 的博客 - CSDN 博客_js 导
    常见的导出方式创建文件foo.js//设置常量constname='tom';constage=20;consthello=function(age){console.log('张三今年'+age)}方式一exportc......
  • js实现深度优先遍历和广度优先遍历
    什么是深度优先和广度优先其实简单来说深度优先就是自上而下的遍历搜索广度优先则是逐层遍历,如下图所示深度优先广度优先两者的区别对于算法来说无非就是时......
  • .NET 7 升级Visual Studio 2022 17.4发生 WorkloadManifest.json冲突,导致项目无法加载
    .NET7的发布,升级VisualStudio2022的17.4版本,然后无法打开所有解决方案。提示信息如下异常:SDK解析程序失败:"尝试解析SDK"Microsoft.NET.Sdk"时,SDK解析程序”Microsoft.Do......
  • Newtonsoft.Json null值不序列化
    varjSetting=newJsonSerializerSettings{NullValueHandling=NullValueHandling.Ignore};varjson=JsonConvert.SerializeObject(response,Formatting.Indented,......
  • 原生JS获取伪元素
    1.获取伪元素原生JS中可以使用window.getComputedStyle()来获取伪元素.然后利用getPropertyValue方法或直接使用键值访问都可以获取对应的属性值。语法:window.getCompu......
  • js对象和原型、原型链的关系
    JS的原型、原型链一直是比较难理解的内容,不少初学者甚至有一定经验的老鸟都不一定能完全说清楚,更多的"很可能"是一知半解,而这部分内容又是JS的核心内容,想要技术进阶的话肯......
  • js异步编程的三种模式
    写在前面javascript语言的执行环境是"单线程"(singlethread),就是指一次只能完成一件任务。如果有多个任务,就必须排队,等前面一个任务完成,再执行后面一个任务,以此类推。......