解决批量绘制canvas卡顿的问题

前情提要:项目采用 vue3 + element-plus 的结构

需求:在背景图上画出红色矩形,圈出重点,格式类似下面

解决卡顿方法:

1.图片懒加载(很重要)

2.图片本地缓存(比较重要)

3.创建web worker子线程(效果不佳)

思路存在,开始写代码

1.创建每一组图片的 ImageGroup.vue

<template>
  <div>
    <div style="display: flex;">
      <div style="flex: 1;">
        <el-image
          style="width: 100%; height: 107px"
          :src="url1"
          :zoom-rate="1.2"
          :max-scale="7"
          :min-scale="0.2"
          :preview-src-list="srcList"
          :initial-index="0"
          fit="cover"
          lazy
          @load="onload"/>
        <div>标准图</div>
      </div>
      <div style="flex: 1;">
        <el-image
          style="width: 100%; height: 107px"
          :src="url2"
          :zoom-rate="1.2"
          :max-scale="7"
          :min-scale="0.2"
          :preview-src-list="srcList"
          :initial-index="1"
          fit="cover"
          lazy>
          <template #error>
            <div>加载中</div>
          </template>
        </el-image>
        <div>canvas图</div>
      </div>
    </div>
  </div>
</template>

js代码

<script setup>
import { ref, onMounted, computed } from 'vue'
import { useImageStore } from '@/stores/image' // 图片缓存

const imageStore = useImageStore()

const info = ref({}) // 每一组图片的信息,包含id,图片url,和矩形四个角在图片上的绘制位置等(这里是子组件,应该从父组件获取)

const url1 = ref('') // 标准图的url

const url2 = ref('') // canvas图的url

const srcList = computed(() => { // 图片预览列表
  return [url1.value, url2.value]
})

const bgUrl = ref('') // 图二的背景图

const pointList = ref([]) // 红框的点位信息

let worker // web worker子线程

const createWorker = () => { // 创建web worker子线程
  if (!worker) {
    worker = new Worker('/apple/worker/worker.js') // 这里的路径要注意,和我下面的图对照一下
  }
  return worker
}

// 1.首先加载标准图
onMounted(() => {
  let images = info.value.image
  url1.value = images[0].url
})

// 2.(el-image的加载事件调用,这里取个巧,用element的懒加载功能带动canvas的懒加载)标准图加载,用于懒加载实景图
function onload() {
  let images = info.value.image
  if(info.value.pointList) { // 有标记红框的图片,才需要画图(为了节省性能)
    pointList.value = info.value.pointList // 设置点位信息
    bgUrl.value = images[1].url // 设置背景图

    // 获取图片缓存,有缓存的直接显示,无缓存的则生成base64(为了节省性能)
    const img = imageStore.findImage(info.value.id)
    if(img) {
      url2.value = img.url
    } else {
      generateBase64()
    }
  } else { // 没有标记红框的图片,直接显示,不进行画图
    url2.value = images[1].url
  }
}
// 3.生成图片base64
const generateBase64 = async () => {
  const worker = createWorker() // 创建web worker子线程
  
  worker.onmessage = (event) => { // 接收子线程返回的base64数据
    if (event.data.type === "toDataURL") {
      url2.value = event.data.img
      // 缓存图片,主键为id
      imageStore.addImageList({id: info.value.id, url: event.data.img})
      worker.terminate() // 结束子线程
    }
  }
  worker.onerror = (error) => {
    console.log(error)
  }

  // 将canvas数据发送给Web Worker
  worker.postMessage({ type: 'toDataURL', pointList: JSON.stringify(pointList.value), bgUrl: bgUrl.value })
}
</script>

2.创建本地缓存,目的是尽量减少canvas绘画次数

import { ref } from 'vue'
import { defineStore } from 'pinia'

export const useImageStore = defineStore('image', () => {
  const imageList = ref([])

  function addImageList(image) {
    if(!imageList.value.filter((item) => item.id === image.id)[0]) 
    imageList.value.push(image)
  }

  function resetImageList() {
    imageList.value = []
  }

  function findImage(id) {
    return imageList.value.filter((item) => item.id === id)[0]
  }

  return {
    imageList,
    addImageList,
    resetImageList,
    findImage
  }
})

3.web woker代码(在这里用canvas比较麻烦)

 

onmessage = async function(event) {
  // 接收到消息时开始处理数据
  const { type, pointList, bgUrl } = event.data;
  const points = JSON.parse(pointList)
  // 获取背景图信息
  const response = await fetch(bgUrl) // 获取图片
  const blob = await response.blob() // 获取blob对象
  const image = await createImageBitmap(blob) // 创建一个ImageBitmap对象

  if (type !== 'toDataURL') return 
  const canvas = new OffscreenCanvas(100, 1) // 子线程中获取不到实体canvas,只能用OffscreenCanvas了
  const ctx = canvas.getContext('2d')
  canvas.width = image.width
  canvas.height = image.height
  ctx.drawImage(image, 0, 0)
  points.forEach(item => {
    const [x,y,w,h] = item[0]
    ctx.strokeStyle = "rgb(255, 0, 0)"
    ctx.lineWidth = 2
    ctx.strokeRect(Number(x), Number(y), Number(w), Number(h))
  })

  const bloba = await canvas.convertToBlob() // OffscreenCanvas没有转换base64的方法,只能先转换成blob

  // 然后创建一个文件读取器
  let reader = new FileReader()
  reader.onload = () => {
    let data = reader.result
    postMessage({ type, img: data })
  }

  // 读取blob对象为DataURL
  reader.readAsDataURL(bloba)
}

总结:懒加载是很有必要的,本地缓存效果也不错,web woker子线程只是提供了一种优化思路,但是效果聊胜于无,感兴趣的可以试一下。

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值