Skip to content

视频字符化特效

说明

使用 HTML5 Canvas 将视频每一帧的像素转换为字符,实现独特的视觉效果。

效果演示

实现原理

  1. 视频源:使用 <video> 标签加载并播放视频(隐藏原始视频)。
  2. 画布绘制:使用 <canvas> 获取视频的每一帧图像。
  3. 像素分析
    • 获取画布上的像素数据 (getImageData)。
    • 遍历像素点,计算灰度值或平均颜色。
  4. 字符渲染
    • 清空画布。
    • 根据像素位置和颜色,使用 fillText 绘制字符(如 '8')。
    • 使用 requestAnimationFrame 循环执行,实现动画效果。
查看代码
vue
<template>
  <div class="container" ref="boxRef">
    <video
      :muted="false"
      autoplay
      preload="true"
      loop
      x5-video-player-fullscreen="true"
      x5-playsinline="true"
      playsinline
      webkit-playsinline="true"
      crossorigin="anonymous"
      ref="videoRef"
    >
      <source :src="videoSrc" />
    </video>
    <canvas ref="canvasRef"></canvas>
  </div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const props = defineProps({
  videoSrc: {
    type: String,
    default: '',
  },
})

const canvasWidth = 324
const canvasHeight = 570
let ctxRef = null
let frameId = 0

const boxRef = ref()
const videoRef = ref()
const canvasRef = ref()

const init = () => {
  if (boxRef.value && videoRef.value && canvasRef.value) {
    canvasRef.value.width = canvasWidth
    canvasRef.value.height = canvasHeight
    ctxRef = canvasRef.value.getContext('2d', {
      willReadFrequently: true,
    })
    videoRef.value.crossOrigin = 'anonymous'
  }
}

const play = () => {
  if (videoRef.value && ctxRef) {
    ctxRef.drawImage(videoRef.value, 0, 0, canvasWidth, canvasHeight)
    const imageData = ctxRef.getImageData(0, 0, canvasWidth, canvasHeight)
    ctxRef.clearRect(0, 0, canvasWidth, canvasHeight)
    const { data, width, height } = imageData
    for (let y = 0; y < height; y++) {
      for (let x = 0; x < width; x++) {
        const startIndex = (y * width + x) * 4
        if (x % 6 === 0 && y % 6 === 0) {
          const avgColor = (data[startIndex] + data[startIndex + 1] + data[startIndex + 2]) / 3
          ctxRef.fillStyle = `rgb(${avgColor}, ${avgColor}, ${avgColor})`
          ctxRef.font = '10px Arial'
          ctxRef.fillText('8', x, y)
        }
      }
    }
    frameId = window.requestAnimationFrame(play)
  }
}

onMounted(() => {
  init()
  play()
})

onUnmounted(() => {
  frameId && cancelAnimationFrame(frameId)
})
</script>

<style scoped lang="scss">
.container {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;

  video {
    position: absolute;
    width: 324px;
    height: 570px;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
    visibility: hidden;
  }

  .tip {
    margin-top: 20px;
    color: #666;
  }
}
</style>

如有转载或 CV 的请标注本站原文地址