2020-06-03 新添注意事项javascript
若不须要极致的效果的话,请全篇使用html2canvas便可,不须要domtoimage了!html
这篇文章估计面向的人很少,因此我也不大篇幅的介绍这是干啥的了 ... 起先,我是单纯想用domtoimage来解决我节点的截图的,但尝试了无数种方案,终是让我败下阵来java
缘由有仨android
敲鼓了一天半,最终得出的方案是dom-to-image与html2canvas来配合 固然你觉得单纯使用就ok了吗?No!不看下去,你依然会死在canvas.toDataURL里ios
注意我使用的版本是否与你相匹git
{ "dom-to-image": "^2.6.0", "html2canvas": "^1.0.0-rc.5" } 复制代码
import domtoimage from 'dom-to-image' import html2canvas from 'html2canvas' 复制代码
主函数负责调用,而且咱们须要他来判断两种机型的走向github
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,其中各参数介绍:web
为何要分流?canvas
这就是为何使用这两个库的缘由了,除了以上仨问题,还有就是domtoimage在safari里支持很是不友好segmentfault
message: "The operation is insecure." (操做不安全)
知道这问题的人或许会与我感同身受吧
注意:变量名没写错,这里是IosHandle而不是IosRender,由于还要作一层处理
安卓的参数较简单,代码量也不多,但这并不表明他没有问题
const AndroidRender = (el, options, success, error, handle) => { domtoimage.toPng(el, { ...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的差距
这里咱们须要定义两个函数来用
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 = document.createElement("canvas") let ctx = canvas.getContext("2d") let img = new Image img.crossOrigin = 'Anonymous' img.src = url img.onload = function () { canvas.height = img.height canvas.width = img.width ctx.drawImage(img, 0, 0) try { canvas.toBlob((blob) => { callback(URL.createObjectURL(blob)) }) } catch (err) { callback(img.src) console.error('转换失败,使用原图', err) } canvas = null } } 复制代码
以上两段代码很是重要 - 核心是在html2canvas执行前先替换全部图片转换为Blob,这种方式不会出现图片缺失的状况
图片缺失:表现的症状是截图时,偶尔有图片丢失,这种状况是由于html2Canvas内部又对节点内的图片进行了一次请求,而这次请求不会管加载是否完毕,将直接转换为canvas生成图,偏偏这种状况如果本地图就不会出现(初次请求就被缓存了),而根据Blob不会重复请求的特性,咱们须要在IosRender前先对他进行Blob的转换,因此才有了上方的IosHandle
问题二:为何要使用setInterval来进行超时的判断,由于要避免跨域图片的存在而致使try捕获不到的异常
到这里还没完,再定义两个函数,咱们要防白边,固定截图位置
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 = canvas.toDataURL('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就等着哭吧
domToImage({ el: document.querySelector('.snapshot'), android: { // options ... }, ios: { // options ... }, handle (data) { console.log(data) }, success (val) { console.log(val) }, error(err) { console.error(err) } }) 复制代码
正常来讲是够用了,如果你还须要更优的效果或处理(高清、锐化),能够用options or handle自行使用
Tips:用于截图的组件一般是隐藏着的,父节点 + opacity 或 定位 + zIndex 又或者离开可视范围都行,但要注意 display: none 与 visibility: hidden 是不行的
以上避免了绝大多数状况,但仍有些问题需注意
而且你还须要稍微调大点,特别针对的是动态添加的元素,高度不固定的父级dom节点,这种状况会出现下载的图被绝对居中且被剪裁了的感受
举例说明就是 -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-image: -webkit-linear-gradient 这种玩意
make:o︻そ╆OVE▅▅▅▆▇◤(清一色天空)
lofter:zcxy-gs.lofter.com/