这里引用上个文章的图片 这里学习下 webWorker
javascript
<template>
<div>
<div style="display: flex; justify-content: center; align-items: center; margin-top: 20px;">
<div class="image-container">
<img src="https://i.imgur.com/cCWBPHi.png" class="final-image">
</div>
<div class="image-container">
<!-- 使用 v-show 确保 canvas 元素始终存在 -->
<canvas v-show="!isComplete" ref="canvasRef" class="canvas" />
<img v-show="isComplete" :src="finalImageData" alt="加载完成的图片" class="final-image">
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
// 定义变量
const canvasRef = ref(null) // canvas 元素引用
const loading = ref(false) // 加载状态
const error = ref(false) // 错误状态
const isComplete = ref(false) // 是否加载完成
const finalImageData = ref('') // 最终图片的 Base64 数据
// 创建 Worker 实例
const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' })
// 监听 Worker 的消息
worker.onmessage = function (event) {
const { type, data } = event.data
if (type === 'progress') {
console.log(`当前分片高度: ${data.chunkHeight}px, 当前绘制位置: y = ${data.y}px`)
}
else if (type === 'complete') {
console.log('图片加载完成!')
finalImageData.value = data.imageData
isComplete.value = true
loading.value = false
worker.terminate() // 释放 Worker 主线程资源
}
else if (type === 'error') {
console.error('加载图片失败:', data.message)
error.value = true
loading.value = false
}
}
// 点击按钮加载图片
function loadImage() {
// 确保 canvas 元素已经渲染
if (!canvasRef.value) {
console.error('canvas 元素未渲染')
return
}
// 将 canvas 的控制权转移到 OffscreenCanvas
// OffscreenCanvas 是 HTML5 提供的一个 API,它允许你将 canvas 的渲染操作放到主线程之外的上下文中执行(例如 Web Worker),从而避免阻塞主线程,提升性能
const offscreenCanvas = canvasRef.value.transferControlToOffscreen()
// 发送任务给 Worker
worker.postMessage(
{
canvas: offscreenCanvas,
url: 'https://i.imgur.com/cCWBPHi.png',
chunkSize: 100,
},
[offscreenCanvas],
)
loading.value = true
error.value = false
}
// 在 onMounted 钩子中调用 loadImage,确保 DOM 已渲染
onMounted(() => {
loadImage()
})
</script>
<style lang="scss" scoped>
.image-container {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
.canvas {
border: 1px solid #ccc;
max-width: 100%;
max-height: 80vh;
/* 限制 canvas 的最大高度 */
}
.final-image {
max-width: 100%;
max-height: 80vh;
/* 限制图片的最大高度 */
border: 1px solid #ccc;
}
</style>
下面是 worker.js 文件
其实通信原理和 iframe 一样 只不过可以开启多线程
javascript
// 监听主线程的消息 这里的self也就是主线程的window对象
self.onmessage = async function (event) {
const { canvas, url, chunkSize } = event.data
try {
// 加载图片
const response = await fetch(url)
const blob = await response.blob()
const img = await createImageBitmap(blob)
// 设置 canvas 尺寸
const width = img.width
const height = img.height
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d')
// 分片加载逻辑
let y = 0 // 初始化 y 坐标,表示当前绘制的位置
const loadNextChunk = function () {
// 计算当前分片的高度
const chunkHeight = Math.min(chunkSize, height - y)
// 绘制当前分片
ctx.drawImage(img, 0, y, width, chunkHeight, 0, y, width, chunkHeight)
y += chunkHeight // 更新 y 坐标,准备绘制下一个分片
// 发送进度信息给主线程
self.postMessage({
type: 'progress',
data: { chunkHeight, y },
})
// 如果未加载完,继续加载下一个分片
if (y < height) {
requestAnimationFrame(loadNextChunk) // 使用 requestAnimationFrame 优化渲染
}
else {
// 加载完成,将 canvas 转换为图片并发送给主线程
canvas.convertToBlob().then((blob) => {
const reader = new FileReader()
reader.readAsDataURL(blob)
reader.onloadend = function () {
self.postMessage({
type: 'complete',
data: { imageData: reader.result },
})
}
})
}
}
// 开始加载第一个分片
loadNextChunk()
}
catch (e) {
console.error('加载图片失败:', e)
self.postMessage({
type: 'error',
data: { message: e.message },
})
}
}
今天,我在学习Web Workers的时候,遇到了一些概念,感觉有点复杂。我决定仔细研究一下,看看能不能理解清楚。
首先,我知道Web Workers是一种在后台线程中运行JavaScript代码的方法,可以用来处理复杂的计算任务,从而不阻塞主线程,提升用户体验。但具体的实现方式,我还不太清楚。
我看到代码中使用了new Worker()
方法来创建一个Web Worker。这让我想到,是不是在主线程中创建了一个新的线程,专门用来处理特定的任务?好像是这样。然后,主线程通过worker.postMessage()
方法向Worker发送任务。这让我联想到Vue.js中父组件和子组件之间的通信,父组件通过$emit
方法向子组件传递数据,而子组件通过事件监听来接收数据。难道这里也是类似的机制?
于是,我查找了一些资料,发现确实,postMessage
方法在主线程和Worker之间传递数据,就像组件之间的事件传递一样。主线程发送消息,Worker接收并处理,然后通过self.postMessage()
将结果发送回去。这确实有点像Vue2中的父子传值,只不过这里的"子"是Worker线程。
接下来,我看到在主线程中使用了worker.onmessage
来监听Worker的消息。这部分有点让我困惑。我想,onmessage
是不是一种事件监听器,当Worker发送消息回来时,主线程就能接收到并处理?这和Vue中的事件处理很像,父组件监听子组件的事件,当子组件触发事件时,父组件执行相应的处理函数。
为了更好地理解,我尝试写了一个简单的例子。主线程创建一个Worker,然后通过postMessage
发送一个计算任务,Worker接收到任务后进行计算,然后通过postMessage
把结果送回主线程。主线程监听到消息后,处理结果并显示出来。运行后,发现确实有效,计算任务在后台线程完成,主线程继续处理其他事情。
但是,我也有疑问。比如,Worker和主线程之间的通信是不是只能通过postMessage
?有没有其他方式?另外,self
在Worker中的作用是什么?为什么不能用this
或者其他变量?
我查阅了相关文档,了解到在Worker中,self
是指向Worker全局对象的,可以用它来发送和接收消息。而主线程和Worker之间的通信只能通过postMessage
方法,这是为了确保线程安全,避免共享内存带来的问题。
还有一个问题,如果主线程和Worker之间传递的是对象,会发生什么?会不会导致数据共享的问题?文档中提到,传递的是结构化克隆,而不是引用,因此不会共享内存,每个线程都有自己的数据副本,这样就避免了竞态条件和数据不一致的问题。
通过这个学习过程,我对Web Workers有了更深入的理解,特别是它们和Vue组件之间的类比,帮助我更好地记忆和应用这些概念。虽然有些细节还需要进一步探索,但总体上,我觉得自己已经掌握了一个基本的框架。
总结:
使用 new Worker()
方法在主线程创建了一个 Web Worker,主线程通过 worker.postMessage()
发送任务给 Worker,并在主线程开启 worker.onmessage
监听 Worker 的消息。这与 Vue2 中的父子组件通信机制相似,主线程类似于父组件,Worker 线程类似于子组件。Worker.js 文件中通过 self.postMessage()
将结果发送回主线程,主线程在接收到消息后进行处理。