前端实现最佳截图方案(下) 前端实现图片裁剪
myzbx 2024-12-19 15:02 37 浏览
作者: 蜀中亮子
转发链接:https://mp.weixin.qq.com/s/ugrBaCIWYzGn8nuStp7ohw
前言
“旧闻重发,由于上一次的图片有些糊,这次分为上下两篇发送,不至于阅读压力太大”
上一篇:‘前端实现最佳截图方案(上)’
换个思路
基于我们对于上篇html2canvas整个流程的实现,会发现中间换算会出现很多不精准的问题,那么怎么做一个可以精准的绘制呢?能不能把所有内部绘制的换算过程全部交给浏览器?
基本思路
上文提到canvas还可以绘制image、svg等等,此处就可以把html处理成svg的结果,然后再绘制到canvas上。
对于svg是一种可扩展标记语言,再转化的过程中,就需要使用到<foreignObject>这个svg元素。<foreignObject>允许包含不同的XML命名空间,在浏览器的上下文中,很可能是XHTML\HTML,如下是使用方式:
这样只需要指定对应的命名空间,就可以把它嵌套到foreignObject中,然后结合SVG,直接渲染。
什么是命名空间,相当于是元素名和属性名的一种集合,元素和属性可以有多种不同的集合,为了解决冲突,就需要有命名空间的指派,对于带有属性xmlns=""就是一个命名空间的表现形式。以下是多种命名空间:
- HTML — http://www.w3.org/1999/xhtml
 - SVG — http://www.w3.org/2000/svg
 - MathML— http://www.w3.org/1998/math/MathML
 - 对于不同的命名空间,浏览器解析的方式也不一样,所以在SVG中嵌套HTML,解析SVG的时候遇到 http://www.w3.org/2000/svg 转化SVG的解析方式,当遇到了http://www.w3.org/1999/xhtml 就使用html的解析方式。
 
这是为什么SVG中可以嵌套HTML,并且浏览器能够正常渲染。
实现
但是这个过程中,会存在一些问题:
- SVG 是不允许连接到外部的资源,比如html中图片链接、css link方式的资源链接等,在SVG中都会有限制;
 - html中会有脚本执行的情况,比如Vue的SPA单页项目,需要先执行js的逻辑才能够渲染出dom节点。但是SVG中,是不支持js执行的情况。
 - SVG的位置大小和foreignObject标签的位置大小不能够确定,需要计算。
 
基于以上的情况,需要做一些其他的处理,以下为这个方案渲染的整个流程,看看如何解决存在的问题:
对于这种方案需要处理以上几个流程:
- 初始化不同类型的截图需要,比如DrawHTML(截取部分文档片段)、DrawDocument(截取完整document节点)、DrawURL(截取一个html资源链接)这几种形式,最后都会处理成截取整个document文档节点,以下是流程第一步的处理,。
 
- DrawHTML 转换部分文档片段为一个完整的document文档节点,然后使用DrawDocument的方式处理。
 
DrawURL 转换一个html资源链接为截取一个完整的document文档节点,再使用DrawDocument的方式处理。
可以看到最后的方式都是处理成一个document文档,实现到drawDocument这个方法里面,使用绘制document的形式来渲染。
基于上面的思路,把document文档转为SVG,但是document文档里面包含了外部链接的图片资源、外部样式资源和脚本资源。这种情况在SVG是不支持的,所以这一步的处理方式是把所有的外部资源,处理为内联形式的,改造为新的document,比如:
以上这种文档结构中,所有的资源都是属于外部资源,如果要转变为SVG,就需要处理成内联的形式,构造新的document文档,如下:
所以上一步把所有截图形式都处理成为了渲染一个document文档之后,就需要对文档进行重构转换,处理文档内部所有外部资源,不同的资源对应不同的处理方式,这里需要处理的资源情况分为以下几点:
在html文档中存在img图片标签的链接为外部资源,需要处理为base64资源,通过loadAndInlineIages函数进行处理,以下是loadAndInlineIages函数。
loadAndInlineImages函数的处理流程是获取到所有和图片有关的标签,在通过ajajx请求下来,然后处理成base64的资源类型,对原有的图片标签进行替换,这样就把所有的标签图片,处理成为了内联资源类型。以下是encodeImageAsDataURI方法内部请求图片资源且转义base64的逻辑:
通过了以上步骤之后,此时的document文档里面的图片标签元素的资源已经全部为内联形式了
在html中同时也存在着脚本为外部资源的情况,对于脚本的处理逻辑,整体就比较简单了,获取到脚本的链接,请求脚本内容,之后用请求的内容替换原有的外部链接的<script>,以下为脚本处理函数loadAndInlineScript的实现方式:
以上处理脚本资源的方法整体比较简单。
- 在处理完成了脚本和图片的情况之后,目前剩余需要处理成为内联资源的情况还剩下外部样式表。但是此处还需要注意一点,对于本来存在的内联样式也需要处理,因为可能会出现使用外链背景图的情况、通过@import导入样式表的情况。
 
所以对于外部样式表请求下来的内容会存在同样的问题,所以对于外部样式表而言,整体的流程就是通过ajax请求外部样式内容,然后对内容存在背景图片和@import的情况做处理。先供上对于css处理不同情况的流程处理:
通过上面的架构流程图,可以看出来远端请求的样式表需要和内联样式做同样的处理,把内部的远端图片资源和字体资源处理为内联形式。
- 对外部样式表的请求逻辑,大致逻辑如下:
 
通过以上代码,可以看见请求和处理逻辑全部在requestStylesheetAndInlineResources方法中,以下为代码方法:
从以上的代码逻辑中,可以清楚,有几个promise的处理流程,每一个流程处理的内容主要做了以下几件事情:
- 请求远端样式资源表,通过封装的ajax方法;
 - 处理请求下来的样式表中可能使用到的远端图片或者字体资源链接,使用inlineCss.adjustPathsOfCssResources方法,把使用到资源的相对地址,处理成为绝对地址;
 - 通过inlineCss.loadCSSImportsForRule方法处理@import资源引入的情况
 - 请求样式表中使用到的图片和文字资源,并且处理成内联,这一步的逻辑在inlineCss.loadAndInlineCSSResourcesForRules这个方法中
 - 基于原有样式表构造新的样式表
 
现在我们来看一下,对应每一种处理情况具体所做的事情:
- ajax请求资源,这一步不做深入,简单的ajax封装
 - 对于adjustPathsOfCssResources方法处理链接相对路劲变为绝对路劲,整体的实现思路是遍历查找所有的CSSRule,查找到background、font-face、@import等对应的Rule,解析属性设置的值,判断引用的地址是否是外部url,处理路劲变换为绝对路劲。构建新的CSSRule。
 
通过上面的逻辑处理之后,此时所有的css中包含的外部资源的链接已经处理为绝对路劲,对于整个资源css中的资源内联处理,第一步就已经完成了。
- 对于处理完成路劲之后,对于上面整个资源处理的大流程loadCSSImportsForRule方法就是把import的外部css请求回来,然后重新构建新的css。大体的思路为搜集当前css中所有的import资源地址,下载下来之后,构建为新的css,在分析新的css是否包含import,递归写入到最后的CSSRule中。 对于以上代码处理@import的函数中,loadAndInlineCSSImport方法就是核心的逻辑了,结合上面讲的整体处理流程,看看以下代码:
 
这样就把所有的css中的@import的资源,也处理进来了。
- 对于css资源,处理到这一步之后,结合我们上面的流程图,就只剩下把所有的资源诸如背景图、font-face等引用的外部链接变为内联资源。这一步的实现和上面css中转换资源相对路劲到绝对路劲,整个思路是一致的。区别在于对于最后一步替换相对路劲为绝对路劲的url不一致,这里需要替换的是资源请求下来之后处理成为base64的data数据之后的链接。
 
- 首先遍历所有CSSRule,找出需要替换的所有Rule
 - 获取对应Rule中包含的外部链接
 - 请求资源回来之后,处理为base64类型的data链接
 - 替换原有Rule中资源的地址,改为内联类型,构造成为新的CSSRule;
 
这样整个流程中的资源就已经处理完成,目前构造出来的文档,全是内联文档,符合构造SVG的要求;
- 在处理完成内容之后,就需要计算整个文档需要展示的大小,这是在SVG构建的时候需要使用到的;因为在用户截图的时候会传入对应想要的大小,这个时候,怎么去控制。大致的思路如下:
 
- 根据用户传入宽高大小创建iframe,把上面处理过的内联文档装载到iframe中执行
 - 获取到执行之后文档的clientWidth和clientHeight,同时根据zoom计算缩放的大小来作为最后SVG需要渲染的结果
 - 获取装载之后iframe中的文档的font-size来设置SVG的内容字体大小
 
经过上面这些步骤,我们计算出来了大小,剩下最后一步,序列化处理之后的文档节点构建SVG;
- 序列化文档节点的过程,就是把文档节点处理成为整个字符串的过程,在大多数浏览器中都是有序列化api的支持,不过有少数兼容问题,所以最优方法为自己实现序列化的过程,整个过程逻辑主要为递归遍历文档节点,处理节点名称大小写、文本内容中包含<、>、&这几个符号的转义处理及对整个文档添加指定的命名空间。
 - 在序列化文档文档之后,就需要使用序列化之后的内容和计算出来的展示文档大小值来构建SVG,整个构建的过程代码大致流程: 至此,SVG构建已经构建完成,剩下最后一步就是把SVG处理成图片可以显示的资源;
 
6.处理图片显示的资源这个过程,其实有两种实现:
- 第一种是通过createObjectURL把图片资源处理为blob数据,img使用时直接使用blob数据;
 - 第二种是直接encode对应的SVG资源,构建data资源链接 这两种生成的连接都可以对应添加到图片的src中;当然,此时也可以拿到对应的SVG调用canvas绘图的api来绘制SVG,做二次加工;
 
至此,这个思路的实现全部完成;
思路缺点
基于以上两个思路的对比,明显会发现,使用html通过foreignObject构建SVG的方法要简单清晰,但是对于一些浏览器也会有一些小问题,不过已经有一个比较不错的库通过hack的方式,处理了这些问题。rasterizeHTML.js是一个比较不错的截图库,实现的逻辑就是基于上面的思路。
不过这两种方式都会涉及到一个问题,就是图片资源跨域问题,如果图片为跨域图片,就需要通过CORS来处理。由于在 `canvas` 位图中的像素可能来自多种来源,包括从其他主机检索的图像或视频,因此不可避免的会出现安全问题,所以对于除CORS以外的跨域图片,canvas都会被处理成污染的情况,此时getImageData、toBlob、toDataURL都会被禁止调用,这种机制也可以避免未经许可拉取远程网站信息而导致的用户隐私泄露,这对于webgl的贴图也是同样的处理,不能使用除CORS以外的跨域图片。
总结
以上总结了html2canvas的整体思路及优缺点,目前html2canvas源码里面也已经开始融合第二种思路,这说明了第二种截图思路的优点。但是第二种思路的过程中自己手动处理的序列化性能相比浏览器处理而言略微慢一点,等到浏览器序列化都支持的特别好的时候,就可以替代这一部分。当然,咱们也可以打开思路,结合webassembly来重写序列化的部分,打开整个BS架构大门。
推荐JavaScript学习相关文章
《Node.js 实现抢票小工具&短信通知提醒(上)「干货」》
《Node.js 实现抢票小工具&短信通知提醒(下)「干货」》
《学习 jQuery 源码整体架构,打造属于自己的 js 类库》
《Angular v10.0.0 正式发布,不再支持 IE9/10》
《「实践」浏览器中的画中画(Picture-in-Picture)模式及其 API》
《「多图」一文带你彻底搞懂 Web Workers (上)》
《「多图」一文带你彻底搞懂 Web Workers (中)》
《webpack4主流程源码解说以及动手实现一个简单的webpack(上)》
《webpack4主流程源码解说以及动手实现一个简单的webpack(下)》
《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)上》
《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)中》
《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)下》
《一文带你搞懂 babel-plugin-import 插件(上)「源码解析」》
《一文带你搞懂 babel-plugin-import 插件(下)「源码解析」》
《教你如何使用内联框架元素 IFrames 的沙箱属性提高安全性?》
《细说DOM API中append和appendChild的三个不同点》
《NodeX Component - 滴滴集团 Node.js 生态组件体系「实践」》
《浅谈浏览器架构、单线程js、事件循环、消息队列、宏任务和微任务》
《了不起的 Webpack HMR 学习指南(上)「含源码讲解」》
《了不起的 Webpack HMR 学习指南(下)「含源码讲解」》
《图解 Promise 实现原理(二):Promise 链式调用》
《图解 Promise 实现原理(三):Promise 原型方法实现》
《图解 Promise 实现原理(四):Promise 静态方法实现》
《使用Service Worker让你的 Web 应用如虎添翼(上)「干货」》
《使用Service Worker让你的 Web 应用如虎添翼(中)「干货」》
《使用Service Worker让你的 Web 应用如虎添翼(下)「干货」》
《一个轻量级 JavaScript 全文搜索库,轻松实现站内离线搜索》
《细品269个JavaScript小函数,让你少加班熬夜(一)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(二)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(三)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(四)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(五)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(六)「值得收藏」》
《手把手教你7个有趣的JavaScript 项目-上「附源码」》
《手把手教你7个有趣的JavaScript 项目-下「附源码」》
《JavaScript 使用 mediaDevices API 访问摄像头自拍》
《一文彻底搞懂JavaScript 中Object.freeze与Object.seal的用法》
《可视化的 JS:动态图演示 - 事件循环 Event Loop的过程》
《可视化的 js:动态图演示 Promises & Async/Await 的过程》
《Pug 3.0.0正式发布,不再支持 Node.js 6/8》
《通过发布/订阅的设计模式搞懂 Node.js 核心模块 Events》
《「速围」Node.js V14.3.0 发布支持顶级 Await 和 REPL 增强功能》
《JavaScript 已进入第三个时代,未来将何去何从?》
《前端上传前预览文件 image、text、json、video、audio「实践」》
《深入细品 EventLoop 和浏览器渲染、帧动画、空闲回调的关系》
《推荐13个有用的JavaScript数组技巧「值得收藏」》
《36个工作中常用的JavaScript函数片段「值得收藏」》
《一文了解文件上传全过程(1.8w字深度解析)「前端进阶必备」》
《手把手教你如何编写一个前端图片压缩、方向纠正、预览、上传插件》
《JavaScript正则深入以及10个非常有意思的正则实战》
《前端开发规范:命名规范、html规范、css规范、js规范》
《100个原生JavaScript代码片段知识点详细汇总【实践】》
《手把手教你深入巩固JavaScript知识体系【思维导图】》
《一个合格的中级前端工程师需要掌握的 28 个 JavaScript 技巧》
《身份证号码的正则表达式及验证详解(JavaScript,Regex)》
《127个常用的JS代码片段,每段代码花30秒就能看懂-【上】》
《深入浅出讲解JS中this/apply/call/bind巧妙用法【实践】》
《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》
《面试中教你绕过关于 JavaScript 作用域的 5 个坑》
作者: 蜀中亮子
转发链接:https://mp.weixin.qq.com/s/ugrBaCIWYzGn8nuStp7ohw
相关推荐
- 如何设计一个优秀的电子商务产品详情页
 - 
        
加入人人都是产品经理【起点学院】产品经理实战训练营,BAT产品总监手把手带你学产品电子商务网站的产品详情页面无疑是设计师和开发人员关注的最重要的网页之一。产品详情页面是客户作出“加入购物车”决定的页面...
 
- 怎么在JS中使用Ajax进行异步请求?
 - 
        
大家好,今天我来分享一项JavaScript的实战技巧,即如何在JS中使用Ajax进行异步请求,让你的网页速度瞬间提升。Ajax是一种在不刷新整个网页的情况下与服务器进行数据交互的技术,可以实现异步加...
 
- 中小企业如何组建,管理团队_中小企业应当如何开展组织结构设计变革
 - 
        
前言写了太多关于产品的东西觉得应该换换口味.从码农到架构师,从前端到平面再到UI、UE,最后走向了产品这条不归路,其实以前一直再给你们讲.产品经理跟项目经理区别没有特别大,两个岗位之间有很...
 
- 前端监控 SDK 开发分享_前端监控系统 开源
 - 
        
一、前言随着前端的发展和被重视,慢慢的行业内对于前端监控系统的重视程度也在增加。这里不对为什么需要监控再做解释。那我们先直接说说需求。对于中小型公司来说,可以直接使用三方的监控,比如自己搭建一套免费的...
 
- Ajax 会被 fetch 取代吗?Axios 怎么办?
 - 
        
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!今天给大家带来的主题是ajax、fetch...
 
- 前端面试题《AJAX》_前端面试ajax考点汇总
 - 
        
1.什么是ajax?ajax作用是什么?AJAX=异步JavaScript和XML。AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX可以使网页实...
 
- Ajax 详细介绍_ajax
 - 
        
1、ajax是什么?asynchronousjavascriptandxml:异步的javascript和xml。ajax是用来改善用户体验的一种技术,其本质是利用浏览器内置的一个特殊的...
 
- 6款可替代dreamweaver的工具_替代powerdesigner的工具
 - 
        
dreamweaver对一个web前端工作者来说,再熟悉不过了,像我07年接触web前端开发就是用的dreamweaver,一直用到现在,身边的朋友有跟我推荐过各种更好用的可替代dreamweaver...
 
- 我敢保证,全网没有再比这更详细的Java知识点总结了,送你啊
 - 
        
接下来你看到的将是全网最详细的Java知识点总结,全文分为三大部分:Java基础、Java框架、Java+云数据小编将为大家仔细讲解每大部分里面的详细知识点,别眨眼,从小白到大佬、零基础到精通,你绝...
 
- 福斯《死侍》发布新剧照 "小贱贱"韦德被改造前造型曝光
 - 
        
时光网讯福斯出品的科幻片《死侍》今天发布新剧照,其中一张是较为罕见的死侍在被改造之前的剧照,其余两张剧照都是死侍在执行任务中的状态。据外媒推测,片方此时发布剧照,预计是为了给不久之后影片发布首款正式预...
 
- 2021年超详细的java学习路线总结—纯干货分享
 - 
        
本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础重点知识点:数据类型、核心语法、面向对象...
 
- 不用海淘,真黑五来到你身边:亚马逊15件热卖爆款推荐!
 - 
        
Fujifilm富士instaxMini8小黄人拍立得相机(黄色/蓝色)扫二维码进入购物页面黑五是入手一个轻巧可爱的拍立得相机的好时机,此款是mini8的小黄人特别版,除了颜色涂装成小黄人...
 
- 2025 年 Python 爬虫四大前沿技术:从异步到 AI
 - 
        
作为互联网大厂的后端Python爬虫开发,你是否也曾遇到过这些痛点:面对海量目标URL,单线程爬虫爬取一周还没完成任务;动态渲染的SPA页面,requests库返回的全是空白代码;好不容易...
 
- 最贱超级英雄《死侍》来了!_死侍超燃
 - 
        
死侍Deadpool(2016)导演:蒂姆·米勒编剧:略特·里斯/保罗·沃尼克主演:瑞恩·雷诺兹/莫蕾娜·巴卡林/吉娜·卡拉诺/艾德·斯克林/T·J·米勒类型:动作/...
 
- 停止javascript的ajax请求,取消axios请求,取消reactfetch请求
 - 
        
一、Ajax原生里可以通过XMLHttpRequest对象上的abort方法来中断ajax。注意abort方法不能阻止向服务器发送请求,只能停止当前ajax请求。停止javascript的ajax请求...
 
- 一周热门
 
- 最近发表
 
- 标签列表
 - 
- HTML 简介 (30)
 - HTML 响应式设计 (31)
 - HTML URL 编码 (32)
 - HTML Web 服务器 (31)
 - HTML 表单属性 (32)
 - HTML 音频 (31)
 - HTML5 支持 (33)
 - HTML API (36)
 - HTML 总结 (32)
 - HTML 全局属性 (32)
 - HTML 事件 (31)
 - HTML 画布 (32)
 - HTTP 方法 (30)
 - 键盘快捷键 (30)
 - CSS 语法 (35)
 - CSS 轮廓宽度 (31)
 - CSS 谷歌字体 (33)
 - CSS 链接 (31)
 - CSS 定位 (31)
 - CSS 图片库 (32)
 - CSS 图像精灵 (31)
 - SVG 文本 (32)
 - 时钟启动 (33)
 - HTML 游戏 (34)
 - JS Loop For (32)
 
 
