一、特效原理 主页轮播区的粒子上升效果基于 Canvas 2D 实现,核心函数为 circleMagic(),位于:
1 themes/hexo-theme-matery/layout/index.ejs
工作流程:
获取容器 #smarts 的宽高,创建一个 <canvas> 覆盖在上面
根据 density(密度)生成大量 Circle 粒子对象
每一帧(requestAnimationFrame):粒子向上移动、alpha 递减、重新绘制
当粒子 alpha 降到 0 时,重置到底部重新上升
二、核心参数一览 所有可调参数集中在 settings 对象中:
1 2 3 4 5 6 const settings = options || { color : 'random' , radius : 10 , density : 0.3 , clearOffset : 0.9 }
参数
作用
默认值
调大效果
调小效果
color
粒子颜色
'rgba(255,255,255,.5)'
—
—
radius
粒子半径上限
10
粒子更大
粒子更小
density
粒子密度
0.3
粒子更多更密
粒子更少更稀
clearOffset
初始 alpha 浮动范围
0.2
粒子存活更久,升得更高
粒子消失更快
三、修改一:开启随机彩色粒子 改动位置 settings.color 和 randomColor() 函数。
原始代码 1 2 3 4 5 color : 'rgba(255,255,255,.5)' ,const alpha = Math .random ().toPrecision (2 );
粒子全部为白色半透明,randomColor() 虽然存在但未被调用。
修改后 1 2 3 4 5 color : 'random' ,const alpha = 1 ;
原理说明 Circle 的 init() 方法中有判断逻辑:
1 2 3 4 5 if (settings.color === 'random' ) { that.color = randomColor (); } else { that.color = settings.color ; }
randomColor() 函数随机生成 RGB 三个通道值(0-255),拼成 rgba() 字符串:
1 2 3 4 5 6 7 function randomColor ( ) { const r = Math .floor (Math .random () * 255 ); const g = Math .floor (Math .random () * 255 ); const b = Math .floor (Math .random () * 255 ); const alpha = 1 ; return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')' ; }
alpha 取值范围 :0(完全透明)到 1(完全不透明),0.5 为半透明。
四、修改二:粒子上升到最高点 改动位置 settings.clearOffset 和 draw() 中的 alpha 衰减值。
原始代码 1 2 3 4 5 clearOffset : 0.2 that.alpha -= 0.0005 ;
修改后 1 2 3 4 5 clearOffset : 0.9 that.alpha -= 0.0001 ;
原理说明 粒子的”寿命”由 alpha 值控制。每个粒子初始化时:
1 that.alpha = 0.1 + Math .random () * settings.clearOffset ;
修改前 :clearOffset: 0.2 -> alpha 范围 0.1 ~ 0.3,衰减 0.0005/帧
最长寿命:0.3 / 0.0005 = 600 帧(约 10 秒 @60fps)
修改后 :clearOffset: 0.9 -> alpha 范围 0.1 ~ 1.0,衰减 0.0001/帧
最长寿命:1.0 / 0.0001 = 10000 帧(约 167 秒 @60fps)
粒子存活时间大幅增加,可以从底部上升到容器顶部。
五、完整修改后代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 circleMagic ();function circleMagic (options ) { let width, height, canvas, ctx; let animateHeader = true ; const circles = []; const settings = options || { color : 'random' , radius : 10 , density : 0.3 , clearOffset : 0.9 } const container = document .getElementById ('smarts' ); initContainer (); addListeners (); function initContainer ( ) { width = container.offsetWidth ; height = container.offsetHeight - 120 ; initCanvas (); canvas = document .getElementById ('homeTopCanvas' ); canvas.width = width; canvas.height = height; canvas.style .position = 'absolute' ; canvas.style .left = '0' ; canvas.style .bottom = '0' ; ctx = canvas.getContext ('2d' ); for (let x = 0 ; x < width * settings.density ; x++) { circles.push (new Circle ()); } animate (); } function initCanvas ( ) { const el = document .createElement ('canvas' ); el.id = 'homeTopCanvas' ; el.style .pointerEvents = 'none' ; container.appendChild (el); el.parentElement .style .overflow = 'hidden' ; } function addListeners ( ) { window .addEventListener ('scroll' , scrollCheck, false ); window .addEventListener ('resize' , resize, false ); } function scrollCheck ( ) { animateHeader = !(document .body .scrollTop > height); } function resize ( ) { width = container.clientWidth ; height = container.clientHeight ; canvas.width = width; canvas.height = height; } function animate ( ) { if (animateHeader) { ctx.clearRect (0 , 0 , width, height); for (const i in circles) circles[i].draw (); } requestAnimationFrame (animate); } function randomColor ( ) { const r = Math .floor (Math .random () * 255 ); const g = Math .floor (Math .random () * 255 ); const b = Math .floor (Math .random () * 255 ); const alpha = 1 ; return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')' ; } function Circle ( ) { const that = this ; (function ( ) { that.pos = {}; init (); })(); function init ( ) { that.pos .x = Math .random () * width; that.pos .y = height + Math .random () * 100 ; that.alpha = 0.1 + Math .random () * settings.clearOffset ; that.scale = 0.1 + Math .random () * 0.3 ; that.speed = Math .random (); that.color = settings.color === 'random' ? randomColor () : settings.color ; } this .draw = function ( ) { if (that.alpha <= 0 ) init (); that.pos .y -= that.speed ; that.alpha -= 0.0001 ; ctx.beginPath (); ctx.arc (that.pos .x , that.pos .y , that.scale * settings.radius , 0 , 2 * Math .PI , false ); ctx.fillStyle = that.color ; ctx.fill (); ctx.closePath (); } } }
六、常用调参速查
想要的效果
修改方式
白色粒子
color: 'rgba(255,255,255,0.5)'
随机彩色粒子
color: 'random'
粒子更大
增大 radius,如 20
粒子更多
增大 density,如 0.6
粒子升得更高
增大 clearOffset(如 0.9)+ 减小 alpha 衰减(如 0.0001)
粒子升得更快
在 init() 中改 that.speed = Math.random() * 3
半透明粒子
randomColor() 中 alpha = 0.5
完全不透明
randomColor() 中 alpha = 1
固定某种颜色
color: 'rgba(255,0,0,1)'(如红色)