Skip to content

Commit 53bf0e2

Browse files
committed
fix(twikoo): 彻底修复 Docsify SPA 多评论区叠加问题
- 新增 destroyTwikoo() 统一销毁函数管理清理逻辑 - 在 hook.beforeEach 路由切换前提前销毁旧评论实例 - ensureTcomment() 改为创建前先清理所有旧容器 - doneEach 中延迟 50ms 创建新容器确保 DOM 更新 - 移除 loadTwikoo/observeAndLoad 中多余的容器创建调用 - 增强 token 机制防止快速切换页面时的竞态条件
1 parent 6b1807e commit 53bf0e2

File tree

2 files changed

+73
-26
lines changed

2 files changed

+73
-26
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## 2026-02-03
4+
- twikoo:彻底修复 Docsify SPA 页面切换时多评论区叠加问题
5+
- twikoo:新增 `destroyTwikoo()` 统一销毁函数,管理定时器、观察者和 DOM 清理
6+
- twikoo:在 `hook.beforeEach` 路由切换前提前销毁旧评论实例
7+
- twikoo:`ensureTcomment()` 改为每次创建前先清理所有旧容器
8+
- twikoo:`doneEach` 中延迟 50ms 创建新容器,确保 DOM 更新完成
9+
- twikoo:移除 `loadTwikoo``observeAndLoad` 中多余的 `ensureTcomment()` 调用
10+
- twikoo:增强 token 机制防止快速切换页面时的竞态条件
11+
312
## 2026-02-02
413
- docsify-last-modified:新增“优先 HEAD、GitHub 备用”的策略与配置项(preferHead)
514
- 同步要求:腾讯云 COS 需开启 CORS,并在 Expose Headers 中加入 `Last-Modified`

docs/index.html

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -226,22 +226,49 @@
226226
window.ViewImage && ViewImage.init('.ebook img,a.zoom');
227227
});
228228
function ensureTcomment() {
229-
var tcomment = document.getElementById('tcomment');
230-
if (tcomment) return tcomment;
229+
// 先确保只有一个 tcomment 容器
230+
removeAllTcomment();
231231
var a = Docsify.dom;
232232
var n = a.create("div");
233233
n.id = "tcomment";
234-
a.appendTo(a.find(".content"), n);
234+
var content = a.find(".content");
235+
if (content) {
236+
a.appendTo(content, n);
237+
}
235238
return n;
236239
}
237240

238241
function removeAllTcomment() {
242+
// 移除所有 #tcomment 容器
239243
var nodes = document.querySelectorAll('#tcomment');
240244
for (var i = 0; i < nodes.length; i++) {
241245
if (nodes[i] && nodes[i].parentNode) {
242246
nodes[i].parentNode.removeChild(nodes[i]);
243247
}
244248
}
249+
// 同时移除 Twikoo 可能创建的弹窗/遮罩等全局元素
250+
var tkModals = document.querySelectorAll('.tk-lightbox, .tk-action-icon__dialog, .tk-image-viewer');
251+
for (var j = 0; j < tkModals.length; j++) {
252+
if (tkModals[j] && tkModals[j].parentNode) {
253+
tkModals[j].parentNode.removeChild(tkModals[j]);
254+
}
255+
}
256+
}
257+
258+
// 全局销毁函数,用于页面切换前彻底清理
259+
function destroyTwikoo() {
260+
// 停止所有定时器和观察者
261+
if (twikooTimer) {
262+
clearTimeout(twikooTimer);
263+
twikooTimer = null;
264+
}
265+
if (twikooObserver) {
266+
twikooObserver.disconnect();
267+
twikooObserver = null;
268+
}
269+
twikooLoading = false;
270+
// 彻底移除评论容器
271+
removeAllTcomment();
245272
}
246273

247274
function normalizeCommentPath(path) {
@@ -257,7 +284,13 @@
257284

258285
// 动态插入 div#tcomment 的 dom
259286
hook.mounted(function () {
260-
ensureTcomment();
287+
// 初始化时不创建,等 doneEach 再创建
288+
})
289+
290+
// 页面开始切换时(路由变化前)清理
291+
hook.beforeEach(function(content) {
292+
destroyTwikoo();
293+
return content;
261294
})
262295

263296
// twikoo 初始化控制变量
@@ -270,11 +303,14 @@
270303
function loadTwikoo(path, token) {
271304
if (token !== twikooToken) return;
272305
if (twikooLoading) return;
306+
307+
// 检查是否已有评论容器
308+
var tcomment = document.getElementById('tcomment');
309+
if (!tcomment) return;
310+
273311
twikooLoading = true;
274-
var tcomment = ensureTcomment();
275-
if (tcomment) {
276-
tcomment.innerHTML = '';
277-
}
312+
tcomment.innerHTML = '';
313+
278314
twikoo.init({
279315
envId: 'https://twikooeo.sicnuwiki.com',
280316
el: '#tcomment',
@@ -286,11 +322,6 @@
286322
}).then(function () {
287323
if (token !== twikooToken) return;
288324
twikooLoading = false;
289-
// 可选:强制刷新一次,确保切换页面后评论更新
290-
setTimeout(function () {
291-
var tkIcon = document.getElementsByClassName("tk-icon")[0];
292-
if (tkIcon) tkIcon.click();
293-
}, 200);
294325
}).catch(function () {
295326
twikooLoading = false;
296327
});
@@ -306,7 +337,7 @@
306337
twikooTimer = null;
307338
}
308339

309-
var tcomment = ensureTcomment();
340+
var tcomment = document.getElementById('tcomment');
310341
if (!tcomment) return;
311342

312343
if (!('IntersectionObserver' in window)) {
@@ -334,27 +365,34 @@
334365

335366
// 调用 twikoo 评论,正则 path,刷新当前评论
336367
hook.doneEach(function () {
368+
// ★★★ 第一步:立即彻底销毁旧的 Twikoo 实例和容器 ★★★
369+
destroyTwikoo();
370+
337371
// 获取当前 docsify 页面路径作为评论区分标识
338372
var currentPath = normalizeCommentPath(vm.route.path || location.hash.replace(/^#/, '').replace(/\?.*$/, '') || '/');
339373

340-
// 如果路径相同,不重复初始化
341-
if (lastPath === currentPath) {
342-
return;
343-
}
374+
// 更新路径记录
344375
lastPath = currentPath;
345376

346377
// 切换页面时更新 token,避免旧请求回流渲染
347378
twikooToken += 1;
379+
var currentToken = twikooToken;
348380
twikooLoading = false;
349381

350-
// 先清理旧容器,避免多个评论区叠加
351-
removeAllTcomment();
352-
var tcomment = ensureTcomment();
353-
if (tcomment) {
354-
tcomment.innerHTML = '';
355-
}
356-
357-
observeAndLoad(currentPath, twikooToken);
382+
// ★★★ 第二步:延迟创建新的评论容器,确保 DOM 已更新 ★★★
383+
setTimeout(function() {
384+
// 检查 token 是否仍然有效(防止快速切换页面时的竞态条件)
385+
if (currentToken !== twikooToken) return;
386+
387+
// 再次清理,确保没有残留
388+
removeAllTcomment();
389+
390+
// 创建新的评论容器
391+
var tcomment = ensureTcomment();
392+
if (tcomment) {
393+
observeAndLoad(currentPath, currentToken);
394+
}
395+
}, 50);
358396
})
359397
}
360398
]

0 commit comments

Comments
 (0)