使用html2canvas截图踩坑总结

       年底的移动端H5需求中,再次用到了html2canvas这个插件,这个插件主要是用来对网页进行截图,在项目需求中,有个交互的点,就是通过用户操作,将页面的内容截图保存下来,方便用户传播扩散。

H5说明:

        H5的大致交互:用户进入页面,首先loading加载页面图片资源,加载完成后,显示相关图片元素,随后进入下个页面,用户可以通过点击按钮截图当前内容,并且分享出去,最后一页则是用户的个性标签页,同样是可以截图分享出去的

        html2canvas版本1.4.1

做这个需求里面,碰到的问题点:

      1、用户第一次进入页面,一整套流程下来没有问题,包括截图等操作,但是问题来了,再次进入页面后,会发现页面卡在加载图片资源那边,通过手机连接电脑,打开网页检查器发现控制台有报错,报错内容如下:

       按照预期的效果是不会出现跨域问题的,因为已经在obs配置好了跨域资源共享 

       2、使用html2canvas测试截图的过程中发现,截图后保存的图片文件里面,一些特定的元素底部边缘,会有颜色断层,看着很突兀,实际在页面上查看是不会出现这个问题的

底部有颜色断层
底部有颜色断层

这里要说明一下,这些元素的背景是使用了渐变背景色,比如这样

        3、在 ios 下使用 html2canvas 截图时,会触发页面全部资源的重新加载包括音频资源,这就导致了截图后,ios 会重复播放背景音乐,导致听起来会有多条音轨同时播放的感觉

 如何解决问题:

       1. 跨域问题:

       这个问题困扰了我几天,明明我在华为云obs已经设置了跨域选项,也就是常用的

Access-Control-Allow-Origin:*

在电脑上代码程序跑起来正常,不会出现跨域错误的提示,然而,在后面是ios机子上面发现第一次打开是不会出现问题的,第二次就会,一开始是以为这个问题是偶然触发,多次测试(清缓存等),才的出来规律。但是这个问题以前没碰到过,然后再掘金的一篇文章(跨域漏洞,我把前端线上搞崩溃了)上看到这句话

这句话一下子就点亮了我的思路,我们来模拟下明明配置了跨域资源共享 ,第二次打开页面还是会出现跨域问题。

       前言:本复现页面上有一个audio标签,图片加载进度条,使用js(new Image方法)加载图片资源,并且资源加载成功后,会通过img标签显示其中一张图片,代码如下:

<template>
  <div>
    <p>进度条</p>
    <div class="box">
      <div class="inner" :style="{ width: `${state.current}%` }"></div>
    </div>
    <div class="text-box">
      <p>测试图片测试图片</p>
      <div class="image-wrap">
        <!-- <img v-if="state.current === 100" :src="state.imageUrl" alt="" /> -->
        <img :src="state.imageUrl" alt="" />
      </div>
      <p>图片地址: {{ state.imageUrl }}</p>
    </div>
  </div>
</template>
<script setup>
import imageLoader from './someJs/imageLoader'
import imageUrls from './someJs/imageFiles'
import { reactive, onMounted } from 'vue'

const state = reactive({
  current: 0,
  imageUrl:
    'https://mobile-assets.obs.cn-south-1.myhuaweicloud.com/image/auto/driving-report/images/scence/pic_together_z03.png'
})

onMounted(() => {
  imageLoader({
    imageUrls,
    onProgress: (val) => {
      state.current = val
    }
  })
})
</script>
<style scoped lang="scss">
.box {
  width: 400px;
  height: 20px;
  border-radius: 10px;
  border: 1px solid #888;
  box-sizing: border-box;
  overflow: hidden;
  .inner {
    background: #888;
    height: 100%;
  }
}

.text-box {
  background-color: aqua;
  width: 400px;
  margin-top: 100px;
  .image-wrap {
    width: 300px;
    height: 300px;
    overflow: hidden;
    & > img {
      width: 100%;
      height: 100%;
    }
  }
}
</style>

       首先我们打开控制台,在 Network 选项卡里面,将 Disabled cache 打钩,模拟没有缓存第一次进入,如下图

 我们观察控制台

能够看到,第一次打开页面,一切正常,能够按照预期展示页面内容,js进度条也能够正常加载图片资源。此时我们再将 Disabled cache 的打钩给取消掉,再次刷新页面

我们可以看到,再次刷新页面后,出现了跨域的报错,在 Network 里面,出现跨域的地方是发起图片资源请求的 imageLoader 报错了,进度条没有加载完就可以看到已经出现了报错了。

分析以下第一次加载页面的过程:

       1、先使用 js 加载图片资源

       2、加载成功后,再显示图片

       注意这个顺序是先 js,再通过 img 标签来直接显示图片。使用 js 发起的请求,在这种条件(图片在obs桶)下,是属于跨域请求的,故头部会带上 Origin 这个属性(头部 Origin 属性的介绍),如果是同源,则不需要带上这个头部。

js请求图片资源
img标签可以直接获取并显示图片,并不受同源策略的影响

       可以看到,js 加载完成后,img标签又去再获取一次同一个图片资源,那么问题来了,第一次加载页面是能够按照预期执行成功的,但是第二次 js 加载同一个文件就出现了跨域错误。我通过多次复现这个情况,猜测是因为后面(img标签)获取同一个图片资源,将前面缓存的图片信息给覆盖掉,缓存里面没有 cross-origin-access-control 这个字段,导致 js 第二次get请求获取的时候直接触发同源策略,代码报跨域错误。

       要验证以上猜测,很简单,只需要调整图片的加载顺序即可,我们只需要在代码里面,将img的显示时机改为直接显示,也就是优先于 js 的请求,代码修改如下(省略了css)

<template>
  <div>
    <p>进度条</p>
    <div class="box">
      <div class="inner" :style="{ width: `${state.current}%` }"></div>
    </div>
    <div class="text-box">
      <p>测试图片测试图片</p>
      <div class="image-wrap">
        <!-- 当加载完成才显示img标签 -->
        <!-- <img v-if="state.current === 100" :src="state.imageUrl" alt="" /> -->
        <!-- 直接显示 -->
        <img :src="state.imageUrl" alt="" />
      </div>
      <p>图片地址: {{ state.imageUrl }}</p>
    </div>
  </div>
</template>
<script setup>
import imageLoader from './someJs/imageLoader'
import imageUrls from './someJs/imageFiles'
import { reactive, onMounted } from 'vue'

const state = reactive({
  current: 0,
  imageUrl:
    'https://xxxxx.com/pic_together_z03.png'
})

onMounted(() => {
  // dom挂载后再js加载
  imageLoader({
    imageUrls,
    onProgress: (val) => {
      state.current = val
    }
  })
})
</script>
<style scoped lang="scss">

</style>

       现在代码的执行就是组件挂载后,img标签先发起拉取图片的请求,不携带 Origin 头部,随后页面挂载回调,执行 js 请求图片资源,

先img标签,再js获取图片

这种情况下,当我们把控制台的 Disabled Cache 关闭后,再次刷新页面,js获取的图片资源,会发现是来自缓存的,并且正确携带有 cors 相关头部

       至此,问题一解决,出现这种问题也是由于自身对http头部的不熟悉引起的,如果了解更深入一些,或许可以减少调试时间

       2. 截图渐变色彩背景出现断层问题

       这个问题解决起来比较简单,一开始以为是图片保存质量问题,修改了几次导出图片质量,发现还是会有,排除图片质量问题。在排除问题的过程中,我写了个简单的demo来复现问题,发现并不会出现

<template>
  <div>
    <div class="box" ref="target"></div>
    <button @click="handlePrtScClick">截图</button>
    <div class="result">
      截图结果
      <img :src="state.base64" alt="" />
    </div>
  </div>
</template>
<script setup>
import html2canvas from 'html2canvas'
import { ref, reactive } from 'vue'

const target = ref()

const state = reactive({
  base64: ''
})

const handlePrtScClick = () => {
  html2canvas(target.value, {
    scale: 2,
    useCORS: true
  }).then((canvas) => {
    state.base64 = canvas.toDataURL('image/jpeg', 0.8) // 保存图片质量为0.8
  })
}
</script>
<style lang="scss" scoped>
.box {
  width: 300px;
  height: 200px;
  background-image: linear-gradient(180deg, #7aeee7 0%, #e3f4c7 100%);
}
.result {
  width: 300px;
  & > img {
    width: 100%;
  }
}
</style>

代码的运行结果如下图,并未出现截图色彩断层问题

      demo没问题,但是我们demo的样式是使用px去写的,在项目中,我使用的rem布局,用rem布局写一个试试,代码修改如下

<template>
  <div>
    <div class="box" ref="target"></div>
    <button @click="handlePrtScClick">截图</button>
    <div class="result">
      截图结果
      <img :src="state.base64" alt="" />
    </div>
  </div>
</template>
<script setup>
import html2canvas from 'html2canvas'
import { ref, reactive } from 'vue'

const target = ref()

const state = reactive({
  base64: ''
})

const handlePrtScClick = () => {
  html2canvas(target.value, {
    scale: 2,
    useCORS: true
  }).then((canvas) => {
    state.base64 = canvas.toDataURL('image/jpeg', 0.8) // 保存图片质量为0.8
  })
}
</script>
<style lang="scss" scoped>
.box {
  width: 16rem;
  height: 9.3rem;
  background-image: linear-gradient(180deg, #7aeee7 0%, #e3f4c7 100%);
}
.result {
  width: 16rem;
  & > img {
    width: 100%;
  }
}
</style>

运行后截图,发现结果图片最下方有明显的颜色断层,通过查看css发现,被截图元素的高度,出现了小数,然后我们的渐变方向又是从上而下的,所以导致了截图出现了颜色断层现象。通过查找html2canvas 的源代码发现,在处理渐变色彩的时候,在使用了createPattern 重复渐变背景,然后html2canvas 对画布的宽高计算方式,是向下取整的,这就导致了画布和渲染渐变的地方有误差,误差部分就被repeat出来了,导致最上方的色彩跑到最下方多出来的像素区域

       3. 同时播放多个背景音乐问题:

       这个问题只有在 ios 上才会出现,安卓和 pc 端 chrome 都正常,一开始是认为 ios 的问题,和同时多次调试后发现,当点击截图后,在控制台的 Network 选项卡里面看到多出来了一个音频资源的请求,ios 就会自动播放这个资源,安卓则不会。在写这个文章的时候,我自己调试,发现页面是有一闪而过的 iframe,后来看文档发现,有个参数,如下图:

 大概意思是是否移除被临时克隆出来的 dom 元素,默认是克隆完成后自动移除,在代码里面将此参数 removeContainer 置为 false 后,发现 html2canvas 将页面整体克隆出来,当有存在audio 标签(preload=“auto”)的时候,ios就会自动播放。

       解决这个问题就需要用到 ignoreElements 这个参数:
 

当 element 是你的目标元素时,返回 true 则可以忽略元素。或者还有另外一个办法,我在源码里面找到了 IGNORE_ATTRIBUTE 这个变量,它的值是 data-html2canvas-ignore ,当在标签上配置这个值,则可以忽略当前标签元素的克隆,就可以解决我们碰到的这个问题。

部分源码

结语:

        至此,坑点的解决思路已经完成,可以说是在写的时候没细致阅读文档,也有一些是基础知识不过关,导致花费了挺多时间去排查问题。但还存在着其他问题点未搞定,比如用 URL.createObjectURL 创建出来的临时 url,有些莫名其妙访问不到,这个问题我现在还没有找到原因,待后续摸索。

       博文写得比较口水化,请大家多多指教。

  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值