Live 2D 看板娘回来啦!


由于害怕一些版权方面的冲突,并且加入了模型可能会导致主题变得臃肿,这个功能当时移植的时候是被我砍掉了;后来也有dalao写的hexo-helper-live2d出现,但是经过实际测试发现效果并不尽如人意,所以想了想又把这个功能加回来了。然而目前还是处于测试阶段,估计各方面的设置也都不是很完备,因此我为主题专门开了一个live2d分支用于测试;对于一般用户而言,如果您需要看板娘的话,直接使用稳定成型的插件就好啦w


既然您愿意继续往下看读到这里,那就不妨来听听我折腾新功能的小小经验吧,如果能给您带来启发,那自然是再好不过的啦~

我参照的是Jad大佬的Waifu-Tips项目,相比起单单引入的插件来说,各方面的交互等体验都更加优异,唯一美中不足的就是这篇博文似乎后来更新的时候删掉了一些内容,删减去了看板娘右边的小工具。但这并难不倒我,因为我是从狗狗的WordPress主题集成中最早熟悉这个项目哒,也因此有相当多的代码(包括失传的Live2D载入SDK),可以从那边直接参考过来w

基本功能代码可以直接拿走,一些进阶的设置则需要特殊配置一下,这里就贴上我的设置代码啦~别忘了把所有的path/to改成您实际的路径哦

首先我们要引入Live2D相关的样式,因此加入如下的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
.waifu, .waifu * {transition:all .3s ease-in-out;-webkit-transform:translateY(3px);transform:translateY(3px);}
.waifu {position:fixed;bottom:0;right:40px;z-index:1;font-size:0;}
.waifu:hover {-webkit-transform:translateY(0);transform:translateY(0);}
.waifu-tool{opacity:0;top:150px;right:0;position:absolute}
.waifu-tool *{display:block;color:#aaa;font-size:16px;margin:5px 0}
.waifu:hover .waifu-tool{opacity:1;}
@media (max-width:768px) {.waifu {display:none;}} /*移动设备上自动隐藏,防止挡住文章及功能部件*/
.waifu-tips {opacity:0;width:200px;height:70px;padding:5px 10px;border:1px solid rgba(224, 186, 140, 0.62);border-radius:12px;background-color:rgba(236, 217, 188, 0.5);box-shadow:0 3px 15px 2px rgba(191, 158, 118, 0.2);font-size:12px;text-overflow:ellipsis;overflow:hidden;position:absolute;animation-delay:5s;animation-duration:50s;animation-iteration-count:infinite;animation-name:shake;animation-timing-function:ease-in-out;text-align:center;}
.waifu #live2d{position:relative;}
@keyframes shake {2% {transform:translate(0.5px, -1.5px) rotate(-0.5deg);}
4% {transform:translate(0.5px, 1.5px) rotate(1.5deg);}
6% {transform:translate(1.5px, 1.5px) rotate(1.5deg);}
8% {transform:translate(2.5px, 1.5px) rotate(0.5deg);}
10% {transform:translate(0.5px, 2.5px) rotate(0.5deg);}
12% {transform:translate(1.5px, 1.5px) rotate(0.5deg);}
14% {transform:translate(0.5px, 0.5px) rotate(0.5deg);}
16% {transform:translate(-1.5px, -0.5px) rotate(1.5deg);}
18% {transform:translate(0.5px, 0.5px) rotate(1.5deg);}
20% {transform:translate(2.5px, 2.5px) rotate(1.5deg);}
22% {transform:translate(0.5px, -1.5px) rotate(1.5deg);}
24% {transform:translate(-1.5px, 1.5px) rotate(-0.5deg);}
26% {transform:translate(1.5px, 0.5px) rotate(1.5deg);}
28% {transform:translate(-0.5px, -0.5px) rotate(-0.5deg);}
30% {transform:translate(1.5px, -0.5px) rotate(-0.5deg);}
32% {transform:translate(2.5px, -1.5px) rotate(1.5deg);}
34% {transform:translate(2.5px, 2.5px) rotate(-0.5deg);}
36% {transform:translate(0.5px, -1.5px) rotate(0.5deg);}
38% {transform:translate(2.5px, -0.5px) rotate(-0.5deg);}
40% {transform:translate(-0.5px, 2.5px) rotate(0.5deg);}
42% {transform:translate(-1.5px, 2.5px) rotate(0.5deg);}
44% {transform:translate(-1.5px, 1.5px) rotate(0.5deg);}
46% {transform:translate(1.5px, -0.5px) rotate(-0.5deg);}
48% {transform:translate(2.5px, -0.5px) rotate(0.5deg);}
50% {transform:translate(-1.5px, 1.5px) rotate(0.5deg);}
52% {transform:translate(-0.5px, 1.5px) rotate(0.5deg);}
54% {transform:translate(-1.5px, 1.5px) rotate(0.5deg);}
56% {transform:translate(0.5px, 2.5px) rotate(1.5deg);}
58% {transform:translate(2.5px, 2.5px) rotate(0.5deg);}
60% {transform:translate(2.5px, -1.5px) rotate(1.5deg);}
62% {transform:translate(-1.5px, 0.5px) rotate(1.5deg);}
64% {transform:translate(-1.5px, 1.5px) rotate(1.5deg);}
66% {transform:translate(0.5px, 2.5px) rotate(1.5deg);}
68% {transform:translate(2.5px, -1.5px) rotate(1.5deg);}
70% {transform:translate(2.5px, 2.5px) rotate(0.5deg);}
72% {transform:translate(-0.5px, -1.5px) rotate(1.5deg);}
74% {transform:translate(-1.5px, 2.5px) rotate(1.5deg);}
76% {transform:translate(-1.5px, 2.5px) rotate(1.5deg);}
78% {transform:translate(-1.5px, 2.5px) rotate(0.5deg);}
80% {transform:translate(-1.5px, 0.5px) rotate(-0.5deg);}
82% {transform:translate(-1.5px, 0.5px) rotate(-0.5deg);}
84% {transform:translate(-0.5px, 0.5px) rotate(1.5deg);}
86% {transform:translate(2.5px, 1.5px) rotate(0.5deg);}
88% {transform:translate(-1.5px, 0.5px) rotate(1.5deg);}
90% {transform:translate(-1.5px, -0.5px) rotate(-0.5deg);}
92% {transform:translate(-1.5px, -1.5px) rotate(1.5deg);}
94% {transform:translate(0.5px, 0.5px) rotate(-0.5deg);}
96% {transform:translate(2.5px, -0.5px) rotate(-0.5deg);}
98% {transform:translate(-1.5px, -1.5px) rotate(-0.5deg);}
0%, 100% {transform:translate(0, 0) rotate(0);}
}

准备失传的live2d加载脚本:(可以从这里获取哦)

1
<script src="path/to/live2d.min.js"></script>

引入waifu-tips脚本:

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
function render(template, context) {
var tokenReg = /(\\)?\{([^\{\}\\]+)(\\)?\}/g;
return template.replace(tokenReg, function (word, slash1, token, slash2) {
if (slash1 || slash2) {
return word.replace('\\', '');
}

var variables = token.replace(/\s/g, '').split('.');
var currentObject = context;
var i, length, variable;

for (i = 0, length = variables.length; i < length; ++i) {
variable = variables[i];
currentObject = currentObject[variable];
if (currentObject === undefined || currentObject === null) return '';
}
return currentObject;
});
}
String.prototype.render = function (context) {
return render(this, context);
};

if(localStorage.getItem('waifu-display')==="none"){
$('.waifu').hide();
}
let l2dCapture = ()=>{
showMessage('拍好啦,是不是很可爱呢~',5000);
window.Live2D.captureName = 'capture.png';
window.Live2D.captureFrame = true;
};
let l2dClose = ()=>{
localStorage.setItem('waifu-display','none');
showMessage('愿有一天,您能与重要的人重逢',2000);
window.setTimeout(function(){$('.waifu').hide();$('.waifu-btn').show()},1000);
};

let re = /x/;
re.toString = ()=>{
showMessage('欸欸欸,那里不可以~', 5000);
return '';
};

$(document).on('copy', ()=>{
showMessage('你都复制了些什么呀,转载要记得加上出处哦', 5000);
});

$.ajax({
cache: true,
url: "path/to/tips.json",
dataType: "json",
success: function (result){
$.each(result.mouseover, function (index, tips){
$(document).on("mouseover", tips.selector, function (){
var text = tips.text;
if(Array.isArray(tips.text)) text = tips.text[Math.floor(Math.random() * tips.text.length + 1)-1];
text = text.render({text: $(this).text()});
showMessage(text, 3000);
});
});
$.each(result.click, function (index, tips){
$(document).on("click", tips.selector, function (){
var text = tips.text;
if(Array.isArray(tips.text)) text = tips.text[Math.floor(Math.random() * tips.text.length + 1)-1];
text = text.render({text: $(this).text()});
showMessage(text, 3000);
});
});
}
});

(function (){
var text;
if(document.referrer !== ''){
var referrer = document.createElement('a');
referrer.href = document.referrer;
text = '好耶! 是来自 <span style="color:#0099cc;">' + referrer.hostname + '</span> 的朋友~';
var domain = referrer.hostname.split('.')[1];
if (domain == 'baidu') {
text = '好耶! 来自 百度 的朋友~<br>你是搜索 <span style="color:#0099cc;">' + referrer.search.split('&wd=')[1].split('&')[0] + '</span> 找到的我吗?';
}else if (domain == 'so') {
text = '好耶! 来自 360 的朋友~<br>你是搜索 <span style="color:#0099cc;">' + referrer.search.split('&q=')[1].split('&')[0] + '</span> 找到的我吗?';
}else if (domain == 'google') {
text = '好耶! 来自 咕咕噜 的朋友~<br>欢迎阅读<span style="color:#0099cc;">『' + document.title.split(' - ')[0] + '』</span>';
}
} else {
if (window.location.href == window.location.protocol+'//'+window.location.host+'/') { //如果是主页
var now = (new Date()).getHours();
if (now > 1 && now <= 5) {
text = '出现了一只野生的夜猫子欸!别忘了及时休息哦~';
} else if (now > 5 && now <= 7) {
text = '早上好!一日之计在于晨,美好的一天就要开始了';
} else if (now > 7 && now <= 11) {
text = '上午好!工作顺利嘛,不要久坐,多起来走动走动哦!';
} else if (now > 11 && now <= 14) {
text = '中午了,工作了一个上午,现在是午餐时间!';
} else if (now > 14 && now <= 17) {
text = '午后很容易犯困呢,今天的运动目标完成了吗?';
} else if (now > 17 && now <= 19) {
text = '傍晚到啦!工作了一天,记得及时放松一下哦~';
} else if (now > 19 && now <= 21) {
text = '晚上好,今天过得怎么样?';
} else if (now > 21 || now <= 1) {
text = '已经这么晚了呀,早点休息吧,おやすい~';
} else {
text = '喵~喵~喵~';
}
} else {
text = '欢迎阅读<span style="color:#0099cc;">『' + document.title.split(' - ')[0] + '』</span>';
}
}
showMessage(text, 6000);
})();

function showMessage(text, timeout){
if(Array.isArray(text)) text = text[Math.floor(Math.random() * text.length + 1)-1];
$('.waifu-tips').stop();
$('.waifu-tips').html(text).fadeTo(200, 1);
if (timeout === null) timeout = 5000;
hideMessage(timeout);
}
function hideMessage(timeout){
$('.waifu-tips').stop().css('opacity',1);
if (timeout === null) timeout = 5000;
$('.waifu-tips').delay(timeout).fadeTo(200, 0);
}

引入交互模块waifu-tips.json:

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
{
"mouseover": [
{
"selector": ".container a[href^='http']",
"text": ["要看看 <span style=\"color:#0099cc;\">{text}</span> 喵?"]
},
{
"selector": ".fui-home",
"text": ["点击前往首页,想回到上一页可以使用浏览器的后退功能哦"]
},
{
"selector": "#tor_show",
"text": ["翻页比较麻烦吗,点击可以显示这篇文章的目录呢"]
},
{
"selector": "#comment_go,.fui-chat",
"text": ["想要去评论些什么吗?"]
},
{
"selector": "#night_mode",
"text": ["深夜时要爱护眼睛呀"]
},
{
"selector": "#qrcode",
"text": ["手机扫一下就能继续看,很方便呢"]
},
{
"selector": ".comment_reply",
"text": ["有什么想说的喵"]
},
{
"selector": ".gotop-btn",
"text": ["回到开始的地方吧"]
},
{
"selector": "#author",
"text": ["该怎么称呼你呢"]
},
{
"selector": "#mail",
"text": ["留下你的邮箱,不然就联系不上啦"]
},
{
"selector": "#url",
"text": ["你的家在哪里呢,好让我去参观参观呀"]
},
{
"selector": ".textarea-wrapper",
"text": ["要认真填写呢,不可以生产垃圾信息哦"]
},
{
"selector": ".OwO-logo",
"text": ["要插入一个表情吗"]
},
{
"selector": ".post-action__button",
"text": ["要提交了喵~"]
},
{
"selector": "a[rel=gallery]",
"text": ["点击图片可以放大呢"]
},
{
"selector": "input[name=s]",
"text": ["找不到想看的内容?搜索看看吧"]
},
{
"selector": ".pagination .prev",
"text": ["去上一页看看吧"]
},
{
"selector": ".pagination .next",
"text": ["去下一页看看吧"]
},
{
"selector": ".aplayer-play, .aplayer-icon-play",
"text": ["想要听点音乐吗"]
},
{
"selector": ".aplayer-bar-wrap",
"text": ["在这里可以调整<span style=\"color:#0099cc;\">播放进度</span>呢"]
},
{
"selector": ".aplayer-volume-bar-wrap",
"text": ["在这里可以调整<span style=\"color:#0099cc;\">音量</span>呢"]
},
{
"selector": ".aplayer-icon-menu",
"text": ["<span style=\"color:#0099cc;\">播放列表</span>里都有什么呢"]
},
{
"selector": ".aplayer-icon-lrc",
"text": ["有<span style=\"color:#0099cc;\">歌词</span>的话就能跟着一起唱呢"]
},
{
"selector": ".waifu #live2d",
"text": ["干嘛呢你,快把手拿开呀", "鼠…鼠标放错地方了!"]
}
],
"click": [
{
"selector": ".waifu #live2d",
"text": ["是…是不小心碰到了吧", "萝莉控是什么呀", "你看到我的小熊了吗", "再摸的话我可要报警了!⌇●﹏●⌇", "110吗,这里有个变态一直在摸我(ó﹏ò。)"]
}
]
}

于页面底部引入如下的容器与加载代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 容器 -->
<div class="waifu">
<div class="waifu-tips"></div>
<canvas id="live2d" width="200" height="400" class="live2d"></canvas>
<div class="waifu-tool">
<a href="/"><span class="fa fa-home"></span></a>
<a onclick="l2dToComment()"><span class="fa fa-comments"></span></a>
<a onclick="l2dCapture()"><span class="fa fa-camera"></span></a>
<a href="https://candinya.com/live-2d"><span class="fa fa-info-circle"></span></a>
<a onclick="l2dClose()"><span class="fa fa-close"></span></a>
</div>
</div>

<!-- 代码 -->
<script defer src="/js/l2d/tips.js"></script>

<script type="text/javascript">
loadlive2d("live2d", "path/to/model.json");
</script>

如果没问题的话,萌萌哒看板娘应该就能成功出现啦!至于一些细节问题(~~例如拍照时出现PNG格式冲突),会在后续更新的时候继续完善的说~~~解决了,是pjax判定的问题