feat(前端界面): 新增页面拖动,默认关闭
build.yaml / build (push) Successful in 47s

This commit is contained in:
xiaoqidun committed 2026-09-13 13:54:51 +08:00
1 parent e60ad5d6c2
commit abbd4de13a
3 files changed
+72

No files matched your search

+1
View File
@@ -37,6 +37,7 @@
<button id="fitButton" class="button" type="button" data-short="宽" aria-label="适宽" disabled>适宽</button>
<button id="fitHeightButton" class="button" type="button" data-short="高" aria-label="适高" disabled>适高</button>
<button id="rotateButton" class="button" type="button" data-short="转" title="顺时针旋转90°" aria-label="旋转" disabled>旋转</button>
<button id="panButton" class="button" type="button" data-short="拖" title="拖动页面" aria-label="拖动" aria-pressed="false" disabled>拖动</button>
<button id="continuousButton" class="button" type="button" data-short="连" title="无距连续显示" aria-label="无距连续显示" aria-pressed="false" disabled>连续</button>
<button id="annotationButton" class="button" type="button" data-short="注" title="关闭注解" aria-label="关闭注解" aria-pressed="true">注解</button>
</div>
+13
View File
@@ -659,6 +659,19 @@ button.outline-link:hover {
overflow-y: hidden;
}
.viewer-panel.pan-mode,
.pan-mode .text-run > span {
cursor: grab;
}
.viewer-panel.panning,
.panning .text-layer,
.panning .text-run > span {
cursor: grabbing;
-webkit-user-select: none;
user-select: none;
}
.empty-state {
position: absolute;
inset: 0;
+58
View File
@@ -47,6 +47,8 @@ const state = {
pageIndex: 0,
scale: 1,
rotation: 0,
panMode: false,
pan: null,
fitMode: "width",
continuous: false,
renderAnnotations: true,
@@ -86,6 +88,7 @@ const el = {
fitButton: document.querySelector("#fitButton"),
fitHeightButton: document.querySelector("#fitHeightButton"),
rotateButton: document.querySelector("#rotateButton"),
panButton: document.querySelector("#panButton"),
continuousButton: document.querySelector("#continuousButton"),
annotationButton: document.querySelector("#annotationButton"),
imageDPI: document.querySelector("#imageDPI"),
@@ -196,6 +199,7 @@ el.zoomInButton.addEventListener("click", () => setScale(state.scale + 0.1));
el.fitButton.addEventListener("click", fitWidth);
el.fitHeightButton.addEventListener("click", fitHeight);
el.rotateButton.addEventListener("click", rotatePages);
el.panButton.addEventListener("click", togglePan);
el.continuousButton.addEventListener("click", toggleContinuous);
el.annotationButton.addEventListener("click", toggleAnnotations);
document.addEventListener("copy", copySelection);
@@ -215,10 +219,16 @@ el.pageInput.addEventListener("change", () => {
}
});
window.addEventListener("resize", resizeViewer);
window.addEventListener("blur", () => endPan());
COMPACT_LAYOUT.addEventListener("change", syncLayoutMode);
el.viewerPanel.addEventListener("scroll", () => {
schedulePageSync();
});
el.viewerPanel.addEventListener("pointerdown", startPan);
el.viewerPanel.addEventListener("pointermove", movePan);
el.viewerPanel.addEventListener("pointerup", endPan);
el.viewerPanel.addEventListener("pointercancel", endPan);
el.viewerPanel.addEventListener("lostpointercapture", endPan);
el.viewerPanel.addEventListener("click", () => {
if (COMPACT_LAYOUT.matches && (state.showPages || state.showMeta)) {
state.showPages = false;
@@ -2838,6 +2848,52 @@ function pageViewSize(page) {
return state.rotation % 180 ? { width: page.height, height: page.width } : page;
}
function togglePan() {
if (document.body.hasAttribute("aria-busy")) {
return;
}
endPan();
state.panMode = !state.panMode;
el.viewerPanel.classList.toggle("pan-mode", state.panMode);
el.panButton.setAttribute("aria-pressed", String(state.panMode));
}
function startPan(event) {
if (!state.panMode || !state.doc || event.pointerType !== "mouse" || event.button !== 0 || document.body.hasAttribute("aria-busy") || event.target.closest("a")) {
return;
}
const rect = el.viewerPanel.getBoundingClientRect();
if (event.clientX >= rect.left + el.viewerPanel.clientWidth || event.clientY >= rect.top + el.viewerPanel.clientHeight) {
return;
}
event.preventDefault();
state.pan = { id: event.pointerId, x: event.clientX + el.viewerPanel.scrollLeft, y: event.clientY + el.viewerPanel.scrollTop };
el.viewerPanel.setPointerCapture(event.pointerId);
el.viewerPanel.classList.add("panning");
el.viewerPanel.focus({ preventScroll: true });
}
function movePan(event) {
const pan = state.pan;
if (!pan || event.pointerId !== pan.id) {
return;
}
el.viewerPanel.scrollLeft = pan.x - event.clientX;
el.viewerPanel.scrollTop = pan.y - event.clientY;
}
function endPan(event) {
const pan = state.pan;
if (!pan || (event && event.pointerId !== pan.id)) {
return;
}
state.pan = null;
el.viewerPanel.classList.remove("panning");
if (el.viewerPanel.hasPointerCapture(pan.id)) {
el.viewerPanel.releasePointerCapture(pan.id);
}
}
function fitWidth(updateStatus = true) {
const page = currentPageInfo();
if (!page) {
@@ -3026,6 +3082,7 @@ function updateControls() {
el.fitButton.disabled = !hasDoc;
el.fitHeightButton.disabled = !hasDoc;
el.rotateButton.disabled = !hasDoc;
el.panButton.disabled = !hasDoc;
el.continuousButton.disabled = !hasDoc;
el.fitButton.toggleAttribute("aria-pressed", hasDoc && state.fitMode === "width");
el.fitHeightButton.toggleAttribute("aria-pressed", hasDoc && state.fitMode === "height");
@@ -3080,6 +3137,7 @@ function setBusy(busy, text = "", percent = 0, status = "") {
}
return;
}
endPan();
el.progressPanel.hidden = false;
setProgress(text, percent, status);
}