最终效果
效果是页面顶部的效果。Matrix Digital Rain,也叫做Matrix雨。
效果在普通模式和黑夜模式下分别呈现出不同的效果。
说明
首先说明,哥们是个后端,所以基本上全靠chatgpt的指导,才能做出这种功能。
效果实现的整体思路是先把原本的图片元素隐藏起来,然后在相同的位置上,也就是在id为page-header
的header
元素下面,挂一个canvas
元素。然后通过js代码来渲染出效果。
前端元素
首先是页面元素的定义。这里设定id为matrix
。同时加入指向js文件的代码。
修改index.pug
文件定义页面元素的部分,修改后的修改部分如下。
最后一行是定义功能的js文件,在这个部分有详细的说明。
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
| header#page-header(class=`${headerClassName+isFixedClass}`)
canvas#matrix
!=partial('includes/header/nav', {}, {cache: true})
if is_post()
include ./post-info.pug
else if is_home()
#site-info
h1#site-title=config.title
if theme.subtitle.enable
- var loadSubJs = true
#site-subtitle
span#subtitle
if(theme.social)
#site_social_icons
!=partial('includes/header/social', {}, {cache: true})
#scroll-down
i.fas.fa-angle-down.scroll-down-effects
else
#page-site-info
h1#site-title=page.title || page.tag || page.category
script(src='/js/matrix_rain.js')
|
CSS设定
这里遇到一个不好解决的问题,就是butterfly的默认主题颜色是蓝色。
对于我们正在做的这件事情来说,就意味着我们的Matrix雨在开始渲染之前,整个页面会呈现出主题的蓝色,很难看。
暂时没找到原因,所以也没找到解决办法。
作为替代方案,我把主题色调成了黑色,这样显得相对自然一些。
调整主题颜色,在butterfly的配置文件里就能做,有一个叫做theme_color
的配置项,这一项和下面的项都被注释了。
影响主题颜色的配置项是theme_color
下的main
配置,放开注释,把这项调成黑色。
1 2
| theme_color: main: "#000000"
|
在butterfly的配置文件中,已经有canvas
这个元素的配置文件了,这个配置文件是function.styl
。我们在index.pug
这个文件里,给canvas
元素定义的id是matrix
,所以加上以下的配置。
1 2 3 4 5 6 7
| #matrix position: absolute top: 0 left: 0 width: 100% height: 100% z-index: 0
|
z-index
设置成0,高于0的话会覆盖表面的标题,低于0会被覆盖掉。
JS功能
我不希望把功能代码混用,所以新建一个matrix_rain.js
文件,存放功能逻辑,文件存放在js
文件夹下。
完整文件内容如下。
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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
| document.addEventListener('DOMContentLoaded', () => {
const canvas = document.getElementById("matrix");
const context = canvas.getContext("2d");
let intervalId;
let theme = document.documentElement.getAttribute("data-theme") || "light";
createRainEffect(theme);
const observer = new MutationObserver(() => {
const newTheme = document.documentElement.getAttribute("data-theme");
if (newTheme !== theme) {
theme = newTheme;
clearInterval(intervalId);
createRainEffect(theme);
}
});
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
function createRainEffect(theme) {
canvas.width = window.innerWidth;
canvas.height = document.querySelector('#page-header').offsetHeight;
context.clearRect(0, 0, canvas.width, canvas.height);
let fontSize, drawFunction, frequency;
if (theme === "light") {
fontSize = 32;
context.font = `${fontSize}px 'KaiTi', 'SimSun', 'serif'`;
context.fillStyle = "rgba(0, 0, 0, 0.7)";
frequency = 120;
const columns = canvas.width / (fontSize * 2);
const drops = Array(Math.floor(columns)).fill(0).map(() => Math.floor(Math.random() * canvas.height / fontSize));
const sentences = [
" 君子疾夫舍曰“欲之”而必为之辞。吾恐季孙之忧,不在颛臾,而在萧墙之内也。",
" 吾生也有涯,而知也无涯。以有涯随无涯,殆已!",
" 对酒当歌,人生几何?譬如朝露,去日苦多。慨当以慷,忧思难忘。",
" 今汉室倾颓,宜奉天子以令不臣,则可以无敌于天下,进而兴复汉室,还于旧都。",
" 胜兵先胜而后求战,败兵先战而后求胜。",
" 不晓事,则挟私固谬,秉公亦谬,小人固谬,君子亦谬,乡愿固谬,狂狷亦谬。",
" 一派青山景色幽,前人田地后人收。后人收得休欢喜,还有收人在后头。"
];
const sentenceIndex = Array(Math.floor(columns)).fill(0).map(() => Math.floor(Math.random() * sentences.length));
drawFunction = function () {
context.fillStyle = "rgba(245, 245, 245, 0.1)";
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "rgba(0, 0, 0, 0.7)";
context.font = `${fontSize}px 'KaiTi', 'SimSun', 'serif'`;
drops.forEach((y, index) => {
const currentSentence = sentences[sentenceIndex[index]];
const letters = currentSentence.split("");
const charIndex = drops[index] % letters.length;
const text = letters[charIndex];
context.globalAlpha = Math.random() * 0.5 + 0.5;
context.fillText(text, index * fontSize * 2, y * fontSize);
if (y * fontSize > canvas.height && Math.random() > 0.98) {
drops[index] = 0;
sentenceIndex[index] = Math.floor(Math.random() * sentences.length);
}
drops[index]++;
});
};
} else if (theme === "dark") {
const letters = "01".split("");
fontSize = 16;
context.font = `${fontSize}px monospace`;
context.fillStyle = "#0F0";
frequency = 80;
const columns = canvas.width / (fontSize * 2);
const drops = Array(Math.floor(columns)).fill(0).map(() => Math.floor(Math.random() * canvas.height / fontSize));
drawFunction = function () {
context.fillStyle = "rgba(0, 0, 0, 0.05)";
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "#0F0";
context.font = `${fontSize}px monospace`;
drops.forEach((y, index) => {
const text = letters[Math.floor(Math.random() * letters.length)];
context.globalAlpha = Math.random() * 0.5 + 0.5;
context.fillText(text, index * fontSize * 2, y * fontSize);
if (y * fontSize > canvas.height && Math.random() > 0.98) {
drops[index] = 0;
}
drops[index]++;
});
};
}
intervalId = setInterval(drawFunction, frequency);
}
});
|
说实话,很感谢AI技术的进步,不然我根本就写不出这样的东西来。
关键部分都有注释说明。
另外,在script/events
目录下的cdn.js
文件中登记这个js文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const internalSrc = {
matrix_rain: {
name: 'hexo-theme-butterfly',
file: 'js/search/matrix_rain.js',
version
}
}
|
这样,重新生成文件,功能完成!