最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

正文

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

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

動(dò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)對(duì)應(yīng)

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

code.png

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

code.png

讓粒子動(dòng)起來

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

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
  }, [])
  /** 繪制移動(dòng)動(dòng)畫*/
  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)位置,停止動(dòng)畫
    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])
  /** 動(dòng)畫*/
  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)動(dòng)態(tài)粒子文字效果的代碼示例的詳細(xì)內(nèi)容,更多關(guān)于Canvas動(dòng)態(tài)粒子文字的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Javascript操作表單實(shí)例講解(下)

    Javascript操作表單實(shí)例講解(下)

    這篇文章主要介紹了Javascript操作表單實(shí)例講解(下)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-06-06
  • Javascript中的this綁定介紹

    Javascript中的this綁定介紹

    在Javascript里,函數(shù)被調(diào)用的時(shí)候,除了接受聲明是定義的形式參數(shù),每一個(gè)函數(shù)還接受兩個(gè)附加的參數(shù):this和arguments。
    2011-09-09
  • 微信小程序之多列表的顯示和隱藏功能【附源碼】

    微信小程序之多列表的顯示和隱藏功能【附源碼】

    今天在項(xiàng)目碰到一個(gè)問題,之前在項(xiàng)目首頁實(shí)現(xiàn)單列表的顯示和隱藏,通過wx:if判斷就可實(shí)現(xiàn),現(xiàn)在要實(shí)現(xiàn)多列表的單項(xiàng)顯示和隱藏功能應(yīng)該如何實(shí)現(xiàn)呢?下面小編給大家?guī)砹宋⑿判〕绦蛑嗔斜淼娘@示和隱藏功能,感興趣的朋友一起看看吧
    2018-08-08
  • javascript中call,apply,bind的區(qū)別詳解

    javascript中call,apply,bind的區(qū)別詳解

    這篇文章主要介紹了javascript中call,apply,bind的區(qū)別詳解,幫助大家更好的理解和使用JavaScript,感興趣的朋友可以了解下
    2020-12-12
  • Layui表格行內(nèi)動(dòng)態(tài)編輯數(shù)據(jù)

    Layui表格行內(nèi)動(dòng)態(tài)編輯數(shù)據(jù)

    本文主要介紹經(jīng)典前端框架 layui 中的動(dòng)態(tài)表格數(shù)據(jù)操作,結(jié)合 JQuery 動(dòng)態(tài)編輯單元格中的數(shù)據(jù),具有一定的參考價(jià)值,感興趣的可以了解一下
    2021-08-08
  • 微信小程序自定義菜單切換欄tabbar組件代碼實(shí)例

    微信小程序自定義菜單切換欄tabbar組件代碼實(shí)例

    這篇文章主要介紹了微信小程序自定義菜單切換欄tabbar組件代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • 簡單談?wù)刯avascript代碼復(fù)用模式

    簡單談?wù)刯avascript代碼復(fù)用模式

    這篇文章主要簡單談?wù)刯avascript代碼復(fù)用模式,主要詳細(xì)介紹了類式繼承模式中的默認(rèn)模式,希望大家能夠喜歡。
    2015-01-01
  • 維護(hù)loading加載狀態(tài)的幾個(gè)方法小結(jié)

    維護(hù)loading加載狀態(tài)的幾個(gè)方法小結(jié)

    在項(xiàng)目開發(fā)中,當(dāng)頁面請(qǐng)求接口時(shí),組件局部或者頁面全局顯示loading加載遮罩可謂是司空見慣了,下面來討論一下如何優(yōu)雅的使用loading狀態(tài),文中通過代碼示例介紹的非常詳細(xì),需要的朋友可以參考下
    2024-09-09
  • JavaScript圖像放大鏡效果實(shí)現(xiàn)方法詳解

    JavaScript圖像放大鏡效果實(shí)現(xiàn)方法詳解

    這篇文章主要介紹了JavaScript圖像放大鏡效果實(shí)現(xiàn)方法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • 靜態(tài)的動(dòng)態(tài)續(xù)篇之來點(diǎn)XML

    靜態(tài)的動(dòng)態(tài)續(xù)篇之來點(diǎn)XML

    靜態(tài)的動(dòng)態(tài)續(xù)篇之來點(diǎn)XML...
    2006-12-12

最新評(píng)論

太仓市| 湾仔区| 金乡县| 五原县| 观塘区| 彭山县| 中方县| 霸州市| 余干县| 长丰县| 眉山市| 大宁县| 信丰县| 塔城市| 宜城市| 临潭县| 铜山县| 乳山市| 广河县| 安康市| 南木林县| 冕宁县| 大埔区| 江华| 东乡县| 衡山县| 航空| 鄂托克旗| 阿巴嘎旗| 任丘市| 富源县| 通海县| 南雄市| 张家界市| 滨州市| 兴化市| 翼城县| 平乡县| 陆丰市| 米泉市| 准格尔旗|