魔改日记(3)

魔改前须知

  1. 魔改有风险,记得备份
  2. 魔改参考:akilar | Fomalhaut🥝 | 安知鱼 …,更多人请到 友链寻找)(全是大佬)。本文仅做一个好用的魔改总结,便于自己查找。
  3. 鉴于每个人的根目录名称都不一样,本帖 博客根目录 一律以[BlogRoot]指代。
  4. 涉及魔改源码的内容,会使用diff代码块标识,复制时请不要忘记删除前面的+、-符号。
  5. 引入 自定义的css与js文件,方法见 Hexo博客添加自定义css和js文件
  6. themes/butterfly 等同于 /node_modules/hexo-theme-butterfly

新年倒计时卡片

详见:博客新年倒计时卡片 | Leonus

1)新建 [Blogroot]\source\_data\widget.yml 文件,在文件中加入如下代码

建议查看butterfly官方文档:自定义侧边栏

1
2
3
4
5
6
7
8
9
10
11
12
13
# top: 创建的 widget 会出现在非 sticky 区域(即所有页面都会显示)
# bottom: 创建的 widget 会出现在 sticky 区域(除了文章页都会显示)
top:
- class_name:
id_name: newYear
name:
icon:
order: 1
html: '<div id="newYear-main"><div class="mask"></div>
<p class="title"></p>
<div class="newYear-time"></div>
<p class="today" style="text-align: right;"></p>
</div>'

2)在 [BlogRoot]\source\css\custom.css 增加如下代码:

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
/* 新年侧边栏 */

#newYear {
color: white;
padding: 0 !important;
}

#newYear p,
#newYear h3 {
font-weight: normal;
color: inherit;
margin: 0;
}

#newYear .item-headline {
display: none;
}

#newYear-main {
min-height: 160px;
padding: 1rem;
position: relative;
border-radius: 12px;
background-image: url(https://cdn.leonus.cn/img/tunian3.webp);
background-size: cover;
background-position: center;
}

#newYear-main * {
position: relative;
line-height: 1.3;
}

#newYear-main .newYear-time {
font-weight: bold;
text-align: center;
}

#newYear-main .time,
#newYear-main .happyNewYear {
font-size: 3.5rem;
margin: 1rem 0;
display: block;
}

#newYear-main .day {
font-size: 5rem;
}

#newYear-main .day .unit {
font-size: 1rem;
}

#newYear-main .mask {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, .1);
}

3)新建文件[BlogRoot]\source\js\newYear.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
let newYearTimer = null;
var newYear = () => {
clearTimeout(newYearTimer);
if (!document.querySelector('#newYear')) return;
// 新年时间戳 and 星期对象
let newYear = new Date('2023-01-22 00:00:00').getTime() / 1000,
week = { 0: '周日', 1: '周一', 2: '周二', 3: '周三', 4: '周四', 5: '周五', 6: '周六' }

time();

// 补零函数
function nol(h) { return h > 9 ? h : '0' + h; };

function time() {
// 现在 时间对象
let now = new Date();

// 右下角 今天
document.querySelector('#newYear .today').innerHTML = now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate() + ' ' + week[now.getDay()]

// 现在与新年相差秒数
let second = newYear - Math.round(now.getTime() / 1000);

// 小于0则表示已经过年
if (second < 0) {
document.querySelector('#newYear .title').innerHTML = 'Happy New Year!';
document.querySelector('#newYear .newYear-time').innerHTML = '<span class="happyNewYear">新年快乐</span>';
} else {
// 大于0则还未过年
document.querySelector('#newYear .title').innerHTML = '距离2023年春节:'

// 大于一天则直接渲染天数
if (second > 86400) {
document.querySelector('#newYear .newYear-time').innerHTML = `<span class="day">${Math.ceil(second / 86400)}<span class="unit">天</span></span>`
} else {
// 小于一天则使用时分秒计时。
let h = nol(parseInt(second / 3600));
second %= 3600;
let m = nol(parseInt(second / 60));
second %= 60;
let s = nol(second);
document.querySelector('#newYear .newYear-time').innerHTML = `<span class="time">${h}:${m}:${s}</span></span>`;
// 计时
newYearTimer = setTimeout(time, 1000);
}
}
}

// 元宝飘落
jQuery(document).ready(function($) {
$('#newYear').wpSuperSnow({
flakes: ['https://cdn.leonus.cn/img/yb1.webp', 'https://cdn.leonus.cn/img/yb2.webp', 'https://cdn.leonus.cn/img/yb3.webp'],
totalFlakes: '100',
zIndex: '999999',
maxSize: '30',
maxDuration: '20',
useFlakeTrans: false
});
});
}
newYear();

4)在主题配置文件_config.butterfly.yml引入该文件:

1
2
3
4
5
inject: 
bottom:
- <script src="https://cdn.staticfile.org/jquery/3.6.3/jquery.min.js"></script> # jQuery
- <script async data-pjax src="https://cdn.wpon.cn/2022-sucai/Gold-ingot.js"></script> # 新年元宝
- <script async data-pjax src="/js/newYear.js"></script> # 新年倒计时

打赏按钮菜单

详见:糖果屋微调合集 Fomalhaut🥝

修改[Blogroot]\themes\butterfly\layout\includes\post\reward.pug,整体替换为以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
link(rel='stylesheet' href=url_for(theme.CDN.coin_css) media="defer" onload="this.media='all'")
.post-reward
button.tip-button.reward-button
span.tip-button__text= _p('donate')
.coin-wrapper
.coin
.coin__middle
.coin__back
.coin__front
.reward-main
ul.reward-all
each item in theme.reward.QR_code
- var clickTo = (item.itemlist||item).link ? (item.itemlist||item).link : (item.itemlist||item).img
li.reward-item
a(href=clickTo target='_blank')
img.post-qr-code-img(src=url_for((item.itemlist||item).img) alt=(item.itemlist||item).text)
.post-qr-code-desc=(item.itemlist||item).text
if theme.reward.coinAudio
- var coinAudio = theme.reward.coinAudio ? url_for(theme.reward.coinAudio) : 'https://npm.elemecdn.com/akilar-candyassets@1.0.16/audio/coin.mp3'
audio#coinAudio(src=coinAudio)
script(defer src=url_for(theme.CDN.coin_js))

2)新建[Blogroot]\themes\butterfly\source\css\coin.css

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
.tip-button {
border: 0;
border-radius: 0.25rem;
cursor: pointer;
font-size: 20px;
font-weight: 600;
height: 2.6rem;
margin-bottom: -4rem;
outline: 0;
position: relative;
top: 0;
transform-origin: 0% 100%;
transition: transform 50ms ease-in-out;
width: auto;
-webkit-tap-highlight-color: transparent;
}
.tip-button:active {
transform: rotate(4deg);
}
.tip-button.clicked {
animation: 150ms ease-in-out 1 shake;
pointer-events: none;
}
.tip-button.clicked .tip-button__text {
opacity: 0;
transition: opacity 100ms linear 200ms;
}
.tip-button.clicked::before {
height: 0.5rem;
width: 60%;
background: $button-hover-color;
}
.tip-button.clicked .coin {
transition: margin-bottom 1s linear 200ms;
margin-bottom: 0;
}
.tip-button.shrink-landing::before {
transition: width 200ms ease-in;
width: 0;
}
.tip-button.coin-landed::after {
opacity: 1;
transform: scale(1);
transform-origin: 50% 100%;
}
.tip-button.coin-landed .coin-wrapper {
background: radial-gradient(circle at 35% 97%, rgba(3, 16, 50, 0.4) 0.04rem, transparent 0.04rem), radial-gradient(circle at 45% 92%, rgba(3, 16, 50, 0.4) 0.04rem, transparent 0.02rem), radial-gradient(circle at 55% 98%, rgba(3, 16, 50, 0.4) 0.04rem, transparent 0.04rem), radial-gradient(circle at 65% 96%, rgba(3, 16, 50, 0.4) 0.06rem, transparent 0.06rem);
background-position: center bottom;
background-size: 100%;
bottom: -1rem;
opacity: 0;
transform: scale(2) translateY(-10px);
}
.tip-button__text {
color: #fff;
margin-right: 1.8rem;
opacity: 1;
position: relative;
transition: opacity 100ms linear 500ms;
z-index: 3;
}
.tip-button::before {
border-radius: 0.25rem;
bottom: 0;
content: '';
display: block;
height: 100%;
left: 50%;
position: absolute;
transform: translateX(-50%);
transition: height 250ms ease-in-out 400ms, width 250ms ease-in-out 300ms;
width: 100%;
z-index: 2;
}
.tip-button::after {
bottom: -1rem;
color: white;
content: 'ヾ(≧O≦)〃嗷~'; /*点击后显示的内容*/
height: 110%;
left: 0;
opacity: 0;
position: absolute;
pointer-events: none;
text-align: center;
transform: scale(0);
transform-origin: 50% 20%;
transition: transform 200ms cubic-bezier(0, 0, 0.35, 1.43);
width: 100%;
z-index: 1;
}

.coin-wrapper {
background: none;
bottom: 0;
height: 18rem;
left: 0;
opacity: 1;
overflow: hidden;
pointer-events: none;
position: absolute;
transform: none;
transform-origin: 50% 100%;
transition: opacity 200ms linear 100ms, transform 300ms ease-out;
width: 100%;
}

.coin {
--front-y-multiplier: 0;
--back-y-multiplier: 0;
--coin-y-multiplier: 0;
--coin-x-multiplier: 0;
--coin-scale-multiplier: 0;
--coin-rotation-multiplier: 0;
--shine-opacity-multiplier: 0.4;
--shine-bg-multiplier: 50%;
bottom: calc(var(--coin-y-multiplier) * 1rem - 3.5rem);
height: 3.5rem;
margin-bottom: 3.05rem;
position: absolute;
right: calc(var(--coin-x-multiplier) * 34% + 16%);
transform: translateX(50%) scale(calc(0.4 + var(--coin-scale-multiplier))) rotate(calc(var(--coin-rotation-multiplier) * -1deg));
transition: opacity 100ms linear 200ms;
width: 3.5rem;
z-index: 3;
}
.coin__front, .coin__middle, .coin__back, .coin::before, .coin__front::after, .coin__back::after {
border-radius: 50%;
box-sizing: border-box;
height: 100%;
left: 0;
position: absolute;
width: 100%;
z-index: 3;
}
.coin__front {
background: radial-gradient(circle at 50% 50%, transparent 50%, rgba(115, 124, 153, 0.4) 54%, #c2cadf 54%), linear-gradient(210deg, #8590b3 32%, transparent 32%), linear-gradient(150deg, #8590b3 32%, transparent 32%), linear-gradient(to right, #8590b3 22%, transparent 22%, transparent 78%, #8590b3 78%), linear-gradient(to bottom, #fcfaf9 44%, transparent 44%, transparent 65%, #fcfaf9 65%, #fcfaf9 71%, #8590b3 71%), linear-gradient(to right, transparent 28%, #fcfaf9 28%, #fcfaf9 34%, #8590b3 34%, #8590b3 40%, #fcfaf9 40%, #fcfaf9 47%, #8590b3 47%, #8590b3 53%, #fcfaf9 53%, #fcfaf9 60%, #8590b3 60%, #8590b3 66%, #fcfaf9 66%, #fcfaf9 72%, transparent 72%);
background-color: #8590b3;
background-size: 100% 100%;
transform: translateY(calc(var(--front-y-multiplier) * 0.3181818182rem / 2)) scaleY(var(--front-scale-multiplier));
}
.coin__front::after {
background: rgba(0, 0, 0, 0.2);
content: '';
opacity: var(--front-y-multiplier);
}
.coin__middle {
background: #737c99;
transform: translateY(calc(var(--middle-y-multiplier) * 0.3181818182rem / 2)) scaleY(var(--middle-scale-multiplier));
}
.coin__back {
background: radial-gradient(circle at 50% 50%, transparent 50%, rgba(115, 124, 153, 0.4) 54%, #c2cadf 54%), radial-gradient(circle at 50% 40%, #fcfaf9 23%, transparent 23%), radial-gradient(circle at 50% 100%, #fcfaf9 35%, transparent 35%);
background-color: #8590b3;
background-size: 100% 100%;
transform: translateY(calc(var(--back-y-multiplier) * 0.3181818182rem / 2)) scaleY(var(--back-scale-multiplier));
}
.coin__back::after {
background: rgba(0, 0, 0, 0.2);
content: '';
opacity: var(--back-y-multiplier);
}
.coin::before {
background: radial-gradient(circle at 25% 65%, transparent 50%, rgba(255, 255, 255, 0.9) 90%), linear-gradient(55deg, transparent calc(var(--shine-bg-multiplier) + 0%), #e9f4ff calc(var(--shine-bg-multiplier) + 0%), transparent calc(var(--shine-bg-multiplier) + 50%));
content: '';
opacity: var(--shine-opacity-multiplier);
transform: translateY(calc(var(--middle-y-multiplier) * 0.3181818182rem / -2)) scaleY(var(--middle-scale-multiplier)) rotate(calc(var(--coin-rotation-multiplier) * 1deg));
z-index: 10;
}
.coin::after {
background: #737c99;
content: '';
height: 0.3181818182rem;
left: 0;
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 100%;
z-index: 2;
}

@keyframes shake {
0% {
transform: rotate(4deg);
}
66% {
transform: rotate(-4deg);
}
100% {
transform: rotate();
}
}

3)新建[Blogroot]\themes\butterfly\source\js\coin.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
var tipButtons = document.querySelectorAll('.tip-button')

function coinAudio() {
var coinAudio = document.getElementById("coinAudio");
if (coinAudio) {
coinAudio.play();//有音频时播放
}
}
// Loop through all buttons (allows for multiple buttons on page)
tipButtons.forEach((button) => {
var coin = button.querySelector('.coin')

// The larger the number, the slower the animation
coin.maxMoveLoopCount = 90

button.addEventListener('click', () => {
if (/Android|webOS|BlackBerry/i.test(navigator.userAgent)) return true; //媒体选择
if (button.clicked) return
button.classList.add('clicked')

// Wait to start flipping th coin because of the button tilt animation
setTimeout(() => {
// Randomize the flipping speeds just for fun
coin.sideRotationCount = Math.floor(Math.random() * 5) * 90
coin.maxFlipAngle = (Math.floor(Math.random() * 4) + 3) * Math.PI
button.clicked = true
flipCoin()
coinAudio()
}, 50)
})

var flipCoin = () => {
coin.moveLoopCount = 0
flipCoinLoop()
}

var resetCoin = () => {
coin.style.setProperty('--coin-x-multiplier', 0)
coin.style.setProperty('--coin-scale-multiplier', 0)
coin.style.setProperty('--coin-rotation-multiplier', 0)
coin.style.setProperty('--shine-opacity-multiplier', 0.4)
coin.style.setProperty('--shine-bg-multiplier', '50%')
coin.style.setProperty('opacity', 1)
// Delay to give the reset animation some time before you can click again
setTimeout(() => {
button.clicked = false
}, 300)
}

var flipCoinLoop = () => {
coin.moveLoopCount++
var percentageCompleted = coin.moveLoopCount / coin.maxMoveLoopCount
coin.angle = -coin.maxFlipAngle * Math.pow((percentageCompleted - 1), 2) + coin.maxFlipAngle

// Calculate the scale and position of the coin moving through the air
coin.style.setProperty('--coin-y-multiplier', -11 * Math.pow(percentageCompleted * 2 - 1, 4) + 11)
coin.style.setProperty('--coin-x-multiplier', percentageCompleted)
coin.style.setProperty('--coin-scale-multiplier', percentageCompleted * 0.6)
coin.style.setProperty('--coin-rotation-multiplier', percentageCompleted * coin.sideRotationCount)

// Calculate the scale and position values for the different coin faces
// The math uses sin/cos wave functions to similate the circular motion of 3D spin
coin.style.setProperty('--front-scale-multiplier', Math.max(Math.cos(coin.angle), 0))
coin.style.setProperty('--front-y-multiplier', Math.sin(coin.angle))

coin.style.setProperty('--middle-scale-multiplier', Math.abs(Math.cos(coin.angle), 0))
coin.style.setProperty('--middle-y-multiplier', Math.cos((coin.angle + Math.PI / 2) % Math.PI))

coin.style.setProperty('--back-scale-multiplier', Math.max(Math.cos(coin.angle - Math.PI), 0))
coin.style.setProperty('--back-y-multiplier', Math.sin(coin.angle - Math.PI))

coin.style.setProperty('--shine-opacity-multiplier', 4 * Math.sin((coin.angle + Math.PI / 2) % Math.PI) - 3.2)
coin.style.setProperty('--shine-bg-multiplier', -40 * (Math.cos((coin.angle + Math.PI / 2) % Math.PI) - 0.5) + '%')

// Repeat animation loop
if (coin.moveLoopCount < coin.maxMoveLoopCount) {
if (coin.moveLoopCount === coin.maxMoveLoopCount - 6) button.classList.add('shrink-landing')
window.requestAnimationFrame(flipCoinLoop)
} else {
button.classList.add('coin-landed')
coin.style.setProperty('opacity', 0)
setTimeout(() => {
button.classList.remove('clicked', 'shrink-landing', 'coin-landed')
setTimeout(() => {
resetCoin()
}, 300)
}, 1500)
}
}
})

4)修改_config.butterfly.yml,添加音频文件配置项,添加CDN配置项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  # Sponsor/reward
reward:
enable: true
+ coinAudio: https://npm.elemecdn.com/akilar-candyassets@1.0.16/audio/coin.mp3
QR_code:



CDN:
# main
main_css: /css/index.css
jquery: https://npm.elemecdn.com/jquery@latest/dist/jquery.min.js
main: /js/main.js
utils:/js/utils.js
+ # 打赏按钮投币效果
+ coin_js: /js/coin.js
+ coin_css: /css/coin.css

5)现在的打赏按钮样式需要稍作适配,当前若提示语太长,悬停时会无法显示完全。需要微调一下,修改[Blogroot]\themes\butterfly\source\css\_layout\reward.styl,整体替换为以下内容:

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
.post-reward
position: relative
margin-top: 4rem
width: 100%
text-align: center

.reward-button
display: inline-block
padding: .2rem 1.2rem
background: var(--btn-bg)
color: var(--btn-color)
cursor: pointer
transition: all .4s

&:hover
box-shadow: inset 15em 0 0 0 var(--btn-hover-color)

.reward-main
display: block

.reward-main
position: absolute
bottom: 40px
left: -25%
z-index: 100
display: none
padding: 0 0 15px
width: 150%

.reward-all
display: inline-block
margin: 0
padding: 1rem .5rem
border-radius: 4px
background: var(--reward-pop)

&:before
position: absolute
bottom: -10px
left: 0
width: 100%
height: 20px
content: ''

&:after
position: absolute
right: 0
bottom: 2px
left: 0
margin: 0 auto
width: 0
height: 0
border-top: 13px solid var(--reward-pop)
border-right: 13px solid transparent
border-left: 13px solid transparent
content: ''

.reward-item
display: inline-block
padding: 0 8px
list-style-type: none
vertical-align: top

img
width: 130px
height: 130px

.post-qr-code-desc
padding-top: .4rem
width: 130px
color: $reward-pop-up-color