feat(前端界面): 优化前端性能
build.yaml / build (push) Successful in 35s

This commit is contained in:
2026-07-08 22:11:30 +08:00
parent ffd61d6412
commit a665df062b
+104 -27
View File
@@ -61,7 +61,9 @@ const state = {
fitMode: "width", fitMode: "width",
renderAnnotations: true, renderAnnotations: true,
pageCache: new Map(), pageCache: new Map(),
pageInFlight: new Set(), pageInFlight: new Map(),
pageRenderQueue: [],
pageRenderRunning: false,
pageObserver: null, pageObserver: null,
scrollFrame: 0, scrollFrame: 0,
thumbnailCache: new Map(), thumbnailCache: new Map(),
@@ -102,6 +104,7 @@ const el = {
svgHost: document.querySelector("#svgHost"), svgHost: document.querySelector("#svgHost"),
pageListPanel: document.querySelector(".page-list-panel"), pageListPanel: document.querySelector(".page-list-panel"),
pageList: document.querySelector("#pageList"), pageList: document.querySelector("#pageList"),
metaPanel: document.querySelector(".meta-panel"),
metaTitle: document.querySelector("#metaTitle"), metaTitle: document.querySelector("#metaTitle"),
metaAuthor: document.querySelector("#metaAuthor"), metaAuthor: document.querySelector("#metaAuthor"),
metaVersion: document.querySelector("#metaVersion"), metaVersion: document.querySelector("#metaVersion"),
@@ -894,6 +897,10 @@ async function openDocument(options = {}) {
if (options.resetScroll) { if (options.resetScroll) {
el.viewerPanel.scrollLeft = 0; el.viewerPanel.scrollLeft = 0;
el.viewerPanel.scrollTop = 0; el.viewerPanel.scrollTop = 0;
el.pageListPanel.scrollLeft = 0;
el.pageListPanel.scrollTop = 0;
el.metaPanel.scrollLeft = 0;
el.metaPanel.scrollTop = 0;
} }
applyFit(false); applyFit(false);
await nextFrame(); await nextFrame();
@@ -936,7 +943,7 @@ async function renderPage(index, options = {}) {
if (options.scroll !== false) { if (options.scroll !== false) {
scrollToPage(index); scrollToPage(index);
} }
await renderFlowPage(index, { throwError: true, openSeq }); await renderFlowPage(index, { throwError: true, openSeq, priority: 0 });
if (openSeq !== state.openSeq) { if (openSeq !== state.openSeq) {
return; return;
} }
@@ -1075,6 +1082,10 @@ function renderPageFlow() {
function resetPageFlow() { function resetPageFlow() {
state.pageCache.clear(); state.pageCache.clear();
for (const task of state.pageInFlight.values()) {
task.resolve(null);
}
state.pageRenderQueue = [];
state.pageInFlight.clear(); state.pageInFlight.clear();
resetThumbnails(); resetThumbnails();
if (state.pageObserver) { if (state.pageObserver) {
@@ -1086,7 +1097,7 @@ function resetPageFlow() {
function observeFlowPage(shell, index) { function observeFlowPage(shell, index) {
const openSeq = state.openSeq; const openSeq = state.openSeq;
if (index < 4 && index !== state.pageIndex) { if (index < 4 && index !== state.pageIndex) {
renderFlowPage(index, { openSeq }); renderFlowPage(index, { openSeq, priority: 3 });
return; return;
} }
const observer = flowPageObserver(); const observer = flowPageObserver();
@@ -1108,7 +1119,7 @@ function flowPageObserver() {
} }
const index = Number.parseInt(entry.target.dataset.pageIndex, 10); const index = Number.parseInt(entry.target.dataset.pageIndex, 10);
state.pageObserver.unobserve(entry.target); state.pageObserver.unobserve(entry.target);
renderFlowPage(index, { openSeq }); renderFlowPage(index, { openSeq, priority: 4 });
} }
}, { }, {
root: el.viewerPanel, root: el.viewerPanel,
@@ -1123,27 +1134,18 @@ async function renderFlowPage(index, options = {}) {
if (openSeq !== state.openSeq) { if (openSeq !== state.openSeq) {
return null; return null;
} }
if (state.pageCache.has(index)) {
return state.pageCache.get(index);
}
const key = `${openSeq}:${index}`;
if (state.pageInFlight.has(key)) {
return null;
}
state.pageInFlight.add(key);
try { try {
await nextFrame(); const page = await loadPageData(index, { openSeq, priority: options.priority || 2 });
if (openSeq !== state.openSeq) { if (openSeq !== state.openSeq) {
return null; return null;
} }
const page = callWASM("ofdgoRenderPage", index); if (page) {
if (openSeq !== state.openSeq) { const shell = pageShell(index);
return null; if (!shell?.classList.contains("rendered")) {
mountPageSVG(index, page, openSeq);
}
updateThumbnail(index, openSeq);
} }
state.pageCache.set(index, page);
cacheThumbnail(index, page.svg);
mountPageSVG(index, page, openSeq);
updateThumbnail(index, openSeq);
return page; return page;
} catch (err) { } catch (err) {
if (openSeq === state.openSeq) { if (openSeq === state.openSeq) {
@@ -1153,11 +1155,88 @@ async function renderFlowPage(index, options = {}) {
throw err; throw err;
} }
return null; return null;
} finally {
state.pageInFlight.delete(key);
} }
} }
function loadPageData(index, options = {}) {
const openSeq = options.openSeq || state.openSeq;
if (openSeq !== state.openSeq) {
return Promise.resolve(null);
}
if (state.pageCache.has(index)) {
return Promise.resolve(state.pageCache.get(index));
}
const key = `${openSeq}:${index}`;
const priority = options.priority || 3;
const current = state.pageInFlight.get(key);
if (current) {
current.priority = Math.min(current.priority, priority);
return current.promise;
}
let resolve;
let reject;
const promise = new Promise((done, fail) => {
resolve = done;
reject = fail;
});
const task = { key, index, openSeq, priority, resolve, reject, promise };
state.pageInFlight.set(key, task);
state.pageRenderQueue.push(task);
schedulePageRender();
return promise;
}
function schedulePageRender() {
if (state.pageRenderRunning) {
return;
}
state.pageRenderRunning = true;
requestAnimationFrame(processPageRenderQueue);
}
async function processPageRenderQueue() {
try {
while (state.pageRenderQueue.length) {
state.pageRenderQueue.sort(comparePageRenderTask);
const task = state.pageRenderQueue.shift();
try {
if (task.openSeq !== state.openSeq) {
task.resolve(null);
continue;
}
if (state.pageCache.has(task.index)) {
task.resolve(state.pageCache.get(task.index));
continue;
}
await nextFrame();
if (task.openSeq !== state.openSeq) {
task.resolve(null);
continue;
}
const page = callWASM("ofdgoRenderPage", task.index);
if (task.openSeq === state.openSeq) {
state.pageCache.set(task.index, page);
cacheThumbnail(task.index, page.svg);
}
task.resolve(page);
} catch (err) {
task.reject(err);
} finally {
state.pageInFlight.delete(task.key);
}
}
} finally {
state.pageRenderRunning = false;
if (state.pageRenderQueue.length) {
schedulePageRender();
}
}
}
function comparePageRenderTask(a, b) {
return a.priority - b.priority || Math.abs(a.index - state.pageIndex) - Math.abs(b.index - state.pageIndex);
}
function mountPageSVG(index, page, openSeq = state.openSeq) { function mountPageSVG(index, page, openSeq = state.openSeq) {
if (openSeq !== state.openSeq) { if (openSeq !== state.openSeq) {
return; return;
@@ -1190,7 +1269,7 @@ function markFlowPageError(index, err) {
function queueNearbyPages(index, openSeq = state.openSeq) { function queueNearbyPages(index, openSeq = state.openSeq) {
for (let i = Math.max(0, index - 1); i <= Math.min((state.doc?.pageCount || 1) - 1, index + 2); i += 1) { for (let i = Math.max(0, index - 1); i <= Math.min((state.doc?.pageCount || 1) - 1, index + 2); i += 1) {
renderFlowPage(i, { openSeq }); renderFlowPage(i, { openSeq, priority: 2 });
} }
} }
@@ -1473,15 +1552,13 @@ async function renderThumbnail(index, openSeq = state.openSeq) {
} }
state.thumbnailInFlight.add(key); state.thumbnailInFlight.add(key);
try { try {
await nextFrame(); const page = await loadPageData(index, { openSeq, priority: 5 });
if (openSeq !== state.openSeq) { if (openSeq !== state.openSeq) {
return; return;
} }
const page = callWASM("ofdgoRenderPage", index); if (!page) {
if (openSeq !== state.openSeq) {
return; return;
} }
cacheThumbnail(index, page.svg);
updateThumbnail(index, openSeq); updateThumbnail(index, openSeq);
} catch { } catch {
if (openSeq === state.openSeq) { if (openSeq === state.openSeq) {