【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!不看下去,你依然会死在canvas.toDataURL里


一、下载导入

注意我使用的版本是否与你相匹

{
    "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) => {
  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的差距

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 = 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捕获不到的异常

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 = 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就等着哭吧

4、调用

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 是不行的


三、异常总结(持续更新)

以上避免了绝大多数情况,但仍有些问题需注意

1)Dom必须设minHeight最小高

并且你还需要稍微调大点,特别针对的是动态添加的元素,高度不固定的父级dom节点,这种情况会出现下载的图被绝对居中且被剪裁了的感觉

2)CSS3部分高级语法不支持

举例说明就是 -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-image: -webkit-linear-gradient 这种玩意


四、外部链接

html2Canvas Options 配置项

dom-to-imgage Options 配置项


关于

make:o︻そ╆OVE▅▅▅▆▇◤(清一色天空)

blog:http://blog.csdn.net/mcky_love

掘金:https://juejin.im/user/59fbe6c66fb9a045186a159a/posts

lofter:http://zcxy-gs.lofter.com/

sf:https://segmentfault.com/u/mybestangel

git:https://github.com/gs3170981

### 回答1: html2canvas是一个JavaScript库,可以将动态创建的DOM元素转换为图片。 动态创建DOM元素是指在页面加载完成后,通过JavaScript的createElement方法或者innerHTML属性创建新的DOM元素。而html2canvas可以将这些动态创建的DOM元素转换为图片。 使用html2canvas进行转换的步骤如下: 首先,在页面中引入html2canvas.js文件。 然后,通过JavaScript动态创建需要转换为图片的DOM元素。 接下来,使用html2canvas库的方法,传入动态创建的DOM元素作为参数,来实现转换。例如,使用html2canvas(element)方法,其中element是要转换的DOM元素。 最后,html2canvas会将这个DOM元素渲染为一张图片,并返回一个Canvas元素。我们可以将这个Canvas元素插入到页面中,或者将其转换为图片格式进行下载、保存等操作。 需要注意的是,由于html2canvas是基于CanvasAPI实现的,所以转换过程中会受到一些限制,比如转换的DOM元素不能跨域访问,如果存在跨域图片,可能会导致转换出错。 总之,html2canvas库可以帮助我们将动态创建的DOM元素转换为图片,在一些需要将页面内容保存为图片或者进行截图等场景中十分有用。 ### 回答2: html2canvas是一个用于截图网页内容并将其转换为图片的JavaScript库。它可以将DOM动态创建的元素转换为图片。 使用html2canvas动态创建的DOM转图片,需要按照以下步骤进行操作: 1. 引入html2canvas库。在HTML文件中添加以下代码: ```html <script src="html2canvas.js"></script> ``` 2. 在JavaScript代码中创建DOM元素,并将其添加到页面中。例如: ```javascript var element = document.createElement('div'); element.innerHTML = '这是动态创建的DOM元素'; document.body.appendChild(element); ``` 3. 使用html2canvas函数来截图动态创建的DOM元素。例如: ```javascript html2canvas(element).then(function(canvas) { // 将canvas转换为图片 var image = canvas.toDataURL(); // 显示转换后的图片 var imgElement = document.createElement('img'); imgElement.src = image; document.body.appendChild(imgElement); }); ``` 在上面的代码中,html2canvas函数接受一个要截图DOM元素作为参数,并返回一个Promise对象。在Promise对象的回调函数中,可以使用canvas.toDataURL方法将canvas转换为图片的Base64编码,并将其添加到页面中。 通过以上步骤,就可以使用html2canvas将动态创建的DOM元素转换为图片,并将其显示在页面上。 ### 回答3: html2canvas是一个JavaScript库,用于将网页内容转换为图像。它可以通过动态创建DOM元素并将其转换为图像的方式,实现网页截图的功能。 首先,我们需要引入html2canvas库,并创建一个指定大小的canvas元素,用于呈现转换后的图像。 接下来,我们可以使用JavaScript动态创建DOM元素,例如使用document.createElement()方法创建新的元素节点,并添加相应的属性和内容。 在创建完DOM元素后,我们需要将其加入到文档中,使其在网页中可见。可以通过document.body.appendChild()或其他适合的方法将DOM元素添加到文档中。 当所有DOM元素都创建完成并添加到文档中后,可以使用html2canvas库的函数将整个文档内容转换为图像。可以通过传递canvas元素的引用以及其他可选参数,调用html2canvas()函数实现转换。 最后,我们可以通过获取canvas元素的数据,生成图像并将其展示在网页上。可以使用canvas.toDataURL()方法将canvas元素转为base64编码的URL,然后将其分配给img元素的src属性即可。 总结来说,使用html2canvas库动态创建DOM并转换为图像的过程包括:引入库、创建canvas元素、动态创建DOM元素、将DOM元素添加到文档中、调用html2canvas函数进行转换、获取canvas数据并展示生成的图像。这样就可以实现将动态创建的DOM转换为图像的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值