一、需求与思路 目标:在 /musics/ 页面的 Twikoo 留言区覆盖一层与主页轮播区相同的 Canvas 彩色粒子上升 特效。
难点 Twikoo 的模板 _partial/twikoo.ejs 是公共组件 ,被文章页、联系页等多个页面共用。如果直接修改该文件,所有使用 Twikoo 的页面都会受影响。
解决方案 不修改公共模板,而是在 musics.ejs 中:
用一个 <div> 包裹 Twikoo 组件,作为粒子的容器
用独立的 <script> 在该容器上创建 Canvas 粒子动画
粒子 Canvas 设置 pointerEvents: none,不影响留言输入
二、完整实现代码 文件:themes/hexo-theme-matery/layout/musics.ejs
在 </article> 之后添加:
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 <% if (theme.twikoo && theme.twikoo.enable) { %> <div id ="musics-twikoo-wrap" style ="position: relative; overflow: hidden;" > <%- partial('_partial/twikoo') %> </div > <script > (function ( ){ var container = document .getElementById ('musics-twikoo-wrap' ); if (!container) return ; var width = container.offsetWidth ; var height = container.offsetHeight ; var canvas = document .createElement ('canvas' ); canvas.id = 'musicsTwikooCanvas' ; canvas.width = width; canvas.height = height; canvas.style .position = 'absolute' ; canvas.style .left = '0' ; canvas.style .top = '0' ; canvas.style .pointerEvents = 'none' ; canvas.style .zIndex = '1' ; container.appendChild (canvas); var ctx = canvas.getContext ('2d' ); var circles = []; var density = 0.3 ; var radius = 10 ; var clearOffset = 0.9 ; function randomColor ( ) { var r = Math .floor (Math .random () * 255 ); var g = Math .floor (Math .random () * 255 ); var b = Math .floor (Math .random () * 255 ); return 'rgba(' + r + ',' + g + ',' + b + ',1)' ; } function Circle ( ) { this .init = function ( ) { this .x = Math .random () * width; this .y = height + Math .random () * 100 ; this .alpha = 0.1 + Math .random () * clearOffset; this .scale = 0.1 + Math .random () * 0.3 ; this .speed = Math .random (); this .color = randomColor (); }; this .draw = function ( ) { if (this .alpha <= 0 ) this .init (); this .y -= this .speed ; this .alpha -= 0.0001 ; ctx.beginPath (); ctx.arc (this .x , this .y , this .scale * radius, 0 , 2 * Math .PI , false ); ctx.fillStyle = this .color ; ctx.fill (); ctx.closePath (); }; this .init (); } for (var i = 0 ; i < width * density; i++) { circles.push (new Circle ()); } function animate ( ) { ctx.clearRect (0 , 0 , width, height); for (var j = 0 ; j < circles.length ; j++) { circles[j].draw (); } requestAnimationFrame (animate); } window .addEventListener ('resize' , function ( ) { width = container.offsetWidth ; height = container.offsetHeight ; canvas.width = width; canvas.height = height; }); animate (); })(); </script > <% } %>
三、代码逐层解析 3.1 包裹容器 1 <div id ="musics-twikoo-wrap" style ="position: relative; overflow: hidden;" >
样式
作用
position: relative
让内部 Canvas 的 absolute 定位相对于此容器
overflow: hidden
防止粒子溢出留言区边界
3.2 Canvas 创建 1 2 3 4 5 6 var canvas = document .createElement ('canvas' );canvas.style .position = 'absolute' ; canvas.style .left = '0' ; canvas.style .top = '0' ; canvas.style .pointerEvents = 'none' ; canvas.style .zIndex = '1' ;
pointerEvents: none 是最关键的设置——它让 Canvas 层对鼠标完全透明,用户点击、输入、选择文本等操作都能穿透到下方的 Twikoo 留言区。
3.3 粒子参数(与主页一致) 1 2 3 4 5 var density = 0.3 ; var radius = 10 ; var clearOffset = 0.9 ; this .alpha -= 0.0001 ;
这些参数与 index.ejs 主页轮播区的 circleMagic() 完全一致:
参数
主页 index.ejs
留言区 musics.ejs
一致性
color
'random'
randomColor()
相同
alpha
1(不透明)
1(不透明)
相同
density
0.3
0.3
相同
radius
10
10
相同
clearOffset
0.9
0.9
相同
alpha 衰减
0.0001
0.0001
相同
3.4 随机颜色 1 2 3 4 5 6 function randomColor ( ) { var r = Math .floor (Math .random () * 255 ); var g = Math .floor (Math .random () * 255 ); var b = Math .floor (Math .random () * 255 ); return 'rgba(' + r + ',' + g + ',' + b + ',1)' ; }
每个粒子独立生成随机 RGB 颜色,alpha 固定为 1(完全不透明)。
3.5 响应式适配 1 2 3 4 5 6 window .addEventListener ('resize' , function ( ) { width = container.offsetWidth ; height = container.offsetHeight ; canvas.width = width; canvas.height = height; });
窗口大小变化时自动调整 Canvas 尺寸,保持粒子覆盖区域正确。
四、为什么不修改公共模板 twikoo.ejs
方案
优点
缺点
修改 _partial/twikoo.ejs
一处修改,全局生效
所有页面的 Twikoo 都会有粒子效果,可能不需要
在 musics.ejs 中包裹 (当前方案)
仅影响音乐页面,其他页面不受影响
代码仅在 musics.ejs 中
当前方案遵循”最小影响”原则:
_partial/twikoo.ejs 被 post-detail.ejs、contact.ejs、friends.ejs 等共用
只在 musics 页面需要粒子效果
用容器包裹 + 独立脚本,零侵入
五、两套粒子特效对比 musics 页面现在有两种粒子特效并存:
区域
技术方案
颜色
上升高度
音乐播放器区(card 内)
CSS @keyframes + jQuery DOM
荧光绿 rgba(128,255,0,0.7)
最高值(-80000%,60s)
留言区(Twikoo 上方)
Canvas 2D + requestAnimationFrame
随机彩色 (不透明)
最高值(clearOffset:0.9,衰减 0.0001)
六、自定义调参 如需调整留言区粒子效果,修改 musics.ejs 中对应变量:
效果
修改
粒子更多
density = 0.6
粒子更大
radius = 20
粒子更快消失
this.alpha -= 0.001
固定白色粒子
return 'rgba(255,255,255,0.5)'
半透明彩色
return 'rgba('+r+','+g+','+b+',0.5)'
粒子升得更快
this.speed = Math.random() * 3
七、复用到其他页面的留言区 如需在其他页面(如 /text/、/friends/)的 Twikoo 留言区也添加彩色粒子,使用相同的包裹模式:
1 2 3 4 5 6 7 8 9 <div id ="页面名-twikoo-wrap" style ="position: relative; overflow: hidden;" > <%- partial('_partial/twikoo') %> </div > <script > (function ( ){ var container = document .getElementById ('页面名-twikoo-wrap' ); })(); </script >
只需修改 id 名称避免冲突即可。