【JS】节点截图的最终解决⽅案dom-to-image与html2canvas
说在前头
2020-06-03 新添注意事项
若不需要极致的效果的话,请全篇使⽤html2canvas即可,不需要domtoimage了!
正⽂
这篇⽂章估计⾯向的⼈不多,所以我也不⼤篇幅的介绍这是⼲啥的了
…
起先,我是单纯想⽤domtoimage来解决我节点的截图的,但尝试了⽆数种⽅案,终是让我败下阵来
原因有仨
1. 不使⽤代理且必须不能本地引⼊(当然前提是你图⽚服务器不在内⽹,也就是外⽹也能请求到)
2. 兼容所有移动端(难点在safari)
3. 必须得到png or Jpeg图的base64(domtoimage的svg导出模式是可⾏的,但除⾮你是下载svg格式,否则转来转去永远也⽆法变
成png的base64,这是⼀个看似有希望,但实则是⼀⾯南墙的坑)
敲⿎了⼀天半,最终得出的⽅案是dom-to-image与html2canvas来配合
当然你以为单纯使⽤就ok了吗?No!不看下去,你依然会死在DataURL⾥
⼀、下载导⼊
注意我使⽤的版本是否与你相匹
{
"dom-to-image":"^2.6.0",
"html2canvas":"^1.0.0-rc.5"
}
import domtoimage from'dom-to-image'
import html2canvas from'html2canvas'
⼆、如何使⽤
1、定义主函数
主函数负责调⽤,并且我们需要他来判断两种机型的⾛向
const domToImage=({ el, android, ios, success, error, handle }={})=>{
if(!el){
console.warn('domToImage: 未到该节点,⽆法执⾏后续的截图操作')
return
}
// ios = html2canvas
if(/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)){
IosHandle(el, ios, success, error, handle)
}
// 安卓 || pc = domtoimage
else{
AndroidRender(el, android, success, error, handle)
}
}
}
我们来分下流,ios⾛html2canvas,安卓与PC⾛domtoimage,其中各参数介绍:
el:element dom节点
android:dom-to-image 配置项 - 具体options可参考后⾯官⽹的链接
ios:html2canvas 配置项
success:成功后的回调
error:失败回调
handle:⾃处理回调,这⾥我们将接收⼀个回调的返回值,布尔,若是false,则我们将抛出库的执⾏结果返回给开发者,且不执⾏⾃⾝后续处理
为什么要分流?
这就是为什么使⽤这两个库的原因了,除了以上仨问题,还有就是domtoimage在safari⾥⽀持⾮常不友好
message: “The operation is insecure.” (操作不安全)
知道这问题的⼈或许会与我感同⾝受吧
注意:变量名没写错,这⾥是IosHandle⽽不是IosRender,因为还要做⼀层处理
2、安卓执⾏ - AndroidRender
安卓的参数较简单,代码量也很少,但这并不代表他没有问题
const AndroidRender=(el, options, success, error, handle)=>{
...options,
quality:0.95,
})
.then(base64 =>{
const isNext = handle &&handle(base64)
// 这就是上⾯所提的,若返回false,则让开发⼈员执⾏⾃⼰的处理
if(handle ===false){
return
}
try{
success &&success(type, base64)
}catch(err){
error &&error(err)
}
})
.catch(err =>{
error &&error(err)
})
}
注意:toJpeg质量会有点低,但toPng在某些机型上好像会有⿊⽩屏的兼容性问题(不确定)
注意:别⼩看了quality参数,若不设置,你仍然会出现速度慢的情况,0.95质量的差别就是1mb与200kb的差距
3、IOS执⾏
3.1 预处理 - IosHandle
这⾥我们需要定义两个函数来⽤
const IosHandle=(el, options, success, error, handle)=>{
const imgArr = el.querySelectorAll('img')
let i =0
if(imgArr[0]){
let timer =setInterval(()=>{
clearInterval(timer)
if(imgArr.length !== i){
error &&error('超时')
}
},10000);
[...imgArr].forEach((dom)=>{
getUrlBlob(dom.src,((blob)=>{
if(blob !==false){
dom.src = blob
}
i ++
console.log(i)
/
/ 校验是否全部替换完毕
if((imgArr.length)=== i){
clearInterval(timer)
IosRender(el, options, success, error, handle)
}
}))
})
return
}
IosRender(el, options, success, error, handle)
}
const getUrlBlob=(url, callback)=>{
const str = url.substring(0,50)
// 避免重复加载
if(str.includes('blob:')){
return callback(false)
}
// 避免img未有src属性的情况,导致未返回
if(!str){
return callback(false)
}
let canvas = ateElement("canvas")
let ctx = Context("2d")
let img =new Image
img.src = url
canvas.height = img.height
canvas.width = img.width
ctx.drawImage(img,0,0)
try{
ateObjectURL(blob))
})
}catch(err){
callback(img.src)
<('转换失败,使⽤原图', err)
}
canvas =null
}
}
以上两段代码⾮常重要 - 核⼼是在html2canvas执⾏前先替换所有图⽚转换为Blob,这种⽅式不会出现图⽚缺失的情况
图⽚缺失:表现的症状是截图时,偶尔有图⽚丢失,这种情况是因为html2Canvas内部⼜对节点内的图⽚进⾏了⼀次请求,⽽此次请求不会管加载是否完毕,将直接转换为canvas⽣成图,恰恰这种情况
若是本地图就不会出现(初次请求就被缓存了),⽽根据Blob不会重复请求的特性,我们需要在IosRender前先对他进⾏Blob的转换,所以才有了上⽅的IosHandle
问题⼆:为什么要使⽤setInterval来进⾏超时的判断,因为要避免跨域图⽚的存在⽽导致try捕获不到的异常
3.2 执⾏ - IosRender
到这⾥还没完,再定义两个函数,我们要防⽩边,固定截图位置
const getOffsetTop=(el)=>{
let top = el.offsetTop
let parent = el.offsetParent
while(parent){
top += parent.offsetTop
parent = parent.offsetParent
}
return top
}
const getOffsetLeft=(el)=>{
let left = el.offsetLeft
let parent = el.offsetParent
while(parent){
left += parent.offsetLeft
parent = parent.offsetParent
}
return left
}
const IosRender=(el, options, success, error, handle)=>{
// 脱离下主线程
setTimeout(()=>{
html2canvas(el,{
scale:2,
allowTaint:true,
useCORS:true,
width: el.offsetWidth,
height: el.offsetHeight,
x:getOffsetLeft(el),
y:getOffsetTop(el),
...options,
})
.then(canvas =>{
const isNext = handle &&handle(canvas)
// 若返回为false,则让开发⼈员执⾏⾃⼰的处理
if(handle ===false){
return
}
try{
const base64 = DataURL('image/png')
success &&success(type, canvas, base64)
}catch(err){
error &&error(err)
}
})
.catch(err =>{
error &&error(err)
})
},500)
}
到这⾥为⽌也就差不多了,拿到canvas图⽚的base64,于是便想下载就下载,想⼲嘛就⼲嘛,但是也有弊端,因为IosHandle的缘故,处理时间略微会有点长,所以得添加下loading界⾯让⽤户感知⼀下
注意:html2canvas的ignoreElements过滤属性该版本是不⽀持的,你可以选择给需要过滤的标签动态添加“data-html2canvas-ignore”属性,不过这⼜会有⼀个问题,隐藏后会与导出的截图⾼度不匹,需要你⼿动处理
注意:scale: 2 不是为了让图⽚更清楚,⽽是你不设置这个值,iphoneX就等着哭吧
4、调⽤
domToImage({
el: document.querySelector('.snapshot'),
android:{
// options ...
},
ios:{
// options ...
},
handle(data){
console.log(data)
},
success(val){
console.log(val)
},
error(err){
<(err)
svg canvas}
})
正常来说是够⽤了,若是你还需要更优的效果或处理(⾼清、锐化),可以⽤options or handle⾃⾏使⽤
Tips:⽤于截图的组件通常是隐藏着的,⽗节点 + opacity 或 定位 + zIndex ⼜或者离开可视范围都⾏,但要注意 display: none 与visibility: hidden 是不⾏的
三、异常总结(持续更新)
以上避免了绝⼤多数情况,但仍有些问题需注意
1)Dom必须设minHeight最⼩⾼
并且你还需要稍微调⼤点,特别针对的是动态添加的元素,⾼度不固定的⽗级dom节点,这种情况会出现下载的图被绝对居中且被剪裁了的感觉
2)CSS3部分⾼级语法不⽀持
举例说明就是 -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-image: -webkit-linear-gradient 这种玩意
四、外部链接
关于
make:o︻そ╆OVE▅▅▅▆▇◤(清⼀⾊天空)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论