Canvas实现动态粒子文字效果的代码示例
作者:卸任
这篇文章主要介绍了如何用Canvas实现动态粒子文字效果,文中有完整的代码示例,文章通过代码介绍的非常清楚,感兴趣的小伙伴跟着小编一起来看看吧
正文
还是先看看效果,其中有几个要解决的难点,后面有完整代码。
怎么确定文字位置的粒子坐标
怎么让粒子的位置和文字位置的粒子坐标对应
怎么让粒子动起来
事先准备
我们要准备几个方便自己写代码的函数,随机数、随机颜色、和绘制粒子
初始化粒子
首先得有粒子,我们使用随机数和随机颜色生成粒子,并且记录坐标信息。
怎么确定文字位置的粒子坐标
原理:用一种特殊颜色(如红色)在画布的一块区域填充文字,然后使用getImageData方法获取这一块区域每一个单位像素颜色,如果这个单位像素是标记的特殊颜色就记录其坐标。
这里是取5个像素为一个单位,是为了有颗粒感。
怎么让粒子的位置和文字位置的粒子坐标对应
我们现在有所有粒子的位置信息
也有文字位置的粒子坐标
,那我们怎么对应起来呢。我们可以使用随机数
加上map
使二者对应。
有了生成的map映射关系
,我们就可以确定每一个粒子要到达的位置。遍历所有粒子,存在map映射关系
的话我们就使用映射到的坐标,不存在映射关系我们使用随机数生成。同时,确定水平和竖直方向速度。
让粒子动起来
起始坐标和目标坐标有了,速度也有了,那不就剩下使用requestAnimationFrame
绘制了吗,再加上边界的判断,最后在所有粒子都到达指定坐标停止动画就行了。
完整代码
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动态粒子文字的资料请关注脚本之家其它相关文章!