浮动粒子制作404动画页面
前言:之前在网上看到了这个效果,之后我在做项目的时候,正好将他放进了项目里面,这篇博客就介绍一下该效果的原理;
1.首先是基础的设置
const canvas = <HTMLCanvasElement>document.getElementById('canvas'); let ctx: any = canvas.getContext('2d'); const W: number = canvas.width = window.innerWidth; const H: number = canvas.height = window.innerHeight; const mFound: number = H * 2.5; //found 的粒子数量 const mStr: number = H * 1.5;// 404的例子数量 ctx.textAlign = "center";//居中
canvas 的大小是占据整个屏幕;
2.绘出字
const str: string = '404'; ctx.textBaseline = 'bottom'; ctx.font = `${H / 2}px 'Arial', sans-serif`; ctx.fillText(str, W / 2, (H + H / 4) / 2, W); const found: string = 'Not Found'; ctx.textBaseline = 'top'; ctx.font = `${H / 6}px 'Arial', sans-serif`; ctx.fillText(found, W / 2, (H + H / 4) / 2, W);
通过 fillText 的最后一个值,可以让字的大小不超过该值,我选择的是,浏览器的宽度,经我手机上检验,字的大小正好到达手机的宽度;
再说一下我这边要写2次,是因为字的尺寸是不一样的;
3.获取粒子
let imageDataStr = ctx.getImageData(0, 0, W, (H + H / 4) / 2); let imageDataFound = ctx.getImageData(0, (H + H / 4) / 2, W, (H - H / 4) / 2); interface dotsType { x: number y: number r: number a: number lx: number rx: number v: number } function getDots(imageData, isStr: boolean): dotsType[] { let dots = []; let index: number = 0; for (let i = 0; i < W; i += 2) { for (let j = 0; j < H; j += 2) { let k = 4 * (i + j * W); if (imageData.data[k + 3] > 0) { dots[index++] = { x: i, y: isStr ? j : j + (H + H / 4) / 2, r: Math.random() * (isStr ? 8 : 3), a: Math.random(), lx: isStr ? i - 4 : i - 2, rx: isStr ? i + 4 : i + 2, v: (Math.random() - .5) * (isStr ? .8 : .4) } } } } let newDots = []; const m = isStr ? mStr : mFound; if (m && (dots.length > m)) { for (let i = 0; i < m; i++) { newDots.push(dots[Math.floor(Math.random() * dots.length)]); } } else { newDots = dots; } return newDots; }
使用 canvas 的 getImageData函数,将 canvas 读取为 imageData 格式,再根据图片的像素的值来获取写有字的例子的位置,将其添加入一个粒子数组,顺便加入粒子的半径,透明度,运动半径,和运动速度,而他接受的isStr参数,是为了区分是 str 还是 found 而区别的;
为什么我要这样用一个参数来区分呢? 因为原来我看到的效果他是拿2个 canvas 叠在一起的,十分麻烦,而我根据定位和对应高度,加上 getImageData 的位置参数可以解决这个问题;
这新建了 newDots 数组是为了根据开头所设置的粒子数来打乱原数组的顺序;
4.渲染
let dataStr: dotsType[] = getDots(imageDataStr, true); let DataFound: dotsType[] = getDots(imageDataFound, false); const data: dotsType[] = [...dataStr, ...DataFound]; function render(): void { ctx.fillStyle = "#4db9ea"; ctx.fillRect(0, 0, W, H); for (let i = 0; i < data.length; i++) { let temp = data[i]; ctx.beginPath(); ctx.fillStyle = `rgba(255,255,255,${temp.a})`; temp.x = temp.x + temp.v; temp.y = temp.y + temp.v; if (temp.x < temp.lx || temp.x > temp.rx) { temp.v = -temp.v; } ctx.arc(temp.x, temp.y, temp.r, 0, 2 * Math.PI); ctx.fill() } } render();
渲染出对于半径和透明度的圆;
因为分成了2个 imageData, 所以需要后面将2个粒子数组合并;并且根据每个数组的速度,来进行一个来回的直线运动;
5.计时
const requestAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame; let startTime = 0; function animation(time: number = 0): void { if (time - startTime > 25) { render(); startTime = time; } requestAnimFrame(animation) }; animation();
本来的话直接使用requestAnimFrame
就行了,但是我调低了帧率,影响不大;
最后:
demo:点击查看
github:https://github.com/Grewer/JsDemo/tree/master/floatParticles
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步