欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Canvas實(shí)現(xiàn)動態(tài)粒子文字效果的代碼示例

 更新時(shí)間:2023年08月03日 08:44:22   作者:卸任  
這篇文章主要介紹了如何用Canvas實(shí)現(xiàn)動態(tài)粒子文字效果,文中有完整的代碼示例,文章通過代碼介紹的非常清楚,感興趣的小伙伴跟著小編一起來看看吧

正文

還是先看看效果,其中有幾個(gè)要解決的難點(diǎn),后面有完整代碼。

  • 怎么確定文字位置的粒子坐標(biāo)
  • 怎么讓粒子的位置和文字位置的粒子坐標(biāo)對應(yīng)
  • 怎么讓粒子動起來

動畫23.gif

事先準(zhǔn)備

我們要準(zhǔn)備幾個(gè)方便自己寫代碼的函數(shù),隨機(jī)數(shù)、隨機(jī)顏色、和繪制粒子

code.png

初始化粒子

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

code.png

怎么確定文字位置的粒子坐標(biāo)

原理:用一種特殊顏色(如紅色)在畫布的一塊區(qū)域填充文字,然后使用getImageData方法獲取這一塊區(qū)域每一個(gè)單位像素顏色,如果這個(gè)單位像素是標(biāo)記的特殊顏色就記錄其坐標(biāo)。

這里是取5個(gè)像素為一個(gè)單位,是為了有顆粒感。

code.png

怎么讓粒子的位置和文字位置的粒子坐標(biāo)對應(yīng)

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

code.png

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

code.png

讓粒子動起來

起始坐標(biāo)和目標(biāo)坐標(biāo)有了,速度也有了,那不就剩下使用requestAnimationFrame繪制了嗎,再加上邊界的判斷,最后在所有粒子都到達(dá)指定坐標(biāo)停止動畫就行了。

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() {
  /** 隨機(jī)文字*/
  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());
  /** 生成隨機(jī)數(shù)*/
  const createRandomNum = useCallback((min: number, max: number) => {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }, [])
  /** 生成隨機(jī)顏色*/
  function getRandomColor() {
    var letters = '0123456789ABCDEF';
    var color = '#';
    for (var i = 0; i < 6; i++) {
      color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
  }
  /** 繪制點(diǎn)*/
  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();
  }, [])
  /** 是否全部到達(dá) */
  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)
    //全部粒子到達(dá)目標(biāo)位置,停止動畫
    if (isAllArrive()) {
      cancelAnimationFrame(moveAnimation.current)
    }
  }, [width, height, isAllArrive])
  /** 設(shè)置文字坐標(biāo)信息*/
  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;
    //組成記錄文字點(diǎn)的信息
    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])
  /** 設(shè)置點(diǎn)到達(dá)坐標(biāo)*/
  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)建映射關(guān)系*/
  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實(shí)現(xiàn)動態(tài)粒子文字效果的代碼示例的詳細(xì)內(nèi)容,更多關(guān)于Canvas動態(tài)粒子文字的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 一個(gè)JavaScript防止表單重復(fù)提交的實(shí)例

    一個(gè)JavaScript防止表單重復(fù)提交的實(shí)例

    防止重復(fù)表單提交的方法有很多,本文使用JavaScript來實(shí)現(xiàn)防止表單重復(fù)提交,很簡單,但很實(shí)用,新手朋友們不要錯過
    2014-10-10
  • 封裝好的一個(gè)萬能檢測表單的方法

    封裝好的一個(gè)萬能檢測表單的方法

    這篇文章主要介紹了一個(gè)封裝好的萬能檢測表單的方法,非常的好用,使用也很方便,這里推薦給小伙伴們。
    2015-01-01
  • 值得分享的JavaScript實(shí)現(xiàn)圖片輪播組件

    值得分享的JavaScript實(shí)現(xiàn)圖片輪播組件

    這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)圖片輪播組件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-11-11
  • javascript表單驗(yàn)證大全

    javascript表單驗(yàn)證大全

    JavaScript是用來在數(shù)據(jù)被傳輸?shù)椒?wù)前對html表單中輸入的數(shù)據(jù)進(jìn)行驗(yàn)證,使用javascript對用戶輸入的信息進(jìn)行驗(yàn)證是項(xiàng)目必須的,下面小編給大家整理一些比較常用的javascript表單驗(yàn)證,需要的朋友可以參考下
    2015-08-08
  • uni-app應(yīng)用配置manifest.json最全最詳細(xì)配置

    uni-app應(yīng)用配置manifest.json最全最詳細(xì)配置

    這篇文章主要給大家介紹了關(guān)于uni-app應(yīng)用配置manifest.json最全最詳細(xì)配置,manifest.json文件是UniApp開發(fā)中用來配置應(yīng)用信息的重要文件,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • 微信小程序自定義彈出層效果

    微信小程序自定義彈出層效果

    這篇文章主要為大家詳細(xì)介紹了微信小程序自定義彈出層效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • javascript 觸發(fā)HTML元素綁定的函數(shù)

    javascript 觸發(fā)HTML元素綁定的函數(shù)

    只能觸發(fā)函數(shù)的執(zhí)行,并不能完全模擬出實(shí)際的點(diǎn)擊。
    2010-09-09
  • JS合并兩個(gè)數(shù)組的3種方法詳解

    JS合并兩個(gè)數(shù)組的3種方法詳解

    這篇文章主要介紹了JS合并兩個(gè)數(shù)組的3種方法詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10
  • JS實(shí)現(xiàn)圖片預(yù)加載無需等待

    JS實(shí)現(xiàn)圖片預(yù)加載無需等待

    網(wǎng)站開發(fā)時(shí)經(jīng)常需要在某個(gè)頁面需要實(shí)現(xiàn)對大量圖片的瀏覽;用javascript來實(shí)現(xiàn)一個(gè)圖片瀏覽器,讓用戶無需等待過長的時(shí)間就能看到其他圖片
    2012-12-12
  • js將json格式的對象拼接成復(fù)雜的url參數(shù)方法

    js將json格式的對象拼接成復(fù)雜的url參數(shù)方法

    下面小編就為大家?guī)硪黄猨s將json格式的對象拼接成復(fù)雜的url參數(shù)方法。小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-05-05

最新評論