本教程适用于基于Twikoo的评论系统

前言

美化之前

美化的时候需要修改网站的源文件、添加样式、js之类的,需要一点基础,可以参考

在添加完js、css后,一定要记得在_config.butterfly.ymlinject里引用

效果

image-20240417132427375

参考链接

修改方案

1. 获取TwikooTwikooUrl

仅提供Vercel部署的获取方式

腾讯云部署方式请参考Butterfly主题的留言弹幕界面增强版(支持Twikoo、Waline、Valine)

开发人员工具->应用程序->本地存储空间->自己的网址->twikoo-access-token里面即可看到对应的值

2. 修改layout.pug

修改[blogRoot]\butterfly\layout\includes\layout.pug

1
2
3
4
5
6
7
8
9
10
11
        ...
main#content-inner.layout(class=hideAside)
if body
div!= body
else
block content
if theme.aside.enable && page.aside !== false
include widget/index.pug

+ .comment-barrage
...
小改动

因为我的侧栏aside在左边,所以弹幕会和文章页有重合

又因为原本的教程里,#post.comment-barrage的祖先,所以当我hover弹幕.comment-barrage元素的时候,会触发祖先#posthover效果,这样会显得怪怪的,然后我想着调整一下,看有没有办法不这样

.comment-barrage-item元素上添加一个事件处理程序,阻止事件冒泡到其祖先元素(包括post元素)上。修改popCommentBarrage函数,为新创建的barrage元素添加事件处理程序来阻止事件冒泡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function popCommentBarrage(data) {
let barrage = document.createElement('div');
let width = commentBarrageConfig.dom.clientWidth;
let height = commentBarrageConfig.dom.clientHeight;
barrage.className = 'comment-barrage-item';
barrage.innerHTML = `
<div class="barrageHead">
<a class="barrageTitle">热评</a>
<div class="barrageNick">${data.nick}</div>
<img class="barrageAvatar" src="https://cravatar.cn/avatar/${data.mailMd5}"/>
<a class="comment-barrage-close" href="javascript:hotreview.switchCommentBarrage()"><i class="iconfont icat-close"></i></a>
</div>
<a class="barrageContent" href="javascript:hotreview.scrollTo('${data.id}');">${data.comment}</a>
`;

// 阻止事件冒泡到祖先元素
barrage.addEventListener('mouseenter', function(event) {
event.stopPropagation();
});

commentBarrageConfig.barrageTimer.push(barrage);
commentBarrageConfig.dom.append(barrage);
}

用这种方法,可以监听到,但是阻止不了,我感觉像是hover并不是一个事件,阻止不了

css 子元素hover时不触发父元素hover - 珞珞9527 - 博客园 (cnblogs.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function popCommentBarrage(data) {
let barrage = document.createElement('div');
let width = commentBarrageConfig.dom.clientWidth;
let height = commentBarrageConfig.dom.clientHeight;
barrage.className = 'comment-barrage-item';
barrage.innerHTML = `
<div class="barrageHead">
<a class="barrageTitle">热评</a>
<div class="barrageNick">${data.nick}</div>
<img class="barrageAvatar" src="https://cravatar.cn/avatar/${data.mailMd5}"/>
<a class="comment-barrage-close" href="javascript:hotreview.switchCommentBarrage()"><i class="iconfont icat-close"></i></a>
</div>
<a class="barrageContent" href="javascript:hotreview.scrollTo('${data.id}');">${data.comment}</a>
`;

// 创建遮罩层
let mask = document.createElement('div');
mask.className = 'barrage-mask';
barrage.appendChild(mask);

commentBarrageConfig.barrageTimer.push(barrage);
commentBarrageConfig.dom.append(barrage);
}
1
2
3
4
5
6
7
8
9
.barrage-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: transparent;
pointer-events: none; /* 避免遮挡下方元素的鼠标事件 */
}

问了chat,感觉没啥用,就没有细去研究😓


后面和鹊楠讨论的时候,给了个思路,既然#post.comment-barrage的祖先,而且参考了其他佬的源码(#post并不是.comment-barrage的祖先)那就干脆把.comment-barrage移到#post外面,所以就有了上面的方案

3. 创建barrage.jscommentBarrage.js

barrage.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
// 热评弹窗跳转
var hotreview = {
switchCommentBarrage: function() {
var commentBarrage = document.querySelector(".comment-barrage");
if (commentBarrage) {
if ($(commentBarrage).is(":visible")) {
$(commentBarrage).hide();
$(".menu-commentBarrage-text").text("显示热评");
localStorage.setItem("commentBarrageSwitch", "false");
} else if ($(commentBarrage).is(":hidden")) {
$(commentBarrage).show();
$(".menu-commentBarrage-text").text("关闭热评");
localStorage.removeItem("commentBarrageSwitch");
}
}
},
scrollTo: function (e) {
const t = document.getElementById(e);
if (t) {
const e = t.getBoundingClientRect().top + window.pageYOffset - 80,
o = window.pageYOffset,
n = e - o;
let a = null;
window.requestAnimationFrame((function e(t) {
a || (a = t);
const i = t - a,
l = (c = Math.min(i / 0, 1)) < .5 ? 2 * c * c : (4 - 2 * c) * c - 1;
var c;
window.scrollTo(0, o + n * l),
i < 600 && window.requestAnimationFrame(e)
}))
}
}
,
}

commentBarrage.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
var postCommentElement = document.getElementById("post-comment");
if (postCommentElement) {
var commentBarrageConfig = {
// 同时最多显示弹幕数
maxBarrage: 1,
// 初始弹幕显示时间ms
initialBarrageTime: 500,
// 弹幕显示间隔时间ms
barrageTime: 4000,
// twikoo部署地址腾讯云的为环境ID
twikooUrl: "{envId}",
// token获取见上方
accessToken: "{YOUR_TOKEN}",
pageUrl: window.location.pathname,
barrageTimer: [],
barrageList: [],
barrageIndex: 0,
dom: document.querySelector('.comment-barrage'),
};

var commentInterval = null;
var hoverOnCommentBarrage = false;

$(".comment-barrage").hover(function() {
hoverOnCommentBarrage = true;
console.log("热评悬浮");
}, function() {
hoverOnCommentBarrage = false;
console.log("停止悬浮");
});

function initCommentBarrage(){
// console.log("开始创建热评")

var data = JSON.stringify({
"event": "COMMENT_GET",
"commentBarrageConfig.accessToken": commentBarrageConfig.accessToken,
"url": commentBarrageConfig.pageUrl
});
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function() {
if(this.readyState === 4) {
commentBarrageConfig.barrageList = commentLinkFilter(JSON.parse(this.responseText).data);
commentBarrageConfig.dom.innerHTML = '';
}
});
xhr.open("POST", commentBarrageConfig.twikooUrl);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(data);


clearInterval(commentInterval);
commentInterval = null;

commentInterval = setInterval(() => {
if (commentBarrageConfig.barrageList.length && !hoverOnCommentBarrage) {
popCommentBarrage(commentBarrageConfig.barrageList[commentBarrageConfig.barrageIndex]);
commentBarrageConfig.barrageIndex += 1;
commentBarrageConfig.barrageIndex %= commentBarrageConfig.barrageList.length;
}
if ((commentBarrageConfig.barrageTimer.length > (commentBarrageConfig.barrageList.length > commentBarrageConfig.maxBarrage ? commentBarrageConfig.maxBarrage : commentBarrageConfig.barrageList.length)) && !hoverOnCommentBarrage) {
removeCommentBarrage(commentBarrageConfig.barrageTimer.shift())
}
}, commentBarrageConfig.initialBarrageTime);
}

function commentLinkFilter(data) {
data.sort((a, b) => {
return a.created - b.created;
});
let newData = [];
data.forEach(item => {
newData.push(...getCommentReplies(item));
});
return newData;
}

function getCommentReplies(item) {
if (item.replies) {
let replies = [item];
item.replies.forEach(item => {
replies.push(...getCommentReplies(item));
});
return replies;
} else {
return [];
}
}

function popCommentBarrage(data) {
let barrage = document.createElement('div');
let width = commentBarrageConfig.dom.clientWidth;
let height = commentBarrageConfig.dom.clientHeight;
barrage.className = 'comment-barrage-item';
barrage.innerHTML = `
<div class="barrageHead">
<a class="barrageTitle">热评</a>
<div class="barrageNick">${data.nick}</div>
<img class="barrageAvatar" src="https://cravatar.cn/avatar/${data.mailMd5}"/>
<a class="comment-barrage-close" href="javascript:hotreview.switchCommentBarrage()"><i class="iconfont icat-close"></i></a>
</div>
<a class="barrageContent" href="javascript:hotreview.scrollTo('${data.id}');">${data.comment}</a>
`;
commentBarrageConfig.barrageTimer.push(barrage);
commentBarrageConfig.dom.append(barrage);

clearInterval(commentInterval);
commentInterval = setInterval(() => {
if (commentBarrageConfig.barrageList.length && !hoverOnCommentBarrage) {
popCommentBarrage(commentBarrageConfig.barrageList[commentBarrageConfig.barrageIndex]);
commentBarrageConfig.barrageIndex += 1;
commentBarrageConfig.barrageIndex %= commentBarrageConfig.barrageList.length;
}
if ((commentBarrageConfig.barrageTimer.length > (commentBarrageConfig.barrageList.length > commentBarrageConfig.maxBarrage ? commentBarrageConfig.maxBarrage : commentBarrageConfig.barrageList.length)) && !hoverOnCommentBarrage) {
removeCommentBarrage(commentBarrageConfig.barrageTimer.shift())
}
}, commentBarrageConfig.barrageTime);
}

function removeCommentBarrage(barrage) {
barrage.className = 'comment-barrage-item out';
setTimeout(() => {
commentBarrageConfig.dom.removeChild(barrage);
}, 1000);
}

// 自动隐藏
document.addEventListener('scroll', btf.throttle(function() {
//滚动条高度+视窗高度 = 可见区域底部高度
var visibleBottom = window.scrollY + document.documentElement.clientHeight;
//可见区域顶部高度
var visibleTop = window.scrollY;
// 获取翻页按钮容器
var pagination = document.querySelector('.comment-barrage');
// 获取位置监测容器,此处采用评论区
var eventlistner = document.getElementById('post-comment');
if (eventlistner && pagination) {
var centerY = eventlistner.offsetTop + (eventlistner.offsetHeight / 2);
if (document.body.clientWidth > 768) {
if (centerY > visibleBottom) {
pagination.style.bottom = '40px';
} else {
pagination.style.bottom = '-200px';
}
}
}
}, 200))

initCommentBarrage();

document.addEventListener('pjax:send', function() {
clearInterval(commentInterval);
});
}

4. 创建barrage.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
/* 热评弹窗样式 */
.comment-barrage {
position: fixed;
bottom: 40px;
right: 68px;
display: flex;
flex-direction: column;
justify-content: end;
align-items: flex-end;
z-index: 999;
transition: 0.3s;
user-select: none;
-webkit-user-select: none;
}

@media screen and (max-width: 768px){
.comment-barrage {
display: none!important;
}
}
.comment-barrage-item {
min-width: 286px;
max-width: 286px;
width: fit-content;
min-height: 80px;
max-height: 150px;
margin: 4px;
padding: 8px 14px;
background: var(--june-maskbgdeep);
border-radius: 8px;
color: var(--june-fontcolor);
animation: barrageIn 0.6s cubic-bezier(0.42, 0, 0.3, 1.11);
transition: 0.3s;
display: flex;
flex-direction: column;
border: var(--style-border-always);
backdrop-filter: saturate(180%) blur(20px);
-webkit-backdrop-filter: blur(20px);
position: fixed;
box-shadow: var(--june-shadow-border);
overflow: hidden;
}

.comment-barrage-item:hover {
border: 1px solid var(--june-border);
box-shadow: var(--june-shadow-black);
}

.comment-barrage-item.out{
opacity: 0;
animation: barrageOut 0.6s cubic-bezier(0.42, 0, 0.3, 1.11);
}

.comment-barrage-item.hovered {
opacity: 0;
}

.comment-barrage-item .comment-barrage-close {
color: var(--june-secondtext);
cursor: pointer;
line-height: 1;
margin: 4px;
}

.comment-barrage-item .comment-barrage-close .junefont {
font-size: 18px!important;
}

.comment-barrage-item pre {
display: none;
}

.comment-barrage-item li {
display: none;
}

.comment-barrage-item p img:not(.tk-owo-emotion) {
display: none;
}

.comment-barrage-item p img.tk-owo-emotion {
width: 16px;
padding: 0;
margin: 0;
transform: translateY(2px);
}

.comment-barrage-item blockquote {
display: none;
}

.comment-barrage-item br {
display: none;
}

.comment-barrage-item .barrageHead{
height: 30px;
padding: 0;
line-height: 30px;
font-size: 12px;
border-bottom: var(--style-border);
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
padding-bottom: 6px;
}

.comment-barrage-item .barrageHead .barrageTitle {
background: var(--june);
color: var(--june-white);
margin-right: 8px;
line-height: 1;
padding: 4px;
border-radius: 4px;
white-space:nowrap;
}

.comment-barrage-item .barrageAvatar{
width: 18px;
height: 18px;
margin: 0;
margin-left: auto;
margin-right: 8px;
border-radius: 50%;
background: var(--june-secondbg);
cursor: url(https://img.june-pj.cn/img/default.cur), default;
}
.comment-barrage-item .barrageContent{
font-size: 14px!important;
font-weight: normal!important;
height: calc(100% - 30px);
overflow: hidden;
width: fit-content;
max-height: 48px;
}

.comment-barrage-item .barrageContent a {
pointer-events:none;
font-size: 14px!important;
}

.comment-barrage-item .barrageContent::-webkit-scrollbar{
height: 0;
width: 4px;
}
.comment-barrage-item .barrageContent::-webkit-scrollbar-button{
display: none;
}

.barrageHead .comment-barrage-close i {
color: var(--june-fontcolor);
}

.comment-barrage-item p{
color: var(--june-fontcolor);
margin: 8px 0 0;
line-height: 1.2;
-webkit-line-clamp: 2;
display: -webkit-box;
-webkit-box-orient: vertical;
font-size: 12px;
font-weight: bold;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
}
.comment-barrage-item p:hover,
.barrageHead .comment-barrage-close i:hover {
color: var(--june);
}

5. 修改rightmenu.pug

修改 [blogRoot]\themes\butterfly\layout\includes\rightmenu.pug

1
2
3
4
5
6
7
8
9
    ...
.rightMenu-group.rightMenu-line.hide#menu-post
a.rightMenu-item(href="#post-comment")
i.fas.fa-comment
span='空降评论'
+ a.rightMenu-item(href="javascript:hotreview.switchCommentBarrage()")
+ i.iconfont.icon-danmu
+ span.menu-commentBarrage-text 关闭热评
...