hexo-theme-linen 1.0.27 → 1.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -10,9 +10,6 @@
|
|
|
10
10
|
}
|
|
11
11
|
.nav-header.is-post {
|
|
12
12
|
position: fixed;
|
|
13
|
-
.header-inner {
|
|
14
|
-
padding: 0 $commonPadding;
|
|
15
|
-
}
|
|
16
13
|
}
|
|
17
14
|
.nav-header.is-post.hide {
|
|
18
15
|
transform: translateY(-100%);
|
|
@@ -42,6 +39,7 @@
|
|
|
42
39
|
border-right: none;
|
|
43
40
|
-ms-overflow-style: unset;
|
|
44
41
|
scrollbar-width: unset;
|
|
42
|
+
overscroll-behavior: contain;
|
|
45
43
|
@include safe-padding(15px);
|
|
46
44
|
.toc-item-wrap {
|
|
47
45
|
transition: unset;
|
|
@@ -41,6 +41,9 @@ html {
|
|
|
41
41
|
min-width: 320px;
|
|
42
42
|
scroll-behavior: smooth;
|
|
43
43
|
overscroll-behavior: auto none;
|
|
44
|
+
&.no-scroll {
|
|
45
|
+
overflow: hidden !important;
|
|
46
|
+
}
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
* {
|
|
@@ -217,6 +220,9 @@ body {
|
|
|
217
220
|
display: block;
|
|
218
221
|
}
|
|
219
222
|
}
|
|
223
|
+
&.no-scroll {
|
|
224
|
+
overflow: hidden !important;
|
|
225
|
+
}
|
|
220
226
|
}
|
|
221
227
|
* {
|
|
222
228
|
box-sizing: border-box;
|
|
@@ -233,7 +239,8 @@ body {
|
|
|
233
239
|
background-color: #fff;
|
|
234
240
|
// backdrop-filter: saturate(180%) blur(20px);
|
|
235
241
|
transform: translateY(0);
|
|
236
|
-
transition: transform 0.
|
|
242
|
+
transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
|
|
243
|
+
will-change: transform;
|
|
237
244
|
z-index: 9;
|
|
238
245
|
position: relative;
|
|
239
246
|
top: 0;
|
|
@@ -362,7 +369,7 @@ body {
|
|
|
362
369
|
}
|
|
363
370
|
}
|
|
364
371
|
&.hide {
|
|
365
|
-
transform: translateY(
|
|
372
|
+
transform: translateY(-100%);
|
|
366
373
|
}
|
|
367
374
|
}
|
|
368
375
|
|
|
@@ -1830,6 +1837,7 @@ body {
|
|
|
1830
1837
|
transition: transform cubic-bezier(0.4, 0, 0.6, 1) 0.3s;
|
|
1831
1838
|
display: flex;
|
|
1832
1839
|
flex-direction: column;
|
|
1840
|
+
overscroll-behavior: contain;
|
|
1833
1841
|
@include safe-padding(15px);
|
|
1834
1842
|
&.visible {
|
|
1835
1843
|
transform: translateY(0);
|
|
@@ -2594,6 +2602,7 @@ body {
|
|
|
2594
2602
|
&.visible {
|
|
2595
2603
|
pointer-events: all;
|
|
2596
2604
|
opacity: 1;
|
|
2605
|
+
touch-action: none;
|
|
2597
2606
|
}
|
|
2598
2607
|
}
|
|
2599
2608
|
|
|
@@ -270,7 +270,8 @@ function onScroll() {
|
|
|
270
270
|
var header = document.querySelector(".nav-header");
|
|
271
271
|
if (!header) return;
|
|
272
272
|
|
|
273
|
-
var
|
|
273
|
+
var rawScrollY = window.scrollY;
|
|
274
|
+
var currentScrollY = Math.max(0, rawScrollY);
|
|
274
275
|
var triggerHeight = window.innerHeight * 1.5;
|
|
275
276
|
|
|
276
277
|
if (!window.$gitalkInitiated && currentScrollY > window.innerHeight) {
|
|
@@ -283,27 +284,32 @@ function onScroll() {
|
|
|
283
284
|
return;
|
|
284
285
|
}
|
|
285
286
|
|
|
286
|
-
|
|
287
|
+
// 接近顶部时(<= 60px),强制展开并退出,消除 iOS/Mac 橡皮筋回弹误判与闪烁
|
|
288
|
+
if (currentScrollY <= 60) {
|
|
287
289
|
header.classList.remove("hide");
|
|
290
|
+
if (backToTop) backToTop.classList.remove("visible");
|
|
288
291
|
previousScrollY = currentScrollY;
|
|
289
292
|
return;
|
|
290
293
|
}
|
|
291
294
|
|
|
292
295
|
var scrollDelta = currentScrollY - previousScrollY;
|
|
293
|
-
|
|
296
|
+
// 滤除手指微小抖动
|
|
297
|
+
if (Math.abs(scrollDelta) < 8) {
|
|
294
298
|
return;
|
|
295
299
|
}
|
|
296
300
|
|
|
297
301
|
if (scrollDelta < 0) {
|
|
302
|
+
// 向上滑动:即刻平滑展开导航栏
|
|
298
303
|
if (!document.querySelector(".pswp--open")) {
|
|
299
304
|
header.classList.remove("hide");
|
|
300
305
|
}
|
|
301
|
-
if (currentScrollY >= triggerHeight) {
|
|
306
|
+
if (backToTop && currentScrollY >= triggerHeight) {
|
|
302
307
|
backToTop.classList.add("visible");
|
|
303
308
|
}
|
|
304
309
|
} else {
|
|
305
|
-
|
|
306
|
-
if (
|
|
310
|
+
// 向下滑动:离开顶部 60% 视口后平滑收起导航栏
|
|
311
|
+
if (backToTop) backToTop.classList.remove("visible");
|
|
312
|
+
if (currentScrollY > window.innerHeight * 0.6) {
|
|
307
313
|
header.classList.add("hide");
|
|
308
314
|
}
|
|
309
315
|
}
|
|
@@ -311,7 +317,20 @@ function onScroll() {
|
|
|
311
317
|
previousScrollY = currentScrollY;
|
|
312
318
|
}
|
|
313
319
|
|
|
314
|
-
|
|
320
|
+
var isScrollTicking = false;
|
|
321
|
+
window.addEventListener(
|
|
322
|
+
"scroll",
|
|
323
|
+
function () {
|
|
324
|
+
if (!isScrollTicking) {
|
|
325
|
+
isScrollTicking = true;
|
|
326
|
+
window.requestAnimationFrame(function () {
|
|
327
|
+
onScroll();
|
|
328
|
+
isScrollTicking = false;
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
{ passive: true }
|
|
333
|
+
);
|
|
315
334
|
|
|
316
335
|
var resizeTimer = null;
|
|
317
336
|
window.addEventListener("resize", function () {
|
|
@@ -339,6 +358,18 @@ function onclickPostItem(element) {
|
|
|
339
358
|
}
|
|
340
359
|
}
|
|
341
360
|
|
|
361
|
+
function setBodyScrollLocked(locked) {
|
|
362
|
+
var body = document.body;
|
|
363
|
+
var html = document.documentElement;
|
|
364
|
+
if (locked) {
|
|
365
|
+
body && body.classList.add("no-scroll");
|
|
366
|
+
html && html.classList.add("no-scroll");
|
|
367
|
+
} else {
|
|
368
|
+
body && body.classList.remove("no-scroll");
|
|
369
|
+
html && html.classList.remove("no-scroll");
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
342
373
|
function handleClick(e) {
|
|
343
374
|
try {
|
|
344
375
|
var target = e.target;
|
|
@@ -358,7 +389,7 @@ function handleClick(e) {
|
|
|
358
389
|
if (/close-series-popup-icon/.test(target.className)) {
|
|
359
390
|
seriesElement?.classList?.remove("visible");
|
|
360
391
|
maskElement.classList.remove("visible");
|
|
361
|
-
|
|
392
|
+
setBodyScrollLocked(false);
|
|
362
393
|
} else if (["H2", "H3"].includes(target.tagName) && target.id) {
|
|
363
394
|
const newLocation = new URL(location.href);
|
|
364
395
|
newLocation.hash = `#${target.id}`;
|
|
@@ -415,7 +446,7 @@ function handleClick(e) {
|
|
|
415
446
|
if (tocElement?.classList?.contains("visible")) {
|
|
416
447
|
tocElement.classList.remove("visible");
|
|
417
448
|
maskElement.classList.remove("visible");
|
|
418
|
-
|
|
449
|
+
setBodyScrollLocked(false);
|
|
419
450
|
}
|
|
420
451
|
if (targetId === "to-page-top") {
|
|
421
452
|
window.scrollTo({
|
|
@@ -456,11 +487,11 @@ function handleClick(e) {
|
|
|
456
487
|
if (tocElement?.classList?.contains("visible")) {
|
|
457
488
|
tocElement.classList.remove("visible");
|
|
458
489
|
maskElement.classList.remove("visible");
|
|
459
|
-
|
|
490
|
+
setBodyScrollLocked(false);
|
|
460
491
|
} else {
|
|
461
492
|
tocElement.classList.add("visible");
|
|
462
493
|
maskElement.classList.add("visible");
|
|
463
|
-
|
|
494
|
+
setBodyScrollLocked(true);
|
|
464
495
|
}
|
|
465
496
|
return;
|
|
466
497
|
} else if (
|
|
@@ -473,13 +504,13 @@ function handleClick(e) {
|
|
|
473
504
|
seriesElement?.classList?.add("visible");
|
|
474
505
|
maskElement?.classList?.add("visible");
|
|
475
506
|
donateModal?.classList?.remove("visible");
|
|
476
|
-
|
|
507
|
+
setBodyScrollLocked(true);
|
|
477
508
|
return;
|
|
478
509
|
} else if (target?.className?.includes("series-item-link")) {
|
|
479
510
|
e.preventDefault();
|
|
480
511
|
seriesElement?.classList?.remove("visible");
|
|
481
512
|
maskElement.classList.remove("visible");
|
|
482
|
-
|
|
513
|
+
setBodyScrollLocked(false);
|
|
483
514
|
const href =
|
|
484
515
|
target.getAttribute("href") ||
|
|
485
516
|
target.parentElement.getAttribute("href");
|
|
@@ -489,7 +520,7 @@ function handleClick(e) {
|
|
|
489
520
|
tocElement?.classList?.remove("visible");
|
|
490
521
|
seriesElement?.classList?.remove("visible");
|
|
491
522
|
maskElement.classList.remove("visible");
|
|
492
|
-
|
|
523
|
+
setBodyScrollLocked(false);
|
|
493
524
|
return;
|
|
494
525
|
} else if (target?.className?.includes("fold-toggle")) {
|
|
495
526
|
const parentElement = target.parentElement;
|
|
@@ -554,3 +585,23 @@ function initAgeWarning() {
|
|
|
554
585
|
}
|
|
555
586
|
|
|
556
587
|
initAgeWarning();
|
|
588
|
+
|
|
589
|
+
function initMaskScrollLock() {
|
|
590
|
+
var mask = document.getElementById("mask");
|
|
591
|
+
if (!mask) return;
|
|
592
|
+
mask.addEventListener(
|
|
593
|
+
"touchmove",
|
|
594
|
+
function (e) {
|
|
595
|
+
if (mask.classList.contains("visible")) {
|
|
596
|
+
e.preventDefault();
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
{ passive: false }
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (document.readyState === "loading") {
|
|
604
|
+
document.addEventListener("DOMContentLoaded", initMaskScrollLock);
|
|
605
|
+
} else {
|
|
606
|
+
initMaskScrollLock();
|
|
607
|
+
}
|
|
@@ -2,182 +2,212 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
2
2
|
const tocContainer = document.getElementById("toc");
|
|
3
3
|
if (!tocContainer) return;
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
5
|
+
const contentContainer =
|
|
6
|
+
document.getElementById("page-content") ||
|
|
7
|
+
document.getElementById("post") ||
|
|
8
|
+
document.body;
|
|
9
|
+
|
|
10
|
+
const headerElements = Array.from(
|
|
11
|
+
contentContainer.querySelectorAll("h2, h3"),
|
|
12
|
+
);
|
|
13
|
+
if (headerElements.length === 0) return;
|
|
14
|
+
|
|
15
|
+
// 1. 保证所有标题均具备有效 ID(补全缺失的 ID 并写回 DOM)
|
|
16
|
+
headerElements.forEach((header, index) => {
|
|
17
|
+
if (!header.id) {
|
|
18
|
+
const text = (header.textContent || "").trim();
|
|
19
|
+
header.id =
|
|
20
|
+
text.replace(/\s+/g, "-").toLowerCase() || `heading-${index}`;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// 2. 解析 TOC 目录的 DOM 结构建立映射字典
|
|
25
|
+
const tocItemWraps = Array.from(
|
|
26
|
+
tocContainer.querySelectorAll(".toc-item-wrap"),
|
|
27
|
+
);
|
|
28
|
+
const tocItemsMap = new Map(); // h2Id -> { wrap, link, subMap: Map(h3Id -> link) }
|
|
29
|
+
const h3ToH2Map = new Map(); // h3Id -> h2Id
|
|
30
|
+
|
|
31
|
+
tocItemWraps.forEach((wrap) => {
|
|
32
|
+
const h2Link = wrap.querySelector(".toc-item-link");
|
|
33
|
+
if (!h2Link) return;
|
|
34
|
+
const h2Id = h2Link.dataset.id;
|
|
35
|
+
if (!h2Id) return;
|
|
36
|
+
|
|
37
|
+
const subMap = new Map();
|
|
38
|
+
const subLinks = Array.from(wrap.querySelectorAll(".toc-sub-item-link"));
|
|
39
|
+
subLinks.forEach((subLink) => {
|
|
40
|
+
const h3Id = subLink.dataset.id;
|
|
41
|
+
if (h3Id) {
|
|
42
|
+
subMap.set(h3Id, subLink);
|
|
43
|
+
h3ToH2Map.set(h3Id, h2Id);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
tocItemsMap.set(h2Id, {
|
|
48
|
+
wrap,
|
|
49
|
+
link: h2Link,
|
|
50
|
+
subMap,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// 3. 构建正文标题列表
|
|
55
|
+
let currentH2Id = null;
|
|
56
|
+
const headers = headerElements.map((header) => {
|
|
57
|
+
const isH2 = header.tagName === "H2";
|
|
58
|
+
if (isH2) {
|
|
59
|
+
currentH2Id = header.id;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
id: header.id,
|
|
63
|
+
isH2,
|
|
64
|
+
h2Id: isH2 ? header.id : h3ToH2Map.get(header.id) || currentH2Id,
|
|
65
|
+
element: header,
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// 顶部导航栏避让阈值
|
|
70
|
+
const TOP_OFFSET = 80;
|
|
7
71
|
|
|
8
|
-
const toc = [];
|
|
9
|
-
let lastH2 = null;
|
|
10
|
-
let previousH2Id = null;
|
|
11
|
-
let previousH3Id = null;
|
|
12
72
|
let lastActiveH2Id = null;
|
|
13
73
|
let lastActiveH3Id = null;
|
|
14
74
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
75
|
+
// 4. 目录容器内部滚动居中/可见,避免触发外部 window 的异常抖动
|
|
76
|
+
function scrollIntoTocView(element) {
|
|
77
|
+
if (!element || !tocContainer) return;
|
|
78
|
+
const containerRect = tocContainer.getBoundingClientRect();
|
|
79
|
+
const elementRect = element.getBoundingClientRect();
|
|
80
|
+
|
|
81
|
+
if (elementRect.top < containerRect.top) {
|
|
82
|
+
tocContainer.scrollTop -= containerRect.top - elementRect.top + 20;
|
|
83
|
+
} else if (elementRect.bottom > containerRect.bottom) {
|
|
84
|
+
tocContainer.scrollTop += elementRect.bottom - containerRect.bottom + 20;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 5. 计算当前应当激活的 H2 / H3
|
|
89
|
+
function determineActive() {
|
|
90
|
+
// 优先兼容外部锚定参数 window.anchoringId
|
|
91
|
+
const currentAnchoringId =
|
|
92
|
+
typeof window !== "undefined" && "anchoringId" in window
|
|
93
|
+
? window.anchoringId
|
|
94
|
+
: null;
|
|
95
|
+
|
|
96
|
+
if (currentAnchoringId) {
|
|
97
|
+
if (tocItemsMap.has(currentAnchoringId)) {
|
|
98
|
+
return {
|
|
99
|
+
h2Id: currentAnchoringId,
|
|
100
|
+
h3Id: null,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (h3ToH2Map.has(currentAnchoringId)) {
|
|
104
|
+
return {
|
|
105
|
+
h2Id: h3ToH2Map.get(currentAnchoringId),
|
|
106
|
+
h3Id: currentAnchoringId,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 根据滚动位置,查找位于视口顶部触发线(top <= TOP_OFFSET)之上的最后一个标题
|
|
112
|
+
let activeHeading = null;
|
|
113
|
+
for (let i = 0; i < headers.length; i++) {
|
|
114
|
+
const header = headers[i];
|
|
115
|
+
const rect = header.element.getBoundingClientRect();
|
|
116
|
+
if (rect.top <= TOP_OFFSET) {
|
|
117
|
+
activeHeading = header;
|
|
118
|
+
} else {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 若未滚动到任何标题(如文章开头前言),激活第一个 H2
|
|
124
|
+
if (!activeHeading) {
|
|
125
|
+
const firstH2 = headers.find((h) => h.isH2);
|
|
126
|
+
return {
|
|
127
|
+
h2Id: firstH2 ? firstH2.id : null,
|
|
128
|
+
h3Id: null,
|
|
27
129
|
};
|
|
28
|
-
toc.push(lastH2);
|
|
29
|
-
} else if (header.tagName === "H3" && lastH2) {
|
|
30
|
-
lastH2.children.push({
|
|
31
|
-
title,
|
|
32
|
-
id,
|
|
33
|
-
active: false,
|
|
34
|
-
visible: false,
|
|
35
|
-
});
|
|
36
130
|
}
|
|
37
|
-
});
|
|
38
131
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const isVisible = !!entry.intersectionRatio;
|
|
46
|
-
toc.forEach((h2) => {
|
|
47
|
-
if (h2.id === id) {
|
|
48
|
-
h2.visible = isVisible;
|
|
49
|
-
if (isVisible) {
|
|
50
|
-
previousH2Id = h2.id;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
h2.children.forEach((h3) => {
|
|
54
|
-
if (h3.id === id) {
|
|
55
|
-
h3.visible = isVisible;
|
|
56
|
-
if (isVisible) {
|
|
57
|
-
previousH3Id = h3.id;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
toc.forEach((item) => {
|
|
63
|
-
item.passiveVisible =
|
|
64
|
-
(item.children || []).findIndex((h3) => h3.visible) !== -1;
|
|
65
|
-
if (item.passiveVisible) {
|
|
66
|
-
previousH2Id = item.id;
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
});
|
|
132
|
+
if (activeHeading.isH2) {
|
|
133
|
+
return {
|
|
134
|
+
h2Id: activeHeading.id,
|
|
135
|
+
h3Id: null,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
70
138
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
h2.children.forEach((h3) => {
|
|
85
|
-
h3.active = false;
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
});
|
|
139
|
+
return {
|
|
140
|
+
h2Id: activeHeading.h2Id,
|
|
141
|
+
h3Id: activeHeading.id,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 6. 执行高亮更新与 DOM 渲染
|
|
146
|
+
function updateActive() {
|
|
147
|
+
const { h2Id, h3Id } = determineActive();
|
|
148
|
+
|
|
149
|
+
if (lastActiveH2Id === h2Id && lastActiveH3Id === h3Id) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
89
152
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
? item.id === anchoringId
|
|
93
|
-
: item.active,
|
|
94
|
-
);
|
|
153
|
+
lastActiveH2Id = h2Id;
|
|
154
|
+
lastActiveH3Id = h3Id;
|
|
95
155
|
|
|
96
|
-
|
|
97
|
-
let targetH3Id = null;
|
|
156
|
+
let targetElementToScroll = null;
|
|
98
157
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
158
|
+
tocItemsMap.forEach((item, currentH2Id) => {
|
|
159
|
+
const isH2Active = currentH2Id === h2Id;
|
|
160
|
+
if (isH2Active) {
|
|
161
|
+
item.wrap.classList.add("active");
|
|
162
|
+
targetElementToScroll = item.wrap;
|
|
103
163
|
} else {
|
|
104
|
-
|
|
105
|
-
if (isScrollingUp) {
|
|
106
|
-
const previousH2Index = toc.findIndex((h2) => h2.id === previousH2Id);
|
|
107
|
-
const previousH3ParentIndex = toc.findIndex((h2) =>
|
|
108
|
-
h2 && h2.children && h2.children.find((h3) => h3.id === previousH3Id),
|
|
109
|
-
);
|
|
110
|
-
let targetH2Index = 0;
|
|
111
|
-
if (!previousH3Id || previousH3ParentIndex > previousH2Index) {
|
|
112
|
-
targetH2Index = previousH2Index - 1;
|
|
113
|
-
} else {
|
|
114
|
-
targetH2Index = previousH3ParentIndex;
|
|
115
|
-
}
|
|
116
|
-
const newActiveH2 = toc[targetH2Index];
|
|
117
|
-
if (newActiveH2) {
|
|
118
|
-
targetH2Id = newActiveH2.id;
|
|
119
|
-
let activeH3Index = (newActiveH2.children ? newActiveH2.children.length : 0) - 1;
|
|
120
|
-
if (newActiveH2.children && newActiveH2.children.find((h3) => h3.id === previousH3Id)) {
|
|
121
|
-
const previousH3Index = newActiveH2.children.findIndex(
|
|
122
|
-
(h3) => h3.id === previousH3Id,
|
|
123
|
-
);
|
|
124
|
-
activeH3Index =
|
|
125
|
-
previousH3Index - 1 > -1 ? previousH3Index - 1 : 0;
|
|
126
|
-
}
|
|
127
|
-
targetH3Id = (newActiveH2.children && newActiveH2.children[activeH3Index] && newActiveH2.children[activeH3Index].id) || null;
|
|
128
|
-
}
|
|
129
|
-
} else {
|
|
130
|
-
targetH2Id = previousH2Id;
|
|
131
|
-
targetH3Id = previousH3Id;
|
|
132
|
-
}
|
|
164
|
+
item.wrap.classList.remove("active");
|
|
133
165
|
}
|
|
134
166
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
167
|
+
// 仅在当前 H2 为 active 时匹配子项 H3,彻底杜绝跨章节残留
|
|
168
|
+
item.subMap.forEach((subLink, currentH3Id) => {
|
|
169
|
+
if (isH2Active && currentH3Id === h3Id) {
|
|
170
|
+
subLink.classList.add("active");
|
|
171
|
+
targetElementToScroll = subLink;
|
|
172
|
+
} else {
|
|
173
|
+
subLink.classList.remove("active");
|
|
138
174
|
}
|
|
139
|
-
|
|
140
|
-
|
|
175
|
+
});
|
|
176
|
+
});
|
|
141
177
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
178
|
+
if (targetElementToScroll) {
|
|
179
|
+
scrollIntoTocView(targetElementToScroll);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
145
182
|
|
|
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
|
-
rootMargin: "0px",
|
|
179
|
-
threshold: [0, 1],
|
|
180
|
-
},
|
|
181
|
-
);
|
|
182
|
-
headers.forEach((header) => observer.observe(header));
|
|
183
|
+
// 7. 使用 requestAnimationFrame 优化滚动触发频率
|
|
184
|
+
let isTicking = false;
|
|
185
|
+
function requestUpdate() {
|
|
186
|
+
if (isTicking) return;
|
|
187
|
+
isTicking = true;
|
|
188
|
+
window.requestAnimationFrame(() => {
|
|
189
|
+
updateActive();
|
|
190
|
+
isTicking = false;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 8. 监听视口变化与滚动
|
|
195
|
+
window.addEventListener("scroll", requestUpdate, { passive: true });
|
|
196
|
+
window.addEventListener("resize", requestUpdate, { passive: true });
|
|
197
|
+
|
|
198
|
+
if ("IntersectionObserver" in window) {
|
|
199
|
+
const observer = new IntersectionObserver(
|
|
200
|
+
() => {
|
|
201
|
+
requestUpdate();
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
rootMargin: `-${TOP_OFFSET}px 0px 0px 0px`,
|
|
205
|
+
threshold: [0, 1],
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
headerElements.forEach((header) => observer.observe(header));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 初始执行一次
|
|
212
|
+
updateActive();
|
|
183
213
|
});
|