分析
移动版的抖音直播用户可以点击屏幕来给主播点赞,点赞的时候会出点击的位置会出现图标,另外在屏幕的右下角也会出现相应的效果。
效果展示
代码实现
1 创建一个 canvas 元素
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>抖音直播点赞效果实现</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
canvas {
background-color: #161823;
}
</style>
</head>
<body>
<canvas id="like"></canvas>
</body>
</html>
2 初始化 Canvas
const canvas = document.getElementById('like')
canvas.width = window.innerWidth
canvas.height = window.innerHeight
// 获取 canvas 上下文对象
const ctx = canvas.getContext('2d')
3 分析动画效果
- 点击屏幕的时候相应的位置出现图标,图标慢慢变大,并逐渐消失
- 右下角也会出现对应的图标,图标会往左上或者右上移动,并逐渐消失
- 每次点击出现的图标是随机的
这两步我们可以使用两个数组来存储相应的数据(其实准确来说,我们使用的是栈,但是 Javascript 中的数组包含了栈的所有特性)。
图标呢?这里我们使用 emoji 表情包来代替。
对于随机的图标,我们只需要使用 Math.random()来实现随机数的生成就可以了
// emoji
const emoji = ['🍇', '🍈', '🍉', '🍊', '🍋', '🍌', '🍍', '🥭', '🍎', '🍒', '🧄', '🍘', '🍙', '🍚', '🍛', '🥡', '🦀', '🦞', '🦐', '🦑', '🦪']
// 点击位置显示列表
let showList = []
// 右小角所有点赞效果
let allList = []
// 生成 min 到 max 间的随机数
function random(min, max) {
return Math.floor(Math.random() * (max - min)) + min
}
// 监听点击事件
canvas.addEventListener('click', e => {
// 获取随机位置
const index = random(0, emoji.length)
// 图标对象
const o = {
text: emoji[index], // 当前显示的文字图标
x: e.offsetX, // 初始位置 x
y: e.offsetY, // 初始位置 y
size: 40, // 图标大小
alpha: 1, // 透明度
random: Math.random() > 0.5 // 决定右下角的图标往左还是往右
}
// 添加到点击位置表
showList.push(o)、
// 添加到右下角图标表
allList.push({
...o,
x: canvas.width - 50, // 当前 canvas 右下角位置 x
y: canvas.height - 50 // 当前 canvas 右下角位置 y
})
})
上面我们已经实现了基本的效果列表的逻辑,接下来就是怎么开始让 canvas “画”出来。
- 因为我们使用了 emoji 作为图标的显示,所以我们需要一个渲染文字的功能
- 让 canvas 开始逐帧渲染出来
// 文字渲染功能
function drawText(text, x, y, fontSize, color = '#000') {
ctx.beginPath()
ctx.font = `${fontSize}px 楷体`
ctx.fillStyle = color
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(text, x, y)
ctx.closePath()
}
// 定义一个全局速度
const speed = 5
// 渲染
function tick() {
// 每次渲染出来前清楚上一帧
ctx.clearRect(0, 0, canvas.width, canvas.height)
show()
showAll()
requestAnimationFrame(tick)
}
// 显示屏幕点赞效果
function show() {
// 过滤掉已经完全透明的
showList = showList.filter(v => v.alpha > 0)
// 循环遍历列表,并渲染出来
showList.forEach((o, i) => {
drawText(o.text, o.x, o.y, o.size, `rgba(0, 0, 0, ${o.alpha})`)
o.y -= 1
o.size += 2
o.alpha -= speed / 200
})
}
// 显示右小角
function showAll() {
// 过滤掉已经完全透明的
allList = showList.filter(v => v.alpha > 0)
// 循环遍历列表,并渲染出来
allList.forEach((o, i) => {
drawText(o.text, o.x, o.y, o.size, `rgba(0, 0, 0, ${o.alpha})`)
o.x = o.random ? o.x + 0.5 : o.x - 0.5
o.y -= speed
o.alpha -= speed / 300
})
}
// 开始渲染
tick()
至此,我们的所有代码都已经完成了。
整体代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>抖音直播点赞效果实现</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
canvas {
background-color: #161823;
}
</style>
</head>
<body>
<canvas id="like"></canvas>
<script>
const canvas = document.getElementById('like')
canvas.width = window.innerWidth
canvas.height = window.innerHeight
const ctx = canvas.getContext('2d')
const speed = 5
// emoji
const emoji = ['🍇', '🍈', '🍉', '🍊', '🍋', '🍌', '🍍', '🥭', '🍎', '🍒', '🧄', '🍘', '🍙', '🍚', '🍛', '🥡', '🦀', '🦞', '🦐', '🦑', '🦪']
// 当前显示列表
let showList = []
// 右小角所有点赞效果
let allList = []
// 文字
function drawText(text, x, y, fontSize, color = '#000') {
ctx.beginPath()
ctx.font = `${fontSize}px 楷体`
ctx.fillStyle = color
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(text, x, y)
ctx.closePath()
}
// 生成随机数
function random(min, max) {
return Math.floor(Math.random() * (max - min)) + min
}
// 监听点击事件
canvas.addEventListener('click', e => {
const index = random(0, emoji.length)
const o = {
text: emoji[index],
x: e.offsetX,
y: e.offsetY,
size: 40,
alpha: 1,
random: Math.random() > 0.5
}
showList.push(o)
allList.push({
...o,
x: canvas.width - 50,
y: canvas.height - 50
})
})
// 开始渲染
function tick() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
show()
showAll()
requestAnimationFrame(tick)
}
// 显示屏幕点赞效果
function show() {
// 过滤掉已经完全透明的
showList = showList.filter(v => v.alpha > 0)
showList.forEach((o, i) => {
drawText(o.text, o.x, o.y, o.size, `rgba(0, 0, 0, ${o.alpha})`)
o.y -= 1
o.size += 2
o.alpha -= speed / 200
})
}
// 显示右小角
function showAll() {
// 过滤掉已经完全透明的
allList = allList.filter(v => v.alpha > 0)
allList.forEach((o, i) => {
drawText(o.text, o.x, o.y, o.size, `rgba(0, 0, 0, ${o.alpha})`)
o.x = o.random ? o.x + 0.5 : o.x - 0.5
o.y -= speed
o.alpha -= speed / 300
})
}
// 开始渲染
tick()
</script>
</body>
</html>
写在最后
在正式开发中可以考虑封装成一个通用的对象或者函数,这里就交给各位去实现了。