There is a magical place where you can watch TV series and variety shows, as well as short live videos and games. Today see the village bully to find which brother, tomorrow see zhang SAN into which prison.
Yes, I am talking about bilibili bullet screen. Some time ago, WHEN I was fishing, I found that the banner of this website has changed, the eyes of the characters will blink and blink, and the depth of field and displacement will change when the mouse moves up. Have 丶, I press F12 observed a wave, found is to use a few pictures with CSS. Just before Vue 3.0 was released, I wanted to make a demo to write, so I used Vite to build a simple project. The Bilibilii banner effect was implemented using Vue 3.0’s Composition API and Canvas. The specific effect can be viewed on my GitHub.
Start by creating a project using Vite
Vite is a vUE official new build tool, its characteristics is a word: fast. How close is it? You just open up your browser, you’re about to take off your pants, type localhost, and it’s built. I don’t know if it’s true. I made it up
Type directly from your command line
npm init vite-app bilibili-autumn
Copy the code
In this case, the CLI tool of Vite is automatically downloaded and a bilibili-autumn folder is generated in the current directory, and some prompts are displayed on the command line
Done. Now run:
cd bilibili-autumn
npm install (or `yarn`)
npm run dev (or `yarn dev`)
Copy the code
Run the project as prompted. The entire directory structure of the project looks like this
. ├ ─ ─ index. The HTML ├ ─ ─ package. The json ├ ─ ─ public │ └ ─ ─ the favicon. Ico └ ─ ─ the SRC ├ ─ ─ App. Vue ├ ─ ─ assets │ └ ─ ─ logo. The PNG ├ ─ ─ ├─ ├─ exclude.org.txt (3 files) ├─ exclude.txt (4 filesCopy the code
Now let’s start implementing the banner effect
Gather the necessary materials
Press F12 to Open the console, view the DOM structure of the banner, unplug all the images (right-click Open in New TAB + Ctrl S to save the image)
Place the saved images in the SRC/Assets folder of your project
Began to lu code
You can start by opening the documentation for Vue 3.0 to see what the API changes are.
Some code fragments are selected below. The complete code and its effect can be viewed on Github
1. Create oneBanner.vue
component
<template>
<div ref="placeholder" class="banner">
<img class="banner-placeholder" src="/src/assets/full-bg.png" />
<canvas :ref="(el) => (layers.bg = el)" class="banner-layer"></canvas>
<canvas :ref="(el) => (layers.twotwo = el)" class="banner-layer"></canvas>
<canvas :ref="(el) => (layers.land = el)" class="banner-layer"></canvas>
<canvas :ref="(el) => (layers.ground = el)" class="banner-layer"></canvas>
<canvas
:ref="(el) => (layers.threethree = el)"
class="banner-layer"
></canvas>
<canvas :ref="(el) => (layers.grass = el)" class="banner-layer"></canvas>
</div>
</template>
Copy the code
The main implementation idea is to use Canvas to draw pictures on each layer. Here, a static background image is introduced using , which is used to expand the parent
2. Logic for drawing layers
Different depth of field can be achieved by setting different filter: blur() for each layer of picture, and displacement can be achieved by setting different starting coordinates for each frame of picture
function draw(image, config) {
const { sx = 0, sy = 0, sw, sh, blur: b } = config || {}
return {
to(canvas) {
const ctx = canvas.getContext('2d')
ctx.imageSmoothingEnabled = true
ctx.clearRect(0.0, canvas.width, canvas.height)
ctx.filter = `blur(${b}px)`
ctx.drawImage(image, sx, sy, sw, sh, 0.0, canvas.width, canvas.height)
},
}
}
Copy the code
3. Get a reference to the image and Canvas element in the appropriate lifecycle and draw the image to the Canvas
// Note that vite handles assets and returns the address of the asset
import bg from '/src/assets/bg.png'
const images = reactive({
bg: null,})function buildImage(src) {
return new Promise((resolve) = > {
const image = new Image()
image.onload = () = > resolve(image)
image.src = src
})
}
onBeforeMount(() = > {
buildImage(bg).then((img) = > (images.bg = img))
})
onMounted(() = > {
watch(
() = > images.bg,
() = > draw(images.bg, config.bg).to(layers.bg)
)
})
Copy the code
4. Listen for mouse events to handle depth of field and shift changes
const enterPoint = {}
placeholder.value.addEventListener('mouseenter'.(e) = > {
const { width } = placeholder.value.getBoundingClientRect()
enterPoint.x = e.clientX
enterPoint.w = width
})
// As the mouse moves, calculate the ratio of the distance between the current position and the initial position
placeholder.value.addEventListener('mousemove'.(e) = > {
const v = e.clientX - enterPoint.x
const ratio = v / enterPoint.w
requestAnimationFrame(() = > render(ratio))
})
// When the mouse mouse leaves, slowly return to the initial frame state (uniform transition)
placeholder.value.addEventListener('mouseout'.(e) = > {
const v = e.clientX - enterPoint.x
let ratio = v / enterPoint.w
const gap = 0.08 * (ratio < 0 ? 1 : -1)
requestAnimationFrame(tick)
function tick() {
if (gap * ratio < 0) {
ratio = ratio + gap
render(ratio)
requestAnimationFrame(tick)
} else {
if (images.bg) {
draw(images.bg, config.bg).to(layers.bg)
}
}
}
})
// Calculate the depth of field and displacement according to the proportion of the distance between the mouse position and the initial position
function render(ratio) {
if (ratio < 0 && images.bg) {
constc = { ... config.bg } c.blur = c.blur + ratio * c.blur draw(images.bg, c).to(layers.bg) } }Copy the code
5. Drawing blink frames of characters
setTimeout(wink, 4800)
async function wink() {
await new Promise((r) = > setTimeout(r, 50))
images.twotwoClosingEye &&
draw(images.twotwoClosingEye, config.twotwo).to(layers.twotwo)
await new Promise((r) = > setTimeout(r, 50))
images.twotwoCloseEye &&
draw(images.twotwoCloseEye, config.twotwo).to(layers.twotwo)
await new Promise((r) = > setTimeout(r, 50))
images.twotwoOpeningEye &&
draw(images.twotwoOpeningEye, config.twotwo).to(layers.twotwo)
await new Promise((r) = > setTimeout(r, 50))
images.twotwo && draw(images.twotwo, config.twotwo).to(layers.twotwo)
setTimeout(wink, 4800)}Copy the code
6. Redraw as the visual window size changes
function resize(layers) {
return {
with({ width, height }) {
for (const canvas of Object.values(layers)) {
canvas.width = width
canvas.height = height
}
},
}
}
onMounted(async () => {
resize(layers).with(placeholder.value.getBoundingClientRect())
window.addEventListener('resize'.() = > {
resize(layers).with(placeholder.value.getBoundingClientRect())
images.bg && draw(images.bg, config.bg).to(layers.bg)
})
})
Copy the code
Implementation effect
Note that the GIF is a bit large and the recorded effect is not very obvious
Finally, let’s guess if the S10 LPL will win again