Canvas實現(xiàn)動態(tài)粒子文字效果的代碼示例
正文
還是先看看效果,其中有幾個要解決的難點,后面有完整代碼。
怎么確定文字位置的粒子坐標怎么讓粒子的位置和文字位置的粒子坐標對應怎么讓粒子動起來

事先準備
我們要準備幾個方便自己寫代碼的函數(shù),隨機數(shù)、隨機顏色、和繪制粒子

初始化粒子
首先得有粒子,我們使用隨機數(shù)和隨機顏色生成粒子,并且記錄坐標信息。

怎么確定文字位置的粒子坐標
原理:用一種特殊顏色(如紅色)在畫布的一塊區(qū)域填充文字,然后使用getImageData方法獲取這一塊區(qū)域每一個單位像素顏色,如果這個單位像素是標記的特殊顏色就記錄其坐標。
這里是取5個像素為一個單位,是為了有顆粒感。

怎么讓粒子的位置和文字位置的粒子坐標對應
我們現(xiàn)在有所有粒子的位置信息也有文字位置的粒子坐標,那我們怎么對應起來呢。我們可以使用隨機數(shù)加上map使二者對應。

有了生成的map映射關系,我們就可以確定每一個粒子要到達的位置。遍歷所有粒子,存在map映射關系的話我們就使用映射到的坐標,不存在映射關系我們使用隨機數(shù)生成。同時,確定水平和豎直方向速度。

讓粒子動起來
起始坐標和目標坐標有了,速度也有了,那不就剩下使用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);
/** 粒子總數(shù)*/
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());
/** 生成隨機數(shù)*/
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])
/** 創(chuàng)建映射關系*/
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>
</>
)
}結(jié)語
感興趣的可以去試試
以上就是Canvas實現(xiàn)動態(tài)粒子文字效果的代碼示例的詳細內(nèi)容,更多關于Canvas動態(tài)粒子文字的資料請關注腳本之家其它相關文章!
相關文章
uni-app應用配置manifest.json最全最詳細配置
這篇文章主要給大家介紹了關于uni-app應用配置manifest.json最全最詳細配置,manifest.json文件是UniApp開發(fā)中用來配置應用信息的重要文件,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-01-01
javascript 觸發(fā)HTML元素綁定的函數(shù)
只能觸發(fā)函數(shù)的執(zhí)行,并不能完全模擬出實際的點擊。2010-09-09
js將json格式的對象拼接成復雜的url參數(shù)方法
下面小編就為大家?guī)硪黄猨s將json格式的對象拼接成復雜的url參數(shù)方法。小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-05-05

