JavaScript中如何检测canvas密集处理任务的起止并避免页面卡顿
Canvas 大图片绘制卡顿与加载提示解决方案
任务起止节点检测逻辑
你提到的ctx.drawImage卡顿本质是大尺寸图片的同步解码+绘制操作占用浏览器主线程导致的,这类同步任务的起止可以通过以下两种方式标记,同时保证UI提示正常显示、页面不卡顿:
方案1:轻量场景快速实现(图片尺寸<4000x4000)
同步绘制前先触发加载提示渲染,等待浏览器完成一帧UI更新后再执行绘制任务,绘制完成后关闭提示即可:
// 任务开始:先展示加载提示 showLoadingTip() // 等待浏览器完成UI渲染,避免提示被绘制任务阻塞无法显示 setTimeout(() => { // 执行绘制任务 ctx.drawImage( image, crop.x * scaleX, crop.y * scaleY, crop.width * scaleX, crop.height * scaleY, 0, 0, crop.width * scaleX, crop.height * scaleY, ) // 任务结束:关闭加载提示 hideLoadingTip() }, 0)
该方案实现简单,但超大尺寸图片绘制时仍会出现短时间卡顿。
方案2:无卡顿高性能实现(推荐,支持超大尺寸图片)
使用OffscreenCanvas把绘制逻辑放到Web Worker线程执行,完全不阻塞主线程的UI渲染和用户交互,绘制完成后Worker主动通知主线程关闭提示即可:
主线程代码
// 1. 展示加载提示 showLoadingTip() // 2. 异步解码图片,避免同步解码阻塞主线程 const imageBitmap = await createImageBitmap( image, crop.x * scaleX, crop.y * scaleY, crop.width * scaleX, crop.height * scaleY ) // 3. 把普通canvas转换为可跨线程传递的OffscreenCanvas const canvas = document.querySelector('canvas') const offscreenCanvas = canvas.transferControlToOffscreen() const drawWorker = new Worker('draw-worker.js') // 4. 传递绘制所需数据到Worker线程 drawWorker.postMessage({ canvas: offscreenCanvas, image: imageBitmap, width: crop.width * scaleX, height: crop.height * scaleY }, [offscreenCanvas, imageBitmap]) // 5. 监听绘制完成消息,关闭加载提示 drawWorker.onmessage = () => { hideLoadingTip() }
Worker线程代码(draw-worker.js)
self.onmessage = (e) => { const { canvas, image, width, height } = e.data const ctx = canvas.getContext('2d') // 执行绘制任务 ctx.drawImage(image, 0, 0, width, height) // 通知主线程绘制完成 self.postMessage({ status: 'success' }) }
方案3:兼容旧浏览器的分块绘制方案
如果需要兼容不支持OffscreenCanvas的旧浏览器,可以把大图片拆分为多个小分片,逐帧调度绘制,每次绘制只占用极短主线程时间,保证页面响应和加载提示正常更新:
const TOTAL_CHUNKS = 20 // 可根据图片尺寸调整分片数量 let currentChunk = 0 const chunkHeight = (crop.height * scaleY) / TOTAL_CHUNKS // 任务开始:展示加载提示 showLoadingTip() function drawNextChunk() { if (currentChunk >= TOTAL_CHUNKS) { // 任务结束:关闭加载提示 hideLoadingTip() return } // 计算当前分片的坐标参数 const sourceY = crop.y + currentChunk * chunkHeight / scaleY const targetY = currentChunk * chunkHeight // 绘制当前分片 ctx.drawImage( image, crop.x * scaleX, sourceY, crop.width * scaleX, chunkHeight / scaleY, 0, targetY, crop.width * scaleX, chunkHeight ) // 可选:更新加载进度百分比 updateLoadingProgress(Math.round((currentChunk / TOTAL_CHUNKS) * 100)) currentChunk++ // 调度下一帧绘制 requestAnimationFrame(drawNextChunk) } drawNextChunk()
内容的提问来源于stack exchange,提问作者mamady
相关产品推荐
相关产品推荐

