Skip to content

Commit 999ae07

Browse files
perf(swup): Add viewport-based link preloading
Use IntersectionObserver to preload pages when links enter the viewport, improving page transition speed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 78bd41d commit 999ae07

2 files changed

Lines changed: 115 additions & 0 deletions

File tree

src/scripts/swup-manager.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import type { PanelHandler } from "./handlers/panel-handler";
2626
import { getPanelHandler, initPanelHandler } from "./handlers/panel-handler";
2727
import { checkKatex, initCustomScrollbar } from "./handlers/scroll-handler";
28+
import { initLinkPreloading } from "../utils/navigation-utils";
2829

2930
/**
3031
* Swup 管理器类
@@ -75,6 +76,9 @@ export class SwupManager {
7576
// 初始化 Banner
7677
this.initBanner();
7778

79+
// 初始化链接预加载
80+
this.initPreloading();
81+
7882
this.initialized = true;
7983
console.log("SwupManager: 初始化完成");
8084
}
@@ -157,6 +161,19 @@ export class SwupManager {
157161
}
158162
}
159163

164+
/**
165+
* 初始化链接预加载
166+
*/
167+
private initPreloading(): void {
168+
if (document.readyState === "loading") {
169+
document.addEventListener("DOMContentLoaded", () => {
170+
initLinkPreloading();
171+
});
172+
} else {
173+
initLinkPreloading();
174+
}
175+
}
176+
160177
/**
161178
* 显示 Banner 和初始化轮播
162179
*/

src/utils/navigation-utils.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,104 @@ export function preloadPage(url: string): void {
136136
}
137137
}
138138

139+
/**
140+
* 检查是否为同源链接
141+
*/
142+
function isSameOrigin(url: string): boolean {
143+
try {
144+
const parsed = new URL(url, window.location.origin);
145+
return parsed.origin === window.location.origin;
146+
} catch {
147+
return false;
148+
}
149+
}
150+
151+
/**
152+
* 检查网络状态是否为慢速连接
153+
*/
154+
function isSlowConnection(): boolean {
155+
const conn = (navigator as any).connection;
156+
if (!conn) return false;
157+
return conn.effectiveType === "2g" || conn.effectiveType === "slow-2g";
158+
}
159+
160+
/**
161+
* 初始化链接预加载功能
162+
* 使用 IntersectionObserver 观察视口内的链接,在进入视野时预加载
163+
*/
164+
export function initLinkPreloading(): void {
165+
// 如果 Swup 不可用或用户偏好减少动画,不进行预加载
166+
if (!isSwupReady() || isSlowConnection()) {
167+
return;
168+
}
169+
170+
// 检查用户是否偏好减少动画
171+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
172+
return;
173+
}
174+
175+
// 已预加载的 URL 集合,避免重复预加载
176+
const preloadedUrls = new Set<string>();
177+
178+
const observer = new IntersectionObserver(
179+
(entries) => {
180+
entries.forEach((entry) => {
181+
if (entry.isIntersecting) {
182+
const link = entry.target as HTMLAnchorElement;
183+
const href = link.href;
184+
185+
// 检查是否有效、是否同源、是否已预加载、是否当前页面
186+
if (
187+
href &&
188+
isSameOrigin(href) &&
189+
!preloadedUrls.has(href) &&
190+
href !== window.location.href &&
191+
!href.includes("#")
192+
) {
193+
preloadedUrls.add(href);
194+
195+
// 使用 requestIdleCallback 在空闲时预加载
196+
if ("requestIdleCallback" in window) {
197+
requestIdleCallback(() => preloadPage(href), {
198+
timeout: 2000,
199+
});
200+
} else {
201+
setTimeout(() => preloadPage(href), 100);
202+
}
203+
}
204+
}
205+
});
206+
},
207+
{
208+
rootMargin: "200px",
209+
},
210+
);
211+
212+
// 观察所有内部链接
213+
const observeLinks = () => {
214+
document
215+
.querySelectorAll('a[href^="/"], a[href^="./"], a[href^="../"]')
216+
.forEach((link) => {
217+
observer.observe(link);
218+
});
219+
};
220+
221+
// 初始观察
222+
observeLinks();
223+
224+
// 页面切换后重新观察(Swup 会替换 main 容器内容)
225+
const mainContainer = document.querySelector("main");
226+
if (mainContainer) {
227+
const mutationObserver = new MutationObserver(() => {
228+
observeLinks();
229+
});
230+
mutationObserver.observe(mainContainer, {
231+
childList: true,
232+
subtree: true,
233+
});
234+
}
235+
}
236+
139237
/**
140238
* 获取当前页面路径
141239
*/

0 commit comments

Comments
 (0)