使用注入器实现换皮动画

前几天看到 shoka 博客主题 的换皮的动画很 nice,想抄到自己的博客主题上。

如果你是和我一样的 Fuild 主题,那直接就抄作业吧,作业位置:

使用注入器实现换皮动画

如果不是的话,那就乖乖把文章看完吧。

文件说明

1
2
3
4
5
6
scripts/injector.js # 注入器
source/css/animation.styl # 动画属性
source/css/custom-theme.styl # 换皮时的动画标签
source/js/cat/custom-utils.js # 自定义的工具
source/js/cat/onClick.js # 监听按键
source/js/global.js # 全局

source/js/global.js

这个文件其实是给 document 封装一些便利的方法。

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
Object.assign(HTMLElement.prototype, {
createChild: function(tag, obj, positon) {
const child = document.createElement(tag);
Object.assign(child, obj);
switch (positon) {
case "after":
this.insertAfter(child);
break;
case "replace":
this.innerHTML = "";
// eslint-disable-next-line no-fallthrough
default:
this.appendChild(child);
}
return child;
},
wrap: function(obj) {
const box = document.createElement("div");
Object.assign(box, obj);
this.parentNode.insertBefore(box, this);
this.parentNode.removeChild(this);
box.appendChild(this);
},
height: function(h) {
if (h) {
this.style.height = typeof h === "number" ? h + "rem" : h;
}
return this.getBoundingClientRect().height;
},
width: function(w) {
if (w) {
this.style.width = typeof w === "number" ? w + "rem" : w;
}
return this.getBoundingClientRect().width;
},
top: function() {
return this.getBoundingClientRect().top;
},
left: function() {
return this.getBoundingClientRect().left;
},
attr: function(type, value) {
if (value === null) {
return this.removeAttribute(type);
}

if (value) {
this.setAttribute(type, value);
return this;
} else {
return this.getAttribute(type);
}
},
insertAfter: function(element) {
const parent = this.parentNode;
if (parent.lastChild === this) {
parent.appendChild(element);
} else {
parent.insertBefore(element, this.nextSibling);
}
},
display: function(d) {
if (d == null) {
return this.style.display;
} else {
this.style.display = d;
return this;
}
},
child: function(selector) {
return $(selector, this);
},
find: function(selector) {
return $.all(selector, this);
},
_class: function(type, className, display) {
const classNames = className.indexOf(" ")
? className.split(" ")
: [className];
const that = this;
classNames.forEach(function(name) {
if (type === "toggle") {
that.classList.toggle(name, display);
} else {
that.classList[type](name);
}
});
},
addClass: function(className) {
this._class("add", className);
return this;
},
removeClass: function(className) {
this._class("remove", className);
return this;
},
toggleClass: function(className, display) {
this._class("toggle", className, display);
return this;
},
hasClass: function(className) {
return this.classList.contains(className);
}
});

这里面有个createChild方法会在source/js/cat/onClick.js中使用。

source/js/cat/custom-utils.js

这个文件是从主题的utils.js中分离出来的方法,原主题中没有的,其实是可以写在source/js/cat/onClick.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
// eslint-disable-next-line no-undef
Fluid.utils.transition = function(target, type, complete) {
let animation = {};
let display = "none";
switch (type) {
case 0:
animation = { opacity: [1, 0] };
break;
case 1:
animation = { opacity: [0, 1] };
display = "block";
break;
case "bounceUpIn":
animation = {
begin: function(anim) {
target.display("block");
},
translateY: [
{ value: -60, duration: 200 },
{ value: 10, duration: 200 },
{ value: -5, duration: 200 },
{ value: 0, duration: 200 }
],
opacity: [0, 1]
};
display = "block";
break;
case "shrinkIn":
animation = {
begin: function(anim) {
target.display("block");
},
scale: [
{ value: 1.1, duration: 300 },
{ value: 1, duration: 200 }
],
opacity: 1
};
display = "block";
break;
case "slideRightIn":
animation = {
begin: function(anim) {
target.display("block");
},
translateX: [100, 0],
opacity: [0, 1]
};
display = "block";
break;
case "slideRightOut":
animation = {
translateX: [0, 100],
opacity: [1, 0]
};
break;
default:
animation = type;
display = type.display;
break;
}
// eslint-disable-next-line no-undef
anime(
Object.assign(
{
targets: target,
duration: 200,
easing: "linear"
},
animation
)
).finished.then(function() {
target.display(display);
complete && complete();
});
};

可以看到,是挂在全局变量的Fluid.utils下面的,如果不是Fluid,建议看一下自己主题是否有utils属性,全局变量名可能是主题名。另外。这也意味着,这个文件需要在全局变量定义后才能加载,否则都不到全局变量。

source/js/cat/onClick.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
(function() {
const rootElement = document.documentElement;
const colorSchemaStorageKey = "Fluid_Color_Scheme";
const colorSchemaMediaQueryKey = "--color-mode";
const colorToggleButtonName = "color-toggle-btn";
const colorToggleIconName = "color-toggle-icon";

function getSchemaFromCSSMediaQuery() {
const res = getComputedStyle(rootElement).getPropertyValue(
colorSchemaMediaQueryKey
);
if (typeof res === "string") {
return res.replace(/["'\s]/g, "");
}
return null;
}
const validColorSchemaKeys = {
dark: true,
light: true
};
function getLS(k) {
try {
return localStorage.getItem(k);
} catch (e) {
return null;
}
}
const invertColorSchemaObj = {
dark: "light",
light: "dark"
};
function toggleCustomColorSchema() {
let currentSetting = getLS(colorSchemaStorageKey);

if (validColorSchemaKeys[currentSetting]) {
// 从 localStorage 中读取模式,并取相反的模式
currentSetting = invertColorSchemaObj[currentSetting];
} else if (currentSetting === null) {
// 当 localStorage 中没有相关值,或者 localStorage 抛了 Error
// 先按照按钮的状态进行切换
const iconElement = document.getElementById(colorToggleIconName);
if (iconElement) {
currentSetting = iconElement.getAttribute("data");
}
if (!iconElement || !validColorSchemaKeys[currentSetting]) {
// 当 localStorage 中没有相关值,或者 localStorage 抛了 Error,则读取默认值并切换到相反的模式
currentSetting = invertColorSchemaObj[getSchemaFromCSSMediaQuery()];
}
} else {
return;
}
// 将相反的模式写入 localStorage

return currentSetting;
}

// eslint-disable-next-line no-undef
Fluid.utils.waitElementLoaded("#" + colorToggleButtonName, function() {
const button = document.getElementById(colorToggleButtonName);
if (button) {
// 当用户点击切换按钮时,获得新的显示模式、写入 localStorage、并在页面上生效
const BODY = document.getElementsByTagName("body")[0];
button.addEventListener("click", () => {
const current = toggleCustomColorSchema();
const neko = BODY.createChild("div", {
id: "neko",
innerHTML:
"<div class=\"planet\"><div class=\"sun\"></div><div class=\"moon\"></div></div><div class=\"body\"><div class=\"face\"><section class=\"eyes left\"><span class=\"pupil\"></span></section><section class=\"eyes right\"><span class=\"pupil\"></span></section><span class=\"nose\"></span></div></div>"
});

const hideNeko = function() {
// eslint-disable-next-line no-undef
Fluid.utils.transition(
neko,
{
delay: 2500,
opacity: 0
},
function() {
BODY.removeChild(neko);
}
);
};
let c;
if (current === "light") {
c = function() {
neko.addClass("dark");
hideNeko();
};
} else {
neko.addClass("dark");
c = function() {
neko.removeClass("dark");
hideNeko();
};
}
// eslint-disable-next-line no-undef
Fluid.utils.transition(neko, 1, function() {
setTimeout(c, 700);
});
});
}
});
})();

Fluild 主题有个Fluid.utils.waitElementLoaded,你的主题如果没有的话,可以使用windows.ready方法来代替,或者抄 Fluild 的作业啊。

刚刚前面两个文件的方法Fluid.utils.transitioncreateChild就在这里用上了。

另外要说明的是,colorSchemaStorageKey是存在 localStorage 中主题状态的 key,如果你的主题不是存 localStorage,那getLS这个方法也需要你修改一下了。currentSetting的值判断也要根据自己的主题来该一下。colorToggleButtonName改成自己的按钮className

样式

剩下的就是样式,照抄就行,反正我也不熟。

source/css/custom-theme.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
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
// custom-theme
#neko {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: none;
background: linear-gradient(to top, #fddb92 0%, #d1fdff 80%);
z-index: 99999;

.planet {
position: fixed;
left: -50%;
top: -50%;
width: 200%;
height: 200%;
animation: rotate 2s cubic-bezier(.7, 0, 0, 1);
transform-origin: center bottom;
}

&:before {
content: "";
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
opacity: 0;
background: linear-gradient(to top, #30cfd0 0%, #330867 100%);
transition: 2s ease all;
}

.sun, .moon {
position: absolute;
border-radius: 100%;
left: 55%;
top: 32%;
}

.sun {
height: 40px;
width: 40px;
background: #ffee94;
box-shadow: 0px 0px 40px #ffee94;
opacity: 1;
}

.moon {
height: 24px;
width: 24px;
background: #eee;
box-shadow: 0px 0px 20px #fff;
opacity: 0;
}

.body {
display: block;
position: absolute;
bottom: -20px;
height: 140px;
width: 135px;
left: 50%;
margin-left: -100px;
background: #777;
transition: all .25s ease-in-out;
animation: slideUpBigIn 1s;

&:before, &:after {
position: absolute;
content: "";
width: 0;
height: 0;
border-bottom: 20px solid #777;
top: -20px;
transition: all .25s ease-in-out;
}
&:before {
border-left: 0px solid transparent;
border-right: 30px solid transparent;
left: 0;
}
&:after {
border-right: 0px solid transparent;
border-left: 30px solid transparent;
right: 0;
}

.eyes {
display: block;
position: absolute;
background: #ffee94;
height: 40px;
width: 40px;
border-radius: 100%;
bottom: 80px;
}

.eyes.left {
left: 12px;
}

.eyes.right {
right: 12px;
}

.eyes .pupil, .nose {
display: block;
position: relative;
background: #ffb399;
border-radius: 100%;
margin: 0 auto;
}

.eyes .pupil {
height: 100%;
width: 5px;
transition: width 1s .5s ease-in-out;
}

.nose {
top: 45px;
height: 10px;
width: 10px;
}
}

&.dark {
&:before {
opacity: 1;
}

.sun {
opacity: 0;
}

.moon {
opacity: 1;
}

.body {
background: #444;
&:before {
border-bottom: 20px solid #444;
}
&:after {
border-bottom: 20px solid #444;
}

.eyes .pupil {
height: 90%;
width: 34px;
margin: 5% auto;
}
}
}
}

.col-12.col-md-4.m-auto.index-img img {
background: #364151;
}

.link-avatar.my-auto img {
background: #364151;
object-fit: contain;
}

.about-avatar img {
background: #364151;
}

source/css/animation.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
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
// animation
// animation: name duration timing-function delay iteration-count direction;

.rotate {
animation: rotate 6s linear infinite;
}

.beat {
animation: beat 1.33s ease-in-out infinite;
}

.flash {
animation: flash 6s cubic-bezier(.22, .61, .36, 1) infinite;
}

.shake {
animation: shake 1s;
}

.fade-in {
animation: fadeIn .5s;
}

.fade-out {
animation: fadeOut .3s;
}

.up-down {
animation: UpDown 2s infinite;
}

.down-up {
animation: DownUp 2s infinite;
}

.slide {
animation: slide .5s;
}

.slide-up-in {
animation: slideUpIn .3s;
}

.slide-up-big-in {
animation: slideUpBigIn .5s;
}

.slide-right-in {
animation: slideRightIn .3s;
}

.slide-left-in {
animation: slideLeftIn .3s;
}

.slide-down-in {
animation: slideDownIn .3s;
}

.blur {
animation: blur .8s ease-in-out forwards;
}

.elastic {
animation: elastic 1s;
}

@keyframes rotate {
from {
transform: rotate(0)
}

to {
transform: rotate(360deg)
}
}

@keyframes rotating {
from {
transform: rotate(720deg);
}

to {
transform: none;
}
}

@keyframes rotate-needle-pause {
0% {
transform: rotateZ(-35deg)
}
100% {
transform: rotateZ(-60deg)
}
}

@keyframes rotate-needle-resume {
0% {
transform: rotateZ(-60deg)
}

100% {
transform: rotateZ(-35deg)
}
}

@keyframes beat {

0%,
100% {
transform: scale(1);
}

10%,
30% {
transform: scale(.9);
}

20%,
40%,
60%,
80% {
transform: scale(1.1);
}

50%,
70% {
transform: scale(1.1);
}
}

@keyframes flash {

0%,
50%,
to {
opacity: 1
}

25%,
75% {
opacity: 0
}
}

@keyframes shake {
from,
to {
transform: translate3d(0, 0, 0);
}

10%,
30%,
50%,
70%,
90% {
transform: translate3d(-10px, 0, 0);
}

20%,
40%,
60%,
80% {
transform: translate3d(10px, 0, 0);
}
}

@keyframes fadeIn {
0% {
opacity: 0;
}

100% {
opacity: 1;
}
}

@keyframes fadeOut {
0% {
opacity: 1;
}

100% {
opacity: 0;
}
}

@keyframes blur {
0% {
filter: blur(10px)
}

to {
filter: blur(0)
}
}

@keyframes blur-dark {
0% {
filter: blur(10px) brightness(.9)
}

to {
filter: blur(0) brightness(.9)
}
}

@keyframes UpDown {
0%, 100% {
opacity: .8;
transform: translateY(10px);
}
50% {
opacity: .4;
transform: translateY(0);
}
}

@keyframes DownUp {
0%, 100% {
opacity: .8;
transform: rotate(180deg) translateY(0);
}
50% {
opacity: .4;
transform: rotate(180deg) translateY(-10px);
}
}

@keyframes slide {
0% {
opacity: 0;
transform: scaleY(0);
}

100% {
opacity: 1;
transform: scaleY(1);
}
}

@keyframes slideRightIn {
0% {
opacity: 0;
transform: translateX(50%);
}

to {
opacity: 1;
transform: translateX(0);
}
}

@keyframes slideLeftIn {
0% {
opacity: 0;
transform: translateX(-50%);
}

to {
opacity: 1;
transform: translateX(0);
}
}

@keyframes slideUpIn {
0% {
opacity: 0;
transform: translateY(10px);
}

to {
opacity: 1;
transform: translateY(0);
}
}

@keyframes slideUpBigIn {
0% {
opacity: 0;
transform: translateY(80px)
}

100% {
opacity: 1;
transform: translateY(0)
}
}

@keyframes slideDownIn {
0% {
opacity: 0;
transform: translateY(-18px)
}
100% {
opacity: 1;
transform: translateY(0)
}
}

@keyframes elastic {
0% {
transform: scale(0)
}

55% {
transform: scale(1)
}

70% {
transform: scale(.98)
}

100% {
transform: scale(1)
}
}

scripts/injector.js

注入器,放在这个文件夹中,因为是给hexo调用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
hexo.extend.injector.register(
"head_begin",
`
<link rel="stylesheet" href="/css/custom-theme.css">
<link rel="stylesheet" href="/css/animation.css">
`,
"default"
);

hexo.extend.injector.register(
"head_end",
`
<script src="https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js"></script>
<script src="/js/global.js"></script>
<script src="/js/cat/custom-utils.js"></script>
<script src="/js/cat/onClick.js"></script>
`,
"default"
);

这里分成两段引入,head_begin放样式表,head_end放脚本。脚本不要放在head_begin,以免全局变量未定义。另外,/js/cat/onClick.js要放在最后,因为它使用了其它三个 js 的方法。

End

最后,有什么问题请留言。


使用注入器实现换皮动画
https://bubao.github.io/posts/3eadcef3.html
作者
一念
发布于
2021年1月24日
许可协议