html2canvas实现html转图片并长按保存

目的

需求是这样的:前端生成分享海报,上面有当前页面路径的二维码,用户长按海报保存到手机。
使用html2canvas可以把html转换成canvas,再进一步转化图片,保存到本地。

html2canvas

html2canvas本身很简单,api也很简单,但使用的过程中有比较多的坑。

背景图片模糊

一个很多人遇到的问题,一般是把背景图片换成。再定位到位置上。

canvas白边

这个也很多人遇到,解决方法是在生成图片时设置canvas的宽高

let post = document.getElementById('post')
let width = post.offsetWidth
let height = post.offsetHeight
html2canvas(post, {
  backgroundColor: 'rgba(255, 255, 255, 0)',
  useCORS: true,
  allowTaint: false,
  width,
  height,
  logging: false,
  scale: 2
})

生成背景透明的png

因为设计稿上生成的图片是有圆角的,所以需要背景透明。

backgroundColor: 'rgba(255, 255, 255, 0)'

某些手机删除线无法显示

之前看到别人出现的情况是删除线位置偏下,我出现的问题是有些机型删除线完全消失了。应该是html2canvas本身对一些css属性兼容问题,可以参考html2canvas官网。
解决方法也比较简单,自己实现一个删除线即可。

.price::after{
  content: '';
  width: 100%;
  height: 0;
  border-bottom: 1.5px solid rgba(255, 255, 255, 0.7);
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
}

多行文本省略号无法显示

应该是对css属性的兼容问题。因为在移动端,就算是不同屏幕,显示的字数也差不多,所以可以数一下大概几个字,超出就用js截断,然后加上省略号。如果不强求的话就直接overflow:hidden截断就好了。

隐藏DOM结构

因为要先根据DOM结构生成图片,生成之后替换掉这段html。但是需求是在生成的时候显示一个loading。如果显示loading的时候把DOM结构设置display:none,那html2canvas会把这行css也进行渲染,导致图片也无法出现。使用visibility: hidden也不行。
最后我把DOM结构移除窗口外,再把生成的图片定位到指定位置。不知道有没有别的方法。

.post-container{
  top: -1000px;
  left: -1000px;
  position: fixed;
}

图片无法渲染

终于到了最终大boss。
如果只是展示,那就没有这个问题。但是需求是要生成一张可以下载的图片,所以需要canvas转图片(base64格式)。但当你使用了外链图片,并且使用canvas.toDataURL('image/png')的时候,就是报错图片跨域。即使我根据别人的经验设置了这些,但依旧没用。

html2canvas(post, {
  useCORS: true
})
image.setAttribute("crossOrigin",'Anonymous')

事实上,图片跨域的首要条件是放图片的服务器支持图片跨域。所以我找了运维设置了图片跨域,跟接口跨域是差不多的,都是用的CORS。
然而还是不行。。。但是我之前在看到的另一种方法是先将图片转base64再用html2canvas转canvas,最后再转图片。因为图片转base64也是用canvas.toDataURL的方法,在这里就行得通了,实在很迷。最后是用两种方法结合。

下面是部分代码:

我项目用的是Vue框架。
图片转base64:
网上一大把,自己写一个也行

export default function getBase64Image(img) {
  let canvas = document.createElement("canvas");
  canvas.width = img.width;
  canvas.height = img.height;
  let ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0, img.width, img.height);
  let dataURL = canvas.toDataURL("image/png");
  return dataURL;
}

整体流程:
点击生成的时候,先执行createdPost()函数。这个函数回去创建二维码,并将海报上的外链图片转成base64。
转化成功之后重新赋值给DOM结构上图片的src。需要注意的是,要等图片加载完成之后才能用html2canvas转化,否则图片也是加载不出来的。

methods: {
    imageLoaded() {
      this.createImg()
    },
    // 点击创建执行该函数
    createdPost() {
      this.loading = true
      this.$nextTick(() => {
        this.createQrcode()
        this.getImage(this.item.picture)
      })
    },
    createImg() {
      let post = document.getElementById('post')
      let width = post.offsetWidth
      let height = post.offsetHeight
      html2canvas(post, {
        backgroundColor: 'rgba(255, 255, 255, 0)',
        useCORS: true,
        allowTaint: false,
        width,
        height,
        logging: false,
        scale: 2
      }).then(canvas => {
        this.postUrl = canvas.toDataURL('image/png')
        this.loading = false
        this.hasCreated = true
      })
    },
    getImage(url) {
      let image = new Image()
      image.setAttribute("crossOrigin",'Anonymous')
      image.src = url + '?v=1.1' // 参数不要随机,否则会击穿CDN缓存
      image.onload = () => {
        let width = image.width
        let height = image.height
        let ratio = 375 / 300
        this.pictureType = (width / height) > ratio ? 'row-image' : 'column-image'
        this.picture = getBase64Image(image)
      };
    }
  }

其他的一些问题

生成二维码

生成二维码的时候,因为是移动端,所以需要计算一下宽高,如果用css适配的话,二维码可能会模糊。可根据设计稿大小自行计算。

// 创建当前路径的二维码
createQrcode() {
  let htmlFontSize = parseFloat(document.querySelector("html").style.fontSize)
  let size = 70 / 37.5 * htmlFontSize
  new QRCode('qrcode', {
    width: size,
    height: size,
    text: window.location.href,
    colorDark : "#000",
    colorLight : "#fff",
    correctLevel: QRCode.CorrectLevel.M
  })
}

体验问题

因为生成图片的时间可能有点长,一般需要用一个loading提示用户,也可以防止用户在生成的过程中乱点乱按。当显示海报时,类似一个弹窗,所以应该禁止底层的滑动。最后一点是,每个页面海报其实只需要生成一次,之后用户再点击生成,直接把之前生成的图片展示即可。

长按保存的问题

刚过了一个坑,又有另一个坑。
现在手机浏览器基本都可以长按保存图片了,理论上这个应该不用我们自己实现。然后这个需求的场景是在支付宝上。
问题:在安卓支付宝上长按图片无法唤起菜单。
其实这个问题可以更详细点,支付宝上长按无法唤起菜单的图片只是base64的,正常图片还是可以的。
我尝试了许多解决方法,包括:

  1. 把图片放到最顶层(z-index)
  2. 把图片append到body下方
  3. 标签的download属性,手动下载。

但都不能解决问题。最奇葩的是第三个方法,支付宝直接强制退出了。感觉还是base64的问题,支付宝的浏览器可能将其识别成文本了。还查到一种方法是把base64传到服务器,转成图片链接,但我觉得这个方法是在太蠢了,很不优雅。最后找不到别的办法,只能在安卓支付宝上换了一种交互。

另一个问题:ios支付宝上长按图片会唤起两个菜单。
真是涝的涝死,旱的旱死。这两个菜单一个是系统菜单,一个是浏览器菜单。不过还好,这个问题很好解决。加上一行css就可以了。禁止唤起系统菜单。

-webkit-touch-callout: none;

参考

html2canavas官网
使用html2canvas在前端生成图片

你可能感兴趣的:(html2canvas实现html转图片并长按保存)