Speed up top nav with turbo routing and external base CSS.

Remove view-transition lag, swap main content without full reload, prefetch pages, and tear down SSE timers on leave.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-26 20:47:22 +08:00
parent ddfe2a52aa
commit c79bb2ea4b
16 changed files with 865 additions and 548 deletions
+230
View File
@@ -0,0 +1,230 @@
/* Copyright (c) 2025-2026 马建军. All rights reserved.
* 专有软件 — 未经授权禁止复制、传播、转售。
* 详见 LICENSE.zh-CN.txt
*/
(function () {
var PERMANENT_CSS = ['/static/css/base.css', '/static/css/tech.css', '/static/css/responsive.css'];
var CORE_SCRIPT_MARKERS = ['theme.js', 'symbol.js', 'page.js', 'nav.js', 'pwa.js', 'turbonav.js'];
var pageCache = new Map();
var MAX_CACHE = 10;
var inflight = null;
var currentUrl = normalizeUrl(window.location.href);
function normalizeUrl(href) {
var u = new URL(href, window.location.origin);
u.hash = '';
return u.href;
}
function isPermanentStylesheet(el) {
var href = el.getAttribute('href') || '';
return PERMANENT_CSS.some(function (p) { return href.indexOf(p) !== -1; });
}
function isCoreScript(el) {
var src = el.getAttribute('src') || '';
if (!src) return false;
return CORE_SCRIPT_MARKERS.some(function (m) { return src.indexOf(m) !== -1; });
}
function shouldTurboClick(e, link) {
if (!link || !link.href) return false;
if (e.defaultPrevented) return false;
if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return false;
if (link.target && link.target !== '_self') return false;
if (link.hasAttribute('download')) return false;
if (link.origin !== window.location.origin) return false;
if (normalizeUrl(link.href) === currentUrl) return false;
return true;
}
function parseHtml(html) {
return new DOMParser().parseFromString(html, 'text/html');
}
function fetchPage(url, signal) {
var key = normalizeUrl(url);
if (pageCache.has(key)) {
return Promise.resolve(pageCache.get(key));
}
return fetch(key, {
credentials: 'same-origin',
headers: { Accept: 'text/html' },
signal: signal
}).then(function (res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.text();
}).then(function (html) {
var doc = parseHtml(html);
pageCache.set(key, doc);
if (pageCache.size > MAX_CACHE) {
var first = pageCache.keys().next().value;
pageCache.delete(first);
}
return doc;
});
}
function collectPageCss(doc) {
var out = [];
doc.querySelectorAll('head link[rel="stylesheet"], head style').forEach(function (el) {
if (el.tagName === 'LINK' && isPermanentStylesheet(el)) return;
out.push(el);
});
return out;
}
function collectPageScripts(doc) {
var out = [];
var pastCore = false;
doc.body.querySelectorAll(':scope > script').forEach(function (el) {
if (isCoreScript(el)) {
if ((el.getAttribute('src') || '').indexOf('pwa.js') !== -1) pastCore = true;
return;
}
if (!pastCore) return;
out.push(el);
});
return out;
}
function removePageAssets() {
document.querySelectorAll('head link[rel="stylesheet"]').forEach(function (el) {
if (!isPermanentStylesheet(el)) el.remove();
});
document.querySelectorAll('head style').forEach(function (el) { el.remove(); });
document.querySelectorAll('body script[data-page-js]').forEach(function (el) { el.remove(); });
}
function applyPageCss(items) {
items.forEach(function (srcEl) {
var el = srcEl.cloneNode(true);
if (el.tagName === 'LINK') {
el.setAttribute('data-page-css', '');
} else {
el.setAttribute('data-page-css', '');
}
document.head.appendChild(el);
});
}
function runPageScripts(items) {
return items.reduce(function (chain, srcEl) {
return chain.then(function () {
return new Promise(function (resolve) {
var s = document.createElement('script');
s.setAttribute('data-page-js', '');
var src = srcEl.getAttribute('src');
if (src) {
var bust = (src.indexOf('?') >= 0 ? '&' : '?') + '_turbo=' + Date.now();
s.src = src + bust;
s.async = false;
s.onload = function () { resolve(); };
s.onerror = function () { resolve(); };
document.body.appendChild(s);
} else {
s.textContent = srcEl.textContent;
document.body.appendChild(s);
resolve();
}
});
});
}, Promise.resolve());
}
function syncNavActive(doc, url) {
var nav = document.getElementById('site-nav');
var fetchedNav = doc.getElementById('site-nav');
if (!nav || !fetchedNav) return;
nav.querySelectorAll('a.active').forEach(function (a) { a.classList.remove('active'); });
fetchedNav.querySelectorAll('a[href].active').forEach(function (fa) {
var href = fa.getAttribute('href');
if (!href) return;
var local = nav.querySelector('a[href="' + href + '"]')
|| nav.querySelector('a[href="' + new URL(href, window.location.origin).pathname + '"]');
if (local) local.classList.add('active');
});
}
function setLoading(on) {
var main = document.querySelector('.main');
if (main) main.classList.toggle('nav-loading', on);
}
function applyDocument(doc, url, fromPopstate) {
var main = document.querySelector('.main');
var fetchedMain = doc.querySelector('.main');
if (!main || !fetchedMain) {
window.location.href = url;
return Promise.resolve();
}
window.dispatchEvent(new Event('qihuo:page-leave'));
removePageAssets();
applyPageCss(collectPageCss(doc));
main.innerHTML = fetchedMain.innerHTML;
document.title = doc.title || document.title;
syncNavActive(doc, url);
return runPageScripts(collectPageScripts(doc)).then(function () {
currentUrl = normalizeUrl(url);
if (!fromPopstate) {
history.pushState({ turbo: true }, '', currentUrl);
}
setLoading(false);
window.scrollTo(0, 0);
if (window.qihuoEmitPageLoad) window.qihuoEmitPageLoad();
});
}
function navigateTo(url, opts) {
opts = opts || {};
var target = normalizeUrl(url);
if (target === currentUrl && !opts.force) return Promise.resolve();
if (inflight) inflight.abort();
var ctrl = new AbortController();
inflight = ctrl;
setLoading(true);
return fetchPage(target, ctrl.signal).then(function (doc) {
if (ctrl.signal.aborted) return;
inflight = null;
applyDocument(doc, target, !!opts.fromPopstate);
}).catch(function (err) {
if (ctrl.signal.aborted) return;
inflight = null;
setLoading(false);
window.location.href = target;
});
}
function prefetchPage(href) {
var target = normalizeUrl(href);
if (target === currentUrl || pageCache.has(target)) return;
fetchPage(target).catch(function () { /* ignore */ });
}
window.qihuoNavigate = navigateTo;
window.qihuoPrefetchPage = prefetchPage;
document.addEventListener('click', function (e) {
var link = e.target.closest('#site-nav a[href]');
if (!link || !shouldTurboClick(e, link)) return;
e.preventDefault();
var navEl = document.getElementById('site-nav');
if (navEl) {
navEl.querySelectorAll('a.active').forEach(function (a) { a.classList.remove('active'); });
link.classList.add('active');
}
navigateTo(link.href);
}, true);
window.addEventListener('popstate', function () {
var target = normalizeUrl(window.location.href);
if (target === currentUrl) return;
navigateTo(target, { fromPopstate: true, force: true });
});
if (window.qihuoEmitPageLoad) window.qihuoEmitPageLoad();
})();