html2Canvas 前端实现截屏保存图片

5,084 阅读4分钟

1. 原理介绍

该脚本遍历它所加载的页面的 DOM。它收集那里所有元素的信息,然后使用这些信息来构建页面的表示。换句话说,它实际上并没有截取页面的屏幕截图,而是根据它从 DOM 读取的属性构建它的表示。

结果,它只能正确渲染它理解的属性,这意味着有许多 CSS 属性不起作用。有关支持的 CSS 属性的完整列表,请查看 支持的功能页面。

2. 方案

这个库能将 HTML 转为 canvas,然后通过canvasAPI又能将 canvas 能转为图片,最后也可以通过a标签来实现图片的下载功能

那基本方案就是: html -> canvas -> image -> a[download]

  1. html2canvas.js:可将 htmldom 转为 canvas 元素。github传送门 ,官网传送门
  2. canvasAPI:toDataUrl() 可将 canvas 转为 base64 格式
  3. 创建 a[download] 标签触发 click 事件实现下载

3. 方案的具体实现代码

//点击生成图片
generateImage() {
  if(this.state.productData.length>=100){
    this.over100=true;
    this.isOver100Refresh=true;
    over100_list=this.state.productData
    this.state.productData=over100_list.slice(0,100)
    return
  }
  this.notMyTitle = false;
  this.notMyFooter = false;
  this.isLoadingBottom = false;
  document.querySelector('#area').scrollTop = 0;
  document.querySelector('.prodListH5').scrollLeft = 0;
  this.$refs.prodListH5Title.classList.remove('xiDing'); //生成图片出错处理
  document.getElementById('area').style.height = 'auto';
  var rect = document.getElementById('appArea').getBoundingClientRect(); // 关键代码
  let height =
    rect.height +
    (158 * ((this.state.trList.width - 24) / 321) + 80 > 300 ? 158 * ((this.state.trList.width - 24) / 321) + 80 : 300); // 加上底部新增图片高度(40是margin值),避免截取不全
  let width = rect.width;
  let userAgent = navigator.userAgent;
  // 苹果手机
  if (userAgent.includes('iPhone') || userAgent.includes('iPad')) {
    var scale = 3000000 / height / width > 2 ? 2 : 1;
  } else {
    //取的谷歌浏览器
    var scale = 14000 / height > 2 ? 2 : 1;
  }
  html2canvas(document.getElementById('appArea'), {
    // scrollY: 2 * rect.top, // 关键代码
    scale, //图像太长处理
    scrollY: rect.top,
    height,
    width: rect.width,
  }).then((canvas) => {
    canvas.toBlob((blob) => {
      // this.imgUrl = URL.createObjectURL(blob)
      // var aImg = document.createElement("a");
      this.imgUrl = canvas.toDataURL('image/jpeg');
      var img = document.createElement('img');
      img.setAttribute('src', this.imgUrl);
      img.className = 'imgItem';
      img.style.width = '100vw';
      img.style.height = 'auto';
      document.querySelector('.canvasImg').appendChild(img);
      document.querySelector('.imgBox').onclick = () => {
        document.getElementById('area').style.height = '88vh';
        document.querySelector('.canvasImg').removeChild(img);
        document.querySelector('.imgBox').style.display = 'none';
        document.querySelector('.saveBtn').style.display = 'block';
        this.notMyTitle = true;
        this.notMyFooter = true;
        this.isLoadingBottom = true;
      };
      document.querySelector('.imgBox').style.display = 'block';
      document.querySelector('.saveBtn').style.display = 'none';
    }, 'image/png');
  });
},

4. 避坑指南

(1) 图片模糊

因为开发的项目过程中,有滚动效果,截取的图片不仅仅是可视窗口的宽高,当数据过多,图片过长等情况,会出现图片模糊的情况,可以通过设置 scale 配置项来解决

html2canvas(document.getElementById('appArea'), {
    // scrollY: 2 * rect.top, // 关键代码
    scale, //图像太长处理
    scrollY: rect.top,
    height,
    width: rect.width,
  }).then((canvas)=>{...//这里已经绘制完成canvas,进行转base64图片等操作})

至于scale的取值,用于渲染的比例。默认为浏览器设备像素比

/** * 根据window.devicePixelRatio获取像素比 */ 
function DPR() { 
    if (window.devicePixelRatio && window.devicePixelRatio > 1) { 
        return window.devicePixelRatio; 
        } 
    return 1; 
    }

(2) 部分ios端出现文字重叠情况

当页面中有内容文字自动换行,且文字设置了text-align:center的情况下,会出现文字内容的高度不变,但文字会重叠到一行上,,粗略解决方法是设置text-align:left下,能解决这个问题,
顺便提一下的是,之前尝试用v-html的方式去渲染,手动去添加换行符,文字处理还是保持text-align:center的处理方式,但并没有成功
问题效果如图所示: image.png

(3) 截屏出来的图片左右区域出现空白

问题出现的情况:当页面向下滚动或者向右滚动了一定距离的时候,会出现截取不全而出现空白的情况 解决的方案:
1.在htmldom转为canvas之前,将页面滚动到scrollTop scrollLeft为0的时候可以解决

(4) 图片底部新增的名片没显示出来,有高度(占了位置)但是空白

问题出现的原因:canvas元素达到浏览器canvas大小限制,窗口限制因浏览器、操作系统和系统硬件而异。具体值

5. 番外

(1)parseInt注意事项

在使用 parseInt 时,尽量都指定一个 radix原因

/** * 将传入值转为整数 */ 
function parseValue(value) { 
    return parseInt(value, 10);
    };

语法:

parseInt(string, radix);

参数

  • string

    要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用  ToString 抽象操作)。字符串开头的空白符将会被忽略。

  • radix 可选

    从 2 到 36,表示字符串的基数。例如指定 16 表示被解析值是十六进制数。请注意,10不是默认值!

返回值

从给定的字符串中解析出的一个整数。

或者 NaN,当

  • radix 小于 2 或大于 36 ,或
  • 第一个非空格字符不能转换为数字。
parseInt('123', 5) // 将'123'看作5进制数,返回十进制数38 => 1*5^2 + 2*5^1 + 3*5^0 = 38