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

JavaScript+Canvas實(shí)現(xiàn)文字粒子流特效

 更新時(shí)間:2023年01月14日 11:09:19   作者:鄭丫頭  
看到大師級(jí)的canvas文字粒子動(dòng)畫(huà),要10個(gè)jq幣才能下載啊,我內(nèi)心的小鹿蠢蠢欲動(dòng),我也要寫(xiě)一個(gè)。所以本文就來(lái)用Canvas實(shí)現(xiàn)簡(jiǎn)單的文字粒子流特效,希望對(duì)大家有所幫助

動(dòng)手前思考

首先要在特定的位置生成粒子,要獲取到canvas上像素的點(diǎn)位,通過(guò)canvas的getImageData函數(shù)我們可以得到canvas像素點(diǎn)的信息,獲取像素點(diǎn)中透明度大于0的位置。

繪制文字

新建一個(gè)canvas畫(huà)布,在畫(huà)布上繪制任意的文字

ctx.font = "200px Arial";
ctx.fontWeight = "900";
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.fillStyle = "red";
ctx.fillText("稀土掘金", 0, 0);

獲取像素點(diǎn)位

canvas的getImageData函數(shù)返回了imageData對(duì)象,該對(duì)象復(fù)制畫(huà)布上指定矩形的像素?cái)?shù)據(jù),數(shù)組中四個(gè)值一組存放像素的RGBA信息,其中A代表透明度,當(dāng)透明度大于0時(shí)表示該位置上可以生成粒子。

對(duì)于 ImageData 對(duì)象中的每個(gè)像素,都存在著四方面的信息,即 RGBA 值:

  • R - 紅色 (0-255)
  • G - 綠色 (0-255)
  • B - 藍(lán)色 (0-255)
  • A - alpha 通道 (0-255; 0 是透明的,255 是完全可見(jiàn)的)

也就是

red=imgData.data[0];
green=imgData.data[1];
blue=imgData.data[2];
alpha=imgData.data[3];

調(diào)用getImageData函數(shù),有4個(gè)參數(shù),x/y表示開(kāi)始復(fù)制的左上角位置的xy坐標(biāo),width/height表示將要復(fù)制的矩形區(qū)域的寬度和高度。

var imgData=context.getImageData(x,y,width,height);

imageData對(duì)象信息如下:

如果繪制文字較小最好截取所在矩形位置,以減少獲取的data數(shù)組。獲取canvas繪制文字的寬度方法如下:

const { width } = ctx.measureText(this.text);

遍歷數(shù)組,得到可用像素點(diǎn):

  • 4組一個(gè)像素點(diǎn),gap表示點(diǎn)的間隔,我們只獲取A的位置。
  • wl表示canvas寬度,矩形像素格子排滿(mǎn)寬度后會(huì)換行,由此可以計(jì)算出點(diǎn)位的xy坐標(biāo)。因?yàn)樗膫€(gè)組合一個(gè)點(diǎn),所以wl要乘以4。
const gap = 4;
for (let i = 0, wl = this.canvas.width * gap; i < length; i += gap) {
    if (data[i + gap - 1]) {
      // 根據(jù)透明度判斷
      const x = (i % wl) / gap;
      const y = parseInt(i / wl);
      this.textPoints.push([x, y]);
    }
}

渲染粒子

獲取到像素點(diǎn)位textPoints數(shù)據(jù)之后就可以開(kāi)始渲染粒子了

  • 新建一個(gè)畫(huà)布,隱藏獲取像素點(diǎn)的畫(huà)布,在這個(gè)新的畫(huà)布上繪制粒子
  • 設(shè)置粒子的半徑為5,間隔10個(gè)點(diǎn)位生成一個(gè)粒子
  • 粒子的顏色隨機(jī)生成,最終得到如下圖:

let startX;
let startY;
const points = [];
for (let i = 0; i < this.textPoints.length; i++) {
    let point = this.textPoints[i];
    let x = point[0];
    let y = point[1];
    const radius = 5;
    // const radius = Math.random() * 10; // 隨機(jī)生成粒子寬度
    const color = parseInt(Math.random() * 0xffffff).toString(16); // 隨機(jī)生成粒子顏色
    const { x: x0, y: y0 } = this.adjustPoint(x, y); // 矯正粒子相對(duì)于畫(huà)布所在位置
    if (i == 0 || ((x - startX) % 10 == 0 && (y - startY) % 10 == 0)) {
      startX = x;
      startY = y;
      const params = { x: x0 + radius, y: y0, radius, color };
      points.push(params);
    }
}

優(yōu)化展示效果

  • 為了讓生成的文字更好看,我們可以隨機(jī)設(shè)置粒子的半徑
  • 為了避免后面生成的粒子總是擋住前面粒子,我們可以隨機(jī)生成粒子的順序
  • adjustPoint函數(shù)讓粒子在新畫(huà)布中居中展示
this.points = points.sort((a, b) => (Math.random() > 0.5 ? -1 : 1)); // 隨機(jī)排序
adjustPoint(x, y) {
  const { width, height } = this.canvasLizi;
  return {
    x: x + (width - this.textWidth) / 2,
    y: y + (height - this.textSize) / 2,
  };
}

簡(jiǎn)單的動(dòng)畫(huà)效果

  • 粒子從上下左右四個(gè)方向隨機(jī)生成,匯聚到畫(huà)布中心點(diǎn)
  • 點(diǎn)擊文字時(shí),粒子出現(xiàn)炸開(kāi)的特效

1、隨機(jī)選擇四個(gè)方向中的某一個(gè)方向,生成初始坐標(biāo)

  • 從左邊進(jìn)入畫(huà)布初始坐標(biāo)為(0,y),從右邊進(jìn)入畫(huà)布初始坐標(biāo)為(canvasWidth, y),y是隨機(jī)數(shù)
  • 從上邊進(jìn)入畫(huà)布初始坐標(biāo)為(x,0),從下邊進(jìn)入畫(huà)布初始坐標(biāo)為(x,canvasHeight),x是隨機(jī)數(shù)
for (let item of this.points) {
    let direction;
    const num = Math.random() * 1;
    if (num < 0.25) {
      direction = "left";
      item.initX = 0;
      item.initY = Math.random() * height;
    } else if (num < 0.5) {
      direction = "right";
      item.initX = width;
      item.initY = Math.random() * height;
    } else if (num < 0.75) {
      direction = "top";
      item.initX = Math.random() * width;
      item.initY = 0;
    } else {
      direction = "bottom";
      item.initX = Math.random() * width;
      item.initY = height;
    }
}

2、從初始位置運(yùn)動(dòng)到實(shí)際位置

  • 計(jì)算實(shí)際點(diǎn)與運(yùn)動(dòng)點(diǎn)之間的坐標(biāo)差offsetX,offsetY
  • 判斷差值是正數(shù)還是負(fù)數(shù),當(dāng)x差值為正數(shù),則每次運(yùn)動(dòng)的速率為正,否則為負(fù);當(dāng)y差值為正數(shù),則每次運(yùn)動(dòng)的速率為正,否則為負(fù)。
  • 計(jì)算每次運(yùn)動(dòng)的增量或減量。因?yàn)槌跏甲鴺?biāo)和實(shí)際坐標(biāo)連線(xiàn)可能是一條斜線(xiàn),當(dāng)x增加或減少一定數(shù)值,y值增量或減量等于x增量或減量乘以斜率。
animatDot() {
  if (!this.points.find((item) => item.hasOwnProperty("initX"))) {
    // 當(dāng)不存在運(yùn)動(dòng)點(diǎn)時(shí)取消動(dòng)畫(huà)
    cancelAnimationFrame(this.animatDot.bind(this));
    return;
  }
  this.points.forEach((item) => {
    const offsetX = item.x - item.initX;
    const offsetY = item.y - item.initY;
    if (Math.abs(offsetX) > 0 || Math.abs(offsetY) > 0) {
      const rate = offsetX / 10; // 速率等于坐標(biāo)差除以10,不斷縮小運(yùn)動(dòng)距離
      const x = item.initX + rate;
      if (Math.abs(rate) < 1) {
        item.initX = item.x; // 當(dāng)運(yùn)動(dòng)距離小于1時(shí),等于實(shí)際坐標(biāo)
      } else {
        if (offsetX > 0) {
          item.initX = x < item.x ? x : item.x;
        } else {
          item.initX = x > item.x ? x : item.x;
        }
      }
      const k = offsetY / offsetX; // 計(jì)算斜率
      const y = k * item.initX;
      if (offsetY > 0) {
        item.initY = y < item.y ? y : item.y;
      } else {
        item.initY = y > item.y ? y : item.y;
      }
    } else {
      delete item.initX; // 當(dāng)運(yùn)動(dòng)點(diǎn)坐標(biāo)和實(shí)際坐標(biāo)相同時(shí),刪除初始坐標(biāo)
      delete item.initY;
    }
  });
  this.drawPoint(); // 繪制粒子函數(shù)
  requestAnimationFrame(this.animatDot.bind(this), 1000 / 60);
}

3、點(diǎn)擊文字炸開(kāi)的特效

  • 監(jiān)聽(tīng)鼠標(biāo)點(diǎn)擊事件,獲取鼠標(biāo)點(diǎn)擊坐標(biāo)clickPointX,clickPointY
  • 設(shè)置一個(gè)鼠標(biāo)點(diǎn)擊緩沖區(qū)clickRange,使一定范圍內(nèi)的粒子都產(chǎn)生炸開(kāi)的效果;設(shè)置一個(gè)炸開(kāi)的最遠(yuǎn)距離spreadRange,當(dāng)大于該距離就停止運(yùn)動(dòng)
  • y軸的移動(dòng)還是跟斜率有關(guān)系,要計(jì)算運(yùn)動(dòng)點(diǎn)跟鼠標(biāo)點(diǎn)擊位置的斜率
this.clickRange = 30; // 點(diǎn)擊范圍
this.spreadRange = 30; // 擴(kuò)散范圍
PBomb() {
  const that = this;
  function animaion(time) {
    that.PBomb();
  }
  if (!this.points.find((item) => item.bomb)) {
    cancelAnimationFrame(animaion); // 停止動(dòng)畫(huà)判斷
    return;
  }
  const step = 10; // x軸步長(zhǎng),每幀增加或減少的大小
  this.points.forEach((point) => {
    if (point.bomb) {
      if (point.bombX > this.clickPointX) {
        if (point.bombX < point.x + this.spreadRange) {
          point.bombX += step;
        } else {
          point.bomb = false;
        }
      }
      if (point.bombX < this.clickPointX) {
        if (point.bombX > point.x - this.spreadRange) {
          point.bombX -= step;
        } else {
          point.bomb = false;
        }
      }
      const k =
        point.x - this.clickPointX == 0
          ? 1
          : Math.abs(
              (point.y - this.clickPointY) /
                (point.x - this.clickPointX),
            ).toFixed(2); // 計(jì)算斜率
      if (point.bombY > this.clickPointY) {
        if (point.bombY < point.y + this.spreadRange) {
          point.bombY += k * step;
        } else {
          point.bomb = false;
        }
      }
      if (point.bombY < this.clickPointY) {
        if (point.bombY > point.y - this.spreadRange) {
          point.bombY -= k * step;
        } else {
          point.bomb = false;
        }
      }
    }
  });
  this.drawPoint();
  setTimeout(() => {
    requestAnimationFrame(animaion);
  }, 1000 / 10);
}

最終效果:

最后加上修改文字內(nèi)容和大小的功能就完美了

到此這篇關(guān)于JavaScript+Canvas實(shí)現(xiàn)文字粒子流特效的文章就介紹到這了,更多相關(guān)JavaScript Canvas文字粒子流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論