This article will introduce the implementation of Web Worker’s off-screen Canvas like and simple Canvas like effect.
Canvas implements likes
Create a Canvas
Create a canvas tag on the page element to initialize the canvas.
You can set the width and height properties on the canvas, and you can set the width and height properties in the style property.
- The width and height of the style on the canvas are the height and width of the canvas rendered in the browser, i.e. the actual width and height in the page.
- The width and height of the Canvas tag are the actual width and height of the canvas.
<canvas ref={canvasNode} width="180" height="400"></canvas>
Copy the code
Image to load
Initialize the loading of the picture of sending flowers to obtain information about the picture
/** * load image *@private
* @memberof SendLove* /
private loadImage() {
const imgs = [
'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png'.'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png'.'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png'.'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png'.'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png'.'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png'.'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png'.'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png',]// Continue all loaded image information
const promiseAll: Array<Promise<HTMLImageElement>> = []
imgs.forEach((img: string) = > {
const p = new Promise<HTMLImageElement>((resolve, reject) = > {
const image = new Image()
image.src = img
image.crossOrigin = 'Anonymous'
image.onerror = image.onload = resolve.bind(null, image)
})
promiseAll.push(p)
})
// Get all image information
Promise.all(promiseAll)
.then(lists= > {
this.listImage = lists.filter((img: HTMLImageElement) = > img && img.width > 0)
})
.catch(err= > {
console.error('Image load failed... ', err)
})
}
Copy the code
Smooth moving position
If you want to smooth a curve, you can actually use the familiar math.sin function to achieve a uniform curve.
/** * draw each like; Here we use a closure to initialize *@private
* @returns {(Loop<number, boolean | void>)}
* @memberof SendLove* /
private createRender(): Loop<number, boolean | void> {
if (!this.listImage.length) return null
// The following are the default values initialized at creation time
const context = this.ctx
// Randomly fetch the scale value
const basicScale = [0.6.0.9.1.2] [this.getRandom(0.2)]
// Select a random image
const img = this.listImage[this.getRandom(0.this.listImage.length - 1)]
const offset = 20
// The position of the random animation X-axis is that the animations do not overlap
const basicX = this.width / 2 + this.getRandom(-offset, offset)
const angle = this.getRandom(2.12)
// The X-axis offset is 10-30
let ratio = this.getRandom(10.30) * (this.getRandom(0.1)?1 : -1)
// Get the X-axis value
const getTranslateX = (diffTime: number): number= > {
if (diffTime < this.scaleTime) {
return basicX
} else {
return basicX + ratio * Math.sin(angle * (diffTime - this.scaleTime))
}
}
// Get the Y value
const getTranslateY = (diffTime: number): number= > {
return Number(img.height) / 2 + (this.height - Number(img.height) / 2) * (1 - diffTime)
}
// Scale method multiple creates a scale value for a flower
const getScale = (diffTime: number): number= > {
if (diffTime < this.scaleTime) {
return Number((diffTime / this.scaleTime).toFixed(2)) * basicScale
} else {
return basicScale
}
}
// Random start fade time,
const fadeOutStage = this.getRandom(16.20) / 100
/ / transparency
const getAlpha = (diffTime: number): number= > {
const left = 1 - diffTime
if (left > fadeOutStage) {
return 1
} else {
return 1 - Number(((fadeOutStage - left) / fadeOutStage).toFixed(2))}}return diffTime= > {
if (diffTime >= 1) return true
const scale = getScale(diffTime)
context.save()
context.beginPath()
context.translate(getTranslateX(diffTime), getTranslateY(diffTime))
context.scale(scale, scale)
context.globalAlpha = getAlpha(diffTime)
context.drawImage(img, -img.width / 2, -img.height / 2.Number(img.width), Number(img.height))
context.restore()
}
}
Copy the code
Real-time rendering
Turn on the real-time drawing scanner and put the created render object into the renderList array. The array is not empty, indicating that there are still animations on the canvas, so you need to keep performing scan until there are no animations on the canvas.
/** * scan *@private
* @memberof SendLove* /
private scan() {
// Clear the screen (clear the last drawing)
this.ctx.clearRect(0.0.this.width, this.height)
this.ctx.fillStyle = '#fff'
this.ctx.fillRect(0.0.180.400)
let index = 0
let len = this.renderList.length
if (len > 0) {
// index= 0 after rescan; Recapture length
requestFrame(this.scan.bind(this))
this.scanning = true
} else {
this.scanning = false
}
while (index < len) {
const curRender = this.renderList[index]
if(! curRender || ! curRender.render || curRender.render.call(null, (Date.now() - curRender.timestamp) / curRender.duration)) {
// Animation finished, delete draw
this.renderList.splice(index, 1)
len--
} else {
index++
}
}
}
Copy the code
Provides external interface trigger animation
/** * provides an external like interface *@returns
* @memberof SendLove* /
public likeStart() {
// Initialize the gift data, callback function
const render = this.createRender()
const duration = this.getRandom(1500.3000)
this.renderList.push({
render,
duration,
timestamp: Date.now()
})
if (!this.scanning) {
this.scanning = true
requestFrame(this.scan.bind(this))}return this
}
Copy the code
Web Worker implements off-screen Canvas likes
What is a Web Worker
Web workers allow Javascript to create a multithreaded environment, allowing the main thread to create Worker threads and assign tasks to run in the background. In this way, high-latency, intensive tasks can be carried by the Worker thread, and the main thread can handle UI interaction smoothly without blocking or slowing down
How to use Web Workers in projects
- Introduce the worker-plugin in the Webpack
const WorkerPlugin = require('worker-plugin')
// Add to plugins
new WorkerPlugin()
Copy the code
- The main thread creates a Worker thread by calling the Worker() constructor with the new command
const worker = new Worker('./like.worker', { type: 'module' })
Copy the code
- The main thread and the Worker thread have communication restrictions and are not in the same context, so it can only be done through messages
worker.postMessage({ canvas: offscreenCanvas }, [offscreenCanvas as OffscreenCanvas])
worker.onmessage = function (event) {console.log(event.data)}
Copy the code
-
Worker usage precautions:
- Can’t manipulate DOM, can’t get window, document, parent, etc
- Following the origin restriction, Worker thread scripts must have the same source as the main thread. And loading the script file is blocked
- Improper operation or negligence can cause performance problems
- PostMessage cannot pass functions
Initialize the
The first step is to determine whether the browser supports off-screen Canvas
const init = async() = > {// Many browsers are not compatible with offscreenCanvas. OffscreenCanvas can be used under Windows or Web workers, while Canvas can only be used under Windows
if ('OffscreenCanvas' in window) {
const worker = new Worker('./like.worker', { type: 'module' })
const offscreenCanvas = canvasNode.current.transferControlToOffscreen()
worker.postMessage({ canvas: offscreenCanvas }, [offscreenCanvas as OffscreenCanvas])
worker.addEventListener('error'.error= > {
console.log(error)
})
setNewWorker(worker)
} else {
const thumbsUpAni = new SendLove(canvasNode.current)
setCavasAni(thumbsUpAni)
}
}
Copy the code
About Worker image loading problem
There is no DOM manipulation in Worker, so new Image() will report an error; Used to load images
fetch(img)
.then(response= > response.blob())
.then(blob= > resolve(createImageBitmap(blob)))
Copy the code
// Initialize the image
private loadImage() {
const imgs = [
'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png'.'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png'.'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png'.'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png'.'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png'.'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png'.'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png'.'https://img.qlchat.com/qlLive/activity/image/LCP31WOW-4IMP-NLAE-1620807553972-OYWXNLLNFNJI.png',]const promiseAll: Array<Promise<any>> = []
imgs.forEach((img: string) = > {
const p = new Promise((resolve, reject) = > {
// It is used to process image data, and it is used to draw pictures off-screen
fetch(img)
.then(response= > response.blob())
.then(blob= > resolve(createImageBitmap(blob)))
})
promiseAll.push(p)
})
// It's a bit slow here
Promise.all(promiseAll)
.then(lists= > {
this.listImage = lists.filter((img: ImageData) = > img && img.width > 0)
})
.catch(err= > {
console.error('Image load failed... ', err)
})
}
Copy the code
The logic of liking effect is the same as Canvas processing
/ / to draw private createRender () : Loop (Boolean | void > {the if (! This.listimage.length) return null Const basicScale = [0.6, 0.9, 1.2][this.getrandom (0, Const img = this.listimage [this.getrandom (0, this.listimage.length-1)] const offset = 20 Const basicX = this.width / 2 + this.getrandom (-offset, offset) const Angle = this.getrandom (2, Let ratio = this.getrandom (10, 30) * (this.getrandom (0, 1))? Const getTranslateX = (diffTime: number): number => { if (diffTime < this.scaleTime) { return basicX } else { return basicX + ratio * Math.sin(angle * (diffTime - This.scaletime))}} const getTranslateY = (diffTime: number): Number => {return number (img.height) / 2 + (this.height - number (img.height) / 2) * (1-difftime)} // scale method multiple Create a scale value for a flower const getScale = (diffTime: number): number => { if (diffTime < this.scaleTime) { return Number((diffTime / this.scaleTime).toFixed(2)) * basicScale } else { Return basicScale}} const fadeOutStage = this.getrandom (16, 20) / 100 // const getAlpha = (diffTime: number): number => { const left = 1 - diffTime if (left > fadeOutStage) { return 1 } else { return 1 - Number(((fadeOutStage - left) / fadeOutStage).toFixed(2)) } } return diffTime => { if (diffTime >= 1) return true const scale = getScale(diffTime) context.save() context.beginPath() context.translate(getTranslateX(diffTime), getTranslateY(diffTime)) context.scale(scale, scale) context.globalAlpha = getAlpha(diffTime) context.drawImage(img, -img.width / 2, -img.height / 2, Number(img.width), Number(img.height)) context.restore()}} private scan() {this.ctx.clearRect(0, 0, 0) this.width, this.height) this.ctx.fillStyle = '#fff' this.ctx.fillRect(0, 0, 180, Let len = this.renderlist. length if (len > 0) { RequestFrame (this.scans.bind (this)) this.scanning = true} else {this.scanning = false} while (index < len) {requestFrame(this.scans.bind (this)) this.scanning = true} else {this.scanning = false} const curRender = this.renderList[index] if (! curRender || ! CurRender. Render | | curRender. Render. Call (null, (Date. Now () - curRender. Timestamp)/curRender duration)) {/ / animation has ended, Render this.renderList.splice(index, 1) len--} else {index++}}} public likeStart() {// initialize gift data, callback function const render = this.createrender () const duration = this.getRandom(1500, 3000) this.renderList.push({ render, duration, timestamp: Date.now() }) if (! this.scanning) { this.scanning = true requestFrame(this.scan.bind(this)) } return this }Copy the code
The last
Both ways of rendering thumbs-up animations have been completed. The complete code
That’s the end of this article. I hope it helps.
Xiaobian for the first time to write an article writing style is limited, untalented and shallow, the article if there is wrong, hope to inform.