[canvas2D] 使用 canvas 实现抖音直播的点赞效果

944 阅读2分钟

分析

移动版的抖音直播用户可以点击屏幕来给主播点赞,点赞的时候会出点击的位置会出现图标,另外在屏幕的右下角也会出现相应的效果。

效果展示

码上掘金-仿抖音直播点赞效果

代码实现

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>

写在最后

在正式开发中可以考虑封装成一个通用的对象或者函数,这里就交给各位去实现了。