javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > Canvas动态粒子文字

Canvas实现动态粒子文字效果的代码示例

作者:卸任

这篇文章主要介绍了如何用Canvas实现动态粒子文字效果,文中有完整的代码示例,文章通过代码介绍的非常清楚,感兴趣的小伙伴跟着小编一起来看看吧

正文

还是先看看效果,其中有几个要解决的难点,后面有完整代码。

动画23.gif

事先准备

我们要准备几个方便自己写代码的函数,随机数、随机颜色、和绘制粒子

code.png

初始化粒子

首先得有粒子,我们使用随机数和随机颜色生成粒子,并且记录坐标信息。

code.png

怎么确定文字位置的粒子坐标

原理:用一种特殊颜色(如红色)在画布的一块区域填充文字,然后使用getImageData方法获取这一块区域每一个单位像素颜色,如果这个单位像素是标记的特殊颜色就记录其坐标。

这里是取5个像素为一个单位,是为了有颗粒感。

code.png

怎么让粒子的位置和文字位置的粒子坐标对应

我们现在有所有粒子的位置信息也有文字位置的粒子坐标,那我们怎么对应起来呢。我们可以使用随机数加上map使二者对应。

code.png

有了生成的map映射关系,我们就可以确定每一个粒子要到达的位置。遍历所有粒子,存在map映射关系的话我们就使用映射到的坐标,不存在映射关系我们使用随机数生成。同时,确定水平和竖直方向速度。

code.png

让粒子动起来

起始坐标和目标坐标有了,速度也有了,那不就剩下使用requestAnimationFrame绘制了吗,再加上边界的判断,最后在所有粒子都到达指定坐标停止动画就行了。

code.png

完整代码

import { useState, useEffect, useRef, useMemo, useCallback } from 'react'
import './Test.scss'
interface DotItem {
  x: number,
  y: number,
  toX: number,
  toY: number,
  speedX: number,
  speedY: number,
  color: string,
  isArrive: boolean,
}
export default function Index() {
  /** 随机文字*/
  const sentenceList = ['Hello World', 'Canvas', '掘金你好', '前端']
  const frameDom = useRef<any>(null);
  /** 粒子总数*/
  const dotTotal = useRef(1200)
  const canvasDom = useRef<any>(null);
  const canvasCtx = useRef<any>(null);
  const [height, setHeight] = useState(0)
  const [width, setWidth] = useState(0)
  /** 粒子信息列表*/
  const allDot = useRef<DotItem[]>([])
  /** 文字粒子信息*/
  const textCoordinateList = useRef<{
    x: number,
    y: number
  }[]>([])
  const moveAnimation = useRef<any>(null)
  /** 文字粒子和粒子全部信息的映射 */
  let map = useRef(new Map());
  /** 生成随机数*/
  const createRandomNum = useCallback((min: number, max: number) => {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }, [])
  /** 生成随机颜色*/
  function getRandomColor() {
    var letters = '0123456789ABCDEF';
    var color = '#';
    for (var i = 0; i < 6; i++) {
      color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
  }
  /** 绘制点*/
  const pointPlot = useCallback((x: number, y: number, color: string) => {
    canvasCtx.current.beginPath()
    canvasCtx.current.strokeStyle = color;
    canvasCtx.current.arc(x, y, 1, 0, 2 * Math.PI);
    canvasCtx.current.stroke();
  }, [])
  /** 是否全部到达 */
  const isAllArrive = useCallback(() => {
    let isTrue = true
    for (let i = 0; i < allDot.current.length; i++) {
      if (!allDot.current[i].isArrive) {
        isTrue = false
      }
    }
    return isTrue
  }, [])
  /** 绘制移动动画*/
  const drawMove = useCallback(() => {
    canvasCtx.current.clearRect(0, 0, width, height)
    for (let i = 0; i < allDot.current.length; i++) {
      let { x: currentX, y: currentY, toX, toY, speedX, speedY } = allDot.current[i]
      let x = 0;
      let y = 0;
      x = currentX + speedX
      y = currentY + speedY
      //边界判断
      if (speedX < 0 && x < toX ||
        speedX > 0 && x > toX
      ) {
        x = toX
        allDot.current[i] = {
          ...allDot.current[i],
          isArrive: true,
        }
      }
      if (speedY < 0 && y < toY ||
        speedY > 0 && y > toY
      ) {
        y = toY;
        allDot.current[i] = {
          ...allDot.current[i],
          isArrive: true,
        }
      }
      pointPlot(x, y, allDot.current[i].color)
      allDot.current[i] = {
        ...allDot.current[i],
        x, y,
      }
    }
    moveAnimation.current = requestAnimationFrame(drawMove)
    //全部粒子到达目标位置,停止动画
    if (isAllArrive()) {
      cancelAnimationFrame(moveAnimation.current)
    }
  }, [width, height, isAllArrive])
  /** 设置文字坐标信息*/
  const setLiteralCoordinate = useCallback(() => {
    let index = createRandomNum(0, sentenceList.length - 1);
    let text = sentenceList[index]
    canvasCtx.current.font = "120px Arial"
    canvasCtx.current.fillStyle = "red"
    let textWidth = canvasCtx.current.measureText(text).width;
    canvasCtx.current.fillText(text, width / 2 - textWidth / 2, height / 2)
    let startX = width / 2 - textWidth / 2
    let endX = startX + textWidth;
    let startY = height / 2 - 120;
    let endY = height / 2 + 30;
    //组成记录文字点的信息
    textCoordinateList.current = [];
    for (let i = startX; i <= endX; i += 5) {
      for (let j = startY; j <= endY; j += 5) {
        let imageData = canvasCtx.current.getImageData(i, j, 2, 2);
        let data = imageData.data
        if (data[0] == 255 && data[1] == 0 && data[2] == 0) {
          textCoordinateList.current.push({
            x: i,
            y: j,
          })
        }
      }
    }
  }, [width, height])
  /** 设置点到达坐标*/
  const setArrivalCoordinate = useCallback(() => {
    for (let i = 0; i < allDot.current.length; i++) {
      let x = 0;
      let y = 0;
      if (map.current.has(i)) {
        x = textCoordinateList.current[map.current.get(i)].x;
        y = textCoordinateList.current[map.current.get(i)].y;
      } else {
        x = createRandomNum(0, width)
        y = createRandomNum(0, height)
      }
      allDot.current[i] = {
        ...allDot.current[i],
        toX: x,
        toY: y,
        speedX: ((x - allDot.current[i].x) / 2000 * 17),
        speedY: ((y - allDot.current[i].y) / 2000 * 17),
        isArrive: false,
      }
    }
  }, [width, height])
  /** 动画*/
  const onScatter = useCallback(() => {
    setLiteralCoordinate()
    createMap()
    setArrivalCoordinate()
    drawMove()
  }, [height, width, drawMove, setLiteralCoordinate, setArrivalCoordinate])
  /**  创建映射关系*/
  const createMap = useCallback(() => {
    map.current.clear()
    var numbers = [];
    for (var i = 0; i < allDot.current.length; i++) {
      numbers.push(i);
    }
    var randomNumbers = [];
    for (var j = 0; j < textCoordinateList.current.length; j++) {
      var randomIndex = createRandomNum(0, numbers.length - 1)
      randomNumbers.push(numbers[randomIndex]);
      map.current.set(numbers[randomIndex], j);
      numbers.splice(randomIndex, 1);
    }
  }, [])
  /** 视口大小变化*/
  const onReSize = useCallback(() => {
    let { height, width } = frameDom.current.getBoundingClientRect();
    setHeight(height)
    setWidth(width)
  }, [])
  /** 初始化*/
  useEffect(() => {
    if (canvasDom.current === null) {
      return
    }
    canvasCtx.current = canvasDom.current.getContext('2d')
    /** 初始化*/
    let { height, width } = frameDom.current.getBoundingClientRect();
    setHeight(height)
    setWidth(width)
  }, [])
  useEffect(() => {
    requestAnimationFrame(() => {
      for (let i = 0; i < dotTotal.current; i++) {
        let x = createRandomNum(0, width)
        let y = createRandomNum(0, height)
        let color = getRandomColor()
        pointPlot(x, y, color)
        allDot.current[i] = {
          x, y, color,
          toX: 0,
          toY: 0,
          speedX: 0,
          speedY: 0,
          isArrive: false,
        }
      }
      onScatter()
    })
  }, [onScatter, height, width])
  useEffect(() => {
    window.addEventListener('resize', onReSize)
    return () => {
      window.removeEventListener('resize', onReSize)
    }
  }, [onReSize])
  return (
    <>
      <div
        ref={frameDom}
        onClick={onScatter}
        style={{
          position: 'relative',
          height: '100vh',
          width: '100%',
          backgroundColor: "black"
        }}>
        <canvas
          style={{
            position: 'absolute',
            top: 0,
            left: 0,
            zIndex: 2,
          }}
          ref={canvasDom} width={width} height={height}></canvas>
      </div>
    </>
  )
}

结语

感兴趣的可以去试试

以上就是Canvas实现动态粒子文字效果的代码示例的详细内容,更多关于Canvas动态粒子文字的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文