JavaScript+Canvas實(shí)現(xiàn)文字粒子流特效
動(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)文章
JavaScript 直接操作本地文件的實(shí)現(xiàn)代碼
Chrome、IE和Firefox都紛紛在新版中增強(qiáng)了JavaScript引擎的執(zhí)行效率,隨著JavaScript效率在各大瀏覽器的顯著提高,JavaScript可以做越來(lái)越多的事,本地文件API的引入將讓很多有趣的功能成為現(xiàn)實(shí)。2009-12-12利用JS來(lái)控制鍵盤(pán)的上下左右鍵(示例代碼)
這篇文章主要介紹了利用JS來(lái)控制鍵盤(pán)的上下左右鍵示例代碼。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-12-12layUI實(shí)現(xiàn)前端分頁(yè)和后端分頁(yè)
這篇文章主要為大家詳細(xì)介紹了layUI實(shí)現(xiàn)前端分頁(yè)和后端分頁(yè),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07javascript新建標(biāo)簽,判斷鍵盤(pán)輸入,以及判斷焦點(diǎn)(示例代碼)
這篇文章主要介紹了javascript新建標(biāo)簽,判斷鍵盤(pán)輸入,以及判斷焦點(diǎn)(示例代碼)。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-11-11JavaScript異步調(diào)用定時(shí)方法并停止該方法實(shí)現(xiàn)代碼
JavaScript異步調(diào)用定時(shí)方法并停止該方法實(shí)現(xiàn)代碼 ,需要的朋友可以參考下2012-03-03JavaScript實(shí)現(xiàn)的可變動(dòng)態(tài)數(shù)字鍵盤(pán)控件方式實(shí)例代碼
本篇文章主要介紹了JavaScript實(shí)現(xiàn)的可變動(dòng)態(tài)數(shù)字鍵盤(pán)控件方式實(shí)例代碼,具有一定的參考價(jià)值,有興趣的可以了了解一下2017-07-07微信小程序使用ucharts在小程序中加入橫屏展示功能的全過(guò)程
這篇文章主要給大家介紹了關(guān)于微信小程序使用ucharts在小程序中加入橫屏展示功能的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用微信小程序具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-09-09JavaScript 嚴(yán)格模式(use strict)用法實(shí)例分析
這篇文章主要介紹了JavaScript 嚴(yán)格模式(use strict)用法,結(jié)合實(shí)例形式分析了JavaScript 嚴(yán)格模式的基本功能、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-03-03一文帶你掌握J(rèn)avaScript中的箭頭函數(shù)
在JavaScript中,箭頭函數(shù)是一種簡(jiǎn)化的函數(shù)語(yǔ)法,它在ES6(ECMAScript?2015)引入,本文就來(lái)和大家深入講講JavaScript中的箭頭函數(shù)的使用吧2023-05-05