主题的模板改成 NexT.Pisces 后,背景看着有些单调,就想加点特效。
搜索一番,发现现在 NexT 主题可以很方便的引入自定义内容了,不需要修改主题的源文件,方便多了。
通过咨询 ChatGPT ,加上了雪花飘落的效果。  
下面是具体的添加方法。
Hexo v5.4.2
NexT v8.15.0
适用于深色主题
修改主题设置
使用了 Javascript 和 CSS 来实现效果
在 NexT 主题的 _config.yaml 中,找到 custom_file_path ,取消对应的注释
_config.yaml| 12
 3
 4
 5
 
 | custom_file_path:
 style: source/_data/styles.styl
 
 bodyEnd: source/_data/body-end.njk
 
 | 
 
然后在 Hexo 目录下的 source 文件夹,创建 _data 文件夹
再创建 styles.styl 和 body-end.njk 文件
添加实现代码
添加 JS 代码
在 body-end.njk 中加入 JS 代码
将动画交给 CSS 处理,不过动态效果还是使用 JS 处理动画的表现更好
预览
source/_data/body-end.njk| 12
 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
 
 | <script>const snowflakes = ["⛄", "❄", "❄", "❆", "❅", "✥"];
 
 function createSnowflake() {
 const snowflake = document.createElement("span");
 snowflake.classList.add("snowflake");
 const randomIndex = Math.floor(Math.random() * snowflakes.length);
 snowflake.textContent = snowflakes[randomIndex];
 
 
 
 
 
 
 
 
 
 
 
 snowflake.style.left = `${Math.random() * 100}vw`;
 snowflake.style.top = `-30px`;
 
 const size = Math.random() * 18 + 10;
 snowflake.style.fontSize = `${size}px`;
 const opacity = Math.random() * 0.6 + (size > 18 ? 0.4 : 0);
 snowflake.style.setProperty("--opacity", opacity);
 
 const fallDuration = Math.random() * 10 + 10;
 
 const rotateDuration = Math.random() * 3 + 1;
 
 snowflake.style.animationDuration = `${fallDuration}s, ${fallDuration}s`;
 
 const translateX = (Math.random() * 500 - 200);
 snowflake.style.setProperty("--translateX", `${translateX}px`);
 
 snowflake.style.setProperty("--translateY", `${window.innerHeight}px`);
 
 document.body.appendChild(snowflake);
 
 setTimeout(() => {
 snowflake.remove();
 }, fallDuration * 1000);
 }
 
 function snowfallAnimation() {
 
 const sidebarnav = document.querySelector('.sidebar');
 const sidebarnavdisplay = window.getComputedStyle(sidebarnav).getPropertyValue('display');
 if (sidebarnavdisplay !== 'none') {
 createSnowflake();
 }
 setTimeout(snowfallAnimation, 150);
 }
 snowfallAnimation();
 </script>
 
 | 
 source/_data/body-end.njk| 12
 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
 
 | <script>
 const snowflakes = ["⛄", "❄", "❅", "❉", "✥"];
 
 function createSnowflake() {
 const snowflake = document.createElement("span");
 snowflake.classList.add("snowflake");
 const randomIndex = Math.floor(Math.random() * snowflakes.length);
 snowflake.textContent = snowflakes[randomIndex];
 
 
 snowflake.style.left = `${Math.random() * 100}vw`;
 snowflake.style.top = '0px';
 
 const size = Math.random() * 18 + 10;
 snowflake.style.fontSize = `${size}px`;
 snowflake.style.opacity = Math.random() * 0.6 + (size > 18 ? 0.4 : 0);
 
 const translateYDuration = Math.random() * 20 + 5;
 const translateXDuration = Math.random() * 5 + 2;
 const rotationDuration = Math.random() * 3 + 1;
 const accumulateDuration = 10;
 
 
 let startTime = null;
 
 function update(timestamp) {
 if (!startTime) startTime = timestamp;
 
 
 const progress = (timestamp - startTime) / 1000;
 
 let translateY = (progress / translateYDuration) * 2000;
 const translateX = Math.sin((progress / translateXDuration) * Math.PI) * 100;
 const rotation = (progress / rotationDuration) * 360;
 
 
 if (translateY + snowflake.offsetHeight > window.innerHeight) {
 translateY = window.innerHeight - snowflake.offsetHeight;
 
 if (progress > translateYDuration + accumulateDuration) {
 snowflake.remove();
 return;
 }
 }
 
 
 snowflake.style.transform = `translateY(${translateY}px) translateX(${translateX}px) rotate(${rotation}deg)`;
 
 
 if (progress < translateYDuration + accumulateDuration) {
 requestAnimationFrame(update);
 } else {
 snowflake.remove();
 }
 }
 
 requestAnimationFrame(update);
 
 
 document.body.appendChild(snowflake);
 }
 
 
 const snowflakeCreationInterval = 250;
 
 
 function snowfallAnimation() {
 
 const sidebarnav = document.querySelector('.sidebar');
 const sidebarnavdisplay = window.getComputedStyle(sidebarnav).getPropertyValue('display');
 if (sidebarnavdisplay !== 'none') {
 createSnowflake();
 }
 setTimeout(() => requestAnimationFrame(snowfallAnimation), snowflakeCreationInterval);
 }
 
 
 requestAnimationFrame(snowfallAnimation);
 </script>
 
 | 
 source/_data/body-end.njk| 12
 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
 
 | <script>let snowflakeInterval;
 
 function createSnowflake() {
 const snowflake = document.createElement("span");
 snowflake.classList.add("snowflake");
 
 
 snowflake.textContent = Math.random() < 0.1 ? "⛄" : "❄";
 
 snowflake.style.left = `${Math.random() * 100}vw`;
 snowflake.style.top = '0px';
 
 
 const size = Math.random() * 18 + 12;
 snowflake.style.fontSize = `${size}px`;
 snowflake.style.opacity = Math.random() * 0.6 + 0.3;
 
 
 const translateYDuration = Math.random() * 20 + 5;
 const translateXDuration = Math.random() * 5 + 2;
 
 const rotationDuration = Math.random() * 3 + 1;
 
 let startTime = null;
 
 function update(timestamp) {
 if (!startTime) startTime = timestamp;
 
 const progress = (timestamp - startTime) / 1000;
 const translateY = (progress / translateYDuration) * 2000;
 const translateX = Math.sin((progress / translateXDuration) * Math.PI) * 100;
 const rotation = (progress / rotationDuration) * 360;
 
 snowflake.style.transform = `translateY(${translateY}px) translateX(${translateX}px) rotate(${rotation}deg)`;
 
 if (progress < translateYDuration) {
 requestAnimationFrame(update);
 } else {
 snowflake.remove();
 }
 }
 
 requestAnimationFrame(update);
 document.body.appendChild(snowflake);
 }
 
 
 function startSnowfall() {
 
 const sidebarnav = document.querySelector('.sidebar');
 const sidebarnavdisplay = window.getComputedStyle(sidebarnav).getPropertyValue('display');
 if (sidebarnavdisplay !== 'none') {
 
 snowflakeInterval = setInterval(createSnowflake, 350);
 }
 }
 
 function stopSnowfall() {
 clearInterval(snowflakeInterval);
 }
 
 function handleVisibilityChange() {
 if (document.visibilityState === "visible") {
 startSnowfall();
 } else {
 stopSnowfall();
 }
 }
 
 document.addEventListener("visibilitychange", handleVisibilityChange);
 
 
 if (document.visibilityState === "visible") {
 startSnowfall();
 }
 </script>
 
 | 
 
| 1
 | snowflake.textContent = Math.random() < 0.1 ? "⛄" : "❄";
 | 
这里设置了 10% 会生成一个 ⛄ ,如果只想生成一种可以使用下面的代码
| 12
 
 | snowflake.textContent = "❄"
 
 | 
添加样式
在 styles.styl 文件中加入雪花样式代码,
- 设置颜色
- 置于网页底部层级
- 鼠标无法选择
- 加入一点荧光
| 12
 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
 
 | .snowflake {
 position: fixed;
 pointer-events: none;
 animation-name: snowflakeFallRotate, snowflakeFadeOut;
 animation-timing-function: linear;
 animation-iteration-count: 1;
 animation-fill-mode: forwards;
 color: white;
 pointer-events: none;
 z-index: -1;
 text-shadow: 0 0 1px rgba(255, 255, 255, 0.8), 0 0 5px rgba(255, 255, 255, 0.8);
 }
 
 @keyframes snowflakeFallRotate {
 0% {
 transform: translateY(0) translateX(0) rotate(0);
 }
 
 70% {
 transform: translateY(var(--translateY)) translateX(var(--translateX)) rotate(720deg);
 }
 100% {
 transform: translateY(var(--translateY)) translateX(var(--translateX)) rotate(800deg);
 }
 }
 
 @keyframes snowflakeFadeOut {
 0%, 90% {
 opacity: var(--opacity);
 }
 100% {
 opacity: 0;
 }
 }
 
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | .snowflake {
 position: fixed;
 color: white;
 display: block;
 pointer-events: none;
 z-index: -1;
 text-shadow: 0 0 5px rgba(255, 255, 255, 0.8), 0 0 10px rgba(255, 255, 255, 0.8);
 }
 
 | 
此时可以 hexo g && hexo s 预览了。
不过这时可以看到由于 NexT.Pisces 边栏和文章主体部分没有透明效果,效果并不好。
我们可以在 styles.styl 中加入下面内容,加上一点透明效果。
source/_data/styles.styl| 12
 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
 
 | .sidebar {
 opacity: 0.85;
 }
 .header, .main-inner {
 background-color: rgba(255,255,255,0.8);
 }
 .menu-item a:hover, .menu-item a.menu-item-active {
 background-color: rgba(150,150,150,0.1);
 }
 .site-brand-container {
 background-color: rgba(0,0,0,0.75);
 }
 
 
 if (hexo-config('darkmode')) {
 @media (prefers-color-scheme: dark){
 
 .sidebar {
 opacity: 0.8;
 }
 .header, .main-inner {
 background-color: rgba(39,39,39,0.75);
 }
 .site-brand-container {
 background-color: rgba(0,0,0,0.5);
 }
 }
 }
 
 | 
 
本地预览没有问题,既可以推送到服务端了。

限制在顶部 30%
看了几天,确实花里胡哨 😅 ,只在顶部有一点效果就不错
修改 JS
source/_data/body-end.njk| 12
 3
 4
 5
 
 | const fallDuration = Math.random() * 10 + 5;
 
 
 snowflake.style.setProperty("--translateY", `${window.innerHeight * 0.3}px`);
 
 | 
 
修改样式文件动画的部分
source/_data/styles.styl| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | @keyframes snowflakeFallRotate {0% {
 transform: translateY(0) translateX(0) rotate(0);
 }
 
 100% {
 transform: translateY(var(--translateY)) translateX(var(--translateX)) rotate(800deg);
 }
 }
 
 @keyframes snowflakeFadeOut {
 0%, 50% {
 opacity: var(--opacity);
 }
 100% {
 opacity: 0;
 }
 }
 
 |