记得在腾讯云的文档中心看到过一个反馈功能会自动截取当前网页的屏幕进行提交反馈问题,第一次看到这个功能内心就是 “ wocao,这是什么技术,好 nb 啊! 有机会一定要研究研究 ”,于是这个技术一直埋藏在我心里想找个时间搞搞

后来有一次,我们公司的一个 APP 也出现了一个类似的功能用 JavaScript 根据每个用户生成不同的活动分享图片,但是遗憾的是我当时还是一个小菜鸡(虽然现在依然那么菜鸡…)并没能参加那次的开发,于是后来我就简单的问了一下开发这个的大佬了解到是通过 html5 的 canvas 来实现转图片的…

这周,我看着一前写的一个邮件在线编辑器,突然想到要不加一个将写好的邮件导出为图片的功能,随便了解一下这个很 nb 的黑科技,于是…

我开始搜索,发现其实浏览器并没有提供相关截图的 api ,于是我一顿 google 发现实现有 Canvas 和 SVG 两种实现方案,原理大概相似,我这里就尝试了 canvas 的方式:MOD > canvas > 图片 ,怎么将 dom 转换成 canvas 图片?当然只能一点点一一层的画到 canvas 里啦,想想都复杂不过还好有轮子,这里我用的是  niklasvh/html2canvas

html2canvas

介绍:该脚本使直接在用户浏览器上截取网页或其一部分的 “屏幕快照” 。屏幕截图基于DOM,因此可能无法真实表示100%的准确度,因为它无法生成实际的屏幕截图,而是根据页面上的可用信息构建屏幕截图。通过读取 DOM 和应用于元素的不同样式,将当前页面呈现 canvas 画布图像。

GitHub:https://github.com/niklasvh/html2canvas/

文档地址:https://github.com/niklasvh/html2canvas/blob/master/docs/configuration.md

官网:https://html2canvas.hertzen.com/

使用起来非常简单,下面是 demo,我直接通过他们官网的下载地址引用的

<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js" ></script>
<body>
<div id="capture"> <p>Hello World</p> </div>
</body>
<script>
html2canvas(document.querySelector("#capture")).then(canvas => {
    document.body.appendChild(canvas)
});
</script>

是的,你没有看错就是这么简单,接下来我可以把得到的 canvas 转换成 imageBase64

var imageBase64;
html2canvas(document.querySelector("#capture")).then(canvas => {
    imageBase64 = canvas.toDataURL("image/png");

    // 输出Canvas到网页,查看最终输出结果
    var pHtml = new Image();
    pHtml.src = imageBase64;
    pHtml.setAttribute('crossorigin', 'anonymous');
    document.body.appendChild(pHtml);
  });

到这里其实基本差不多了,但是当我的页面中有图片的时候出现了错误,不能跨域引用图片,这里除了将图片源设置允许跨域的标识之外,还需要设置一下 html2canvas

var imageBase64;
html2canvas(document.querySelector("#capture"), {
  allowTaint: true,
  useCORS: true,
}).then(canvas => {
    imageBase64 = canvas.toDataURL("image/png");

    // 输出Canvas到网页,查看最终输出结果
    var pHtml = new Image();
    pHtml.src = imageBase64;
    pHtml.setAttribute('crossorigin', 'anonymous');
    document.body.appendChild(pHtml);
  });

没错,就是在后面传入一个配置对象,上面我配置了 allowTaint: true,useCORS: true ,相关文档地址:https://github.com/niklasvh/html2canvas/blob/master/docs/configuration.md (下面我根据自己的理解简单翻译了一下部分参数,我的技术能力有限并且版本可能会更新还是建议去看官方的,如果我理解的有误还请小伙伴们指点一下,到博客 https://bin.zmide.com/?p=482 下面回复一下就好了,我都会认真看并一一给予回复的,谢谢)

参数:allowTaint,默认值:false ,描述:是否允许跨域图像加载到 Canvas 画布上

参数:backgroundColor,默认值:#FFFFFF ,描述:Canvas 画布的背景颜色,如果传入的 DOM 有背景颜色的话就会被那个颜色覆盖,设置为 null 的话可以显示透明

参数:foreignObjectRendering,默认值:false ,描述:如果浏览器支持,是否使用 ForeignObject ( SVG 方式 )渲染

参数:imageTimeout,默认值:15000 ,描述:加载图像的超时时间(以毫秒为单位)。设置 0 为禁用超时。

参数:ignoreElements,默认值:(element) => false ,描述:如果浏览器支持,是否使用 ForeignObject ( SVG 方式 )渲染

参数:logging,默认值:true ,描述:如果浏览器支持,是否使用 ForeignObject ( SVG 方式 )渲染

参数:onclone,默认值:null ,描述:如果浏览器支持,是否使用 ForeignObject ( SVG 方式 )渲染

参数:proxy,默认值:null ,描述:如果浏览器支持,是否使用 ForeignObject ( SVG 方式 )渲染

参数:removeContainer,默认值:true ,描述:是否清除 html2canvas 临时创建的克隆 DOM 元素

参数:scale,默认值:window.devicePixelRatio ,描述:用于渲染的比例,默认为浏览器设备像素比率。

参数:useCORS,默认值:false ,描述:是否尝试使用 CORS 从服务器加载图像

参数:width,默认值:Element width ,描述:Canvas 画布的宽度,不设置的话会默认用你传入的 DOM 宽度

参数:height,默认值:Element height ,描述:Canvas 画布的高度,不设置的话会默认用你传入的 DOM 高度

参数:x,默认值:Element x-offset ,描述:裁剪画布 X 坐标

参数:y,默认值:Element y-offset ,描述:裁剪画布 Y 坐标

参数:scrollX,默认值:Element scrollX ,描述:渲染元素时要使用的x滚动位置(例如,如果元素使用position: fixed)也就是你传入的 DOM 可以横向滚动时加载时候的初始位置

参数:scrollY,默认值:Element scrollY ,描述:呈现元素时要使用的y-scroll位置(例如,如果 Element 使用position: fixed)也就是你传入的 DOM 可以竖向滚动时加载时候的初始位置

参数:windowWidth,默认值:Window.innerWidth ,描述:渲染时使用的窗口宽度,这可能会影响媒体查询之类的内容

参数:windowHeight,默认值:Window.innerHeight,描述:渲染时要使用的窗口高度,这可能会影响媒体查询之类的内容

解决图片资源跨域问题后,我发现它如果超过屏幕范围的 div( 带滑动条的 )就不会去截取了,这个问题我搜索了好久才找到解决思路,将要截取的 div 复制一个把它的 scrollHeight 获取到,然后设置到复制出来的 height 上,宽度也是如此,最后在把宽高比例优化一下就好了,看代码

  var targetMod = document.getElementById('outputCtt');
  var copyDom = targetMod.cloneNode(true); // 克隆节点
  copyDom.id = "outputCttCp";
  copyDom.style.width = targetMod.offsetWidth + 'px';
  copyDom.style.height = targetMod.scrollHeight + 'PX'; // 获得高度
  copyDom.style.paddingTop = "20px";
  document.body.appendChild(copyDom); // 插入节点
  var targetBlock = document.getElementById('outputCttCp');

  var canvasEle = document.createElement("canvas");
  var scaleBy = 10;    //放大倍数
  var imageBase64;  //存放生成Canvas图片的Base64码

  //获取目标DOM的宽高度 * 放大倍数
  canvasEle.width = targetBlock.offsetWidth*scaleBy;
  canvasEle.height = targetBlock.scrollHeight*scaleBy;

  //输出调试,看看尺寸是否正确
  console.log(canvasEle.width+"||"+canvasEle.height);
  console.log(targetBlock.offsetWidth+"||"+targetBlock.scrollHeight);

  //使用canvasEle.context进行放大
  var context = canvasEle.getContext('2d');
  context.scale(scaleBy, scaleBy);

  html2canvas(targetBlock, {
      allowTaint: true,
      useCORS: true,
    }).then(canvas => {
    imageBase64 = canvas.toDataURL("image/png");

    // 输出Canvas到网页,查看最终输出结果
    var pHtml = new Image();
    pHtml.src = imageBase64;
    pHtml.id = "test-img";
    pHtml.setAttribute('crossorigin', 'anonymous');

    if(document.getElementById(pHtml.id)) {
      // 清理预览节点
      document.body.removeChild(document.getElementById(pHtml.id));
    }
    
    document.body.appendChild(pHtml);
    document.body.removeChild(copyDom); // 清理插入节点
  });

到这里其实图片已经截取生成好了,但是我功能还没做完,还有一个点击下载 imageBase64 的功能,我先在这简单的贴出实现的代码,下篇博客在来讲讲这个小功能?

<button onclick="downloadDocs(imageBase64)" >下载图片</button>
script>
function downloadDocs(imageBase64) {
  var aLink = document.createElement('a');
  // 创建一个 a 标签
  var evt = document.createEvent("HTMLEvents");
  // 创建一个 HTMLEvents 类型的事件
  evt.initEvent("click", false, false);
  // 设置点击
  aLink.download = data_test + "test.png";
  // 设置下载的文件名
  aLink.href = imageBase64;
  // 设置下载链接
  // aLink.click();
  // aLink.dispatchEvent(evt);
  aLink.dispatchEvent(new MouseEvent('click', {bubbles: true, cancelable: true, view: window}));
  //触发点击事件,兼容火狐
}
</script>

上面的截图实现尽量不要用在复杂的页面上,毕竟是高度依赖于浏览器的

还有上面贴的 JavaScript 下载 imageBase64( data:image/png )的方法肯定存在浏览器兼容问题的,不过在 Chrome 上应该没问题