q6 commited on
Commit
90d6834
·
1 Parent(s): 479db26

Fix expanded image pan and zoom

Browse files
Files changed (3) hide show
  1. frontend/app.js +41 -12
  2. frontend/index.html +3 -3
  3. frontend/style.css +6 -6
frontend/app.js CHANGED
@@ -15,7 +15,14 @@ const POST_ID_RE = /\d+/g
15
  const PAGE_SIZE = 60
16
  const SEARCH_PAGE_SIZE = 10
17
  const IMAGE_URL_VERSION = 2
 
 
 
 
 
18
  let viewerScale = 1
 
 
19
  let viewerDrag = null
20
  let viewerPostId = ""
21
  let viewerPages = []
@@ -696,6 +703,8 @@ function showViewerPage() {
696
  $("#viewer-prev").classList.toggle("hidden", viewerPages.length <= 1)
697
  $("#viewer-next").classList.toggle("hidden", viewerPages.length <= 1)
698
  viewerScale = 1
 
 
699
  applyViewerZoom()
700
  }
701
 
@@ -728,18 +737,38 @@ function closeViewer() {
728
  }
729
 
730
  function applyViewerZoom() {
731
- $("#viewer-img").style.transform = `scale(${viewerScale})`
732
  $("#viewer-zoom").textContent = `${Math.round(viewerScale * 100)}%`
733
  }
734
 
735
- $("#viewer-zoom-in").addEventListener("click", () => {
736
- viewerScale = Math.min(viewerScale + .25, 6)
 
 
 
737
  applyViewerZoom()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
738
  })
739
 
740
  $("#viewer-zoom-out").addEventListener("click", () => {
741
- viewerScale = Math.max(viewerScale - .25, .25)
742
- applyViewerZoom()
743
  })
744
 
745
  $("#viewer-close").addEventListener("click", closeViewer)
@@ -748,26 +777,26 @@ $("#viewer-next").addEventListener("click", () => moveViewerPage(1))
748
  $("#viewer").addEventListener("click", e => { if (e.target.id === "viewer") closeViewer() })
749
  $("#viewer-stage").addEventListener("wheel", e => {
750
  e.preventDefault()
751
- viewerScale = Math.min(Math.max(viewerScale + (e.deltaY < 0 ? .15 : -.15), .25), 6)
752
- applyViewerZoom()
753
  })
754
  $("#viewer-stage").addEventListener("pointerdown", e => {
755
  if (e.button !== 0 || e.target.closest(".viewer-nav")) return
 
756
  const stage = $("#viewer-stage")
757
  viewerDrag = {
758
  x: e.clientX,
759
  y: e.clientY,
760
- left: stage.scrollLeft,
761
- top: stage.scrollTop,
762
  }
763
  stage.classList.add("dragging")
764
  stage.setPointerCapture(e.pointerId)
765
  })
766
  $("#viewer-stage").addEventListener("pointermove", e => {
767
  if (!viewerDrag) return
768
- const stage = $("#viewer-stage")
769
- stage.scrollLeft = viewerDrag.left - e.clientX + viewerDrag.x
770
- stage.scrollTop = viewerDrag.top - e.clientY + viewerDrag.y
771
  })
772
  $("#viewer-stage").addEventListener("pointerup", e => {
773
  viewerDrag = null
 
15
  const PAGE_SIZE = 60
16
  const SEARCH_PAGE_SIZE = 10
17
  const IMAGE_URL_VERSION = 2
18
+ const VIEWER_GAP = 16
19
+ const VIEWER_MIN_SCALE = .25
20
+ const VIEWER_MAX_SCALE = 6
21
+ const VIEWER_BUTTON_STEP = .25
22
+ const VIEWER_WHEEL_STEP = .15
23
  let viewerScale = 1
24
+ let viewerX = 0
25
+ let viewerY = VIEWER_GAP
26
  let viewerDrag = null
27
  let viewerPostId = ""
28
  let viewerPages = []
 
703
  $("#viewer-prev").classList.toggle("hidden", viewerPages.length <= 1)
704
  $("#viewer-next").classList.toggle("hidden", viewerPages.length <= 1)
705
  viewerScale = 1
706
+ viewerX = 0
707
+ viewerY = VIEWER_GAP
708
  applyViewerZoom()
709
  }
710
 
 
737
  }
738
 
739
  function applyViewerZoom() {
740
+ $("#viewer-img").style.transform = `translate3d(${viewerX}px, ${viewerY}px, 0) scale(${viewerScale})`
741
  $("#viewer-zoom").textContent = `${Math.round(viewerScale * 100)}%`
742
  }
743
 
744
+ function resetViewerZoom() {
745
+ const stage = $("#viewer-stage")
746
+ viewerScale = 1
747
+ viewerX = (stage.clientWidth - $("#viewer-img").naturalWidth) / 2
748
+ viewerY = VIEWER_GAP
749
  applyViewerZoom()
750
+ }
751
+
752
+ function zoomViewer(scale, x, y) {
753
+ const next = Math.min(Math.max(scale, VIEWER_MIN_SCALE), VIEWER_MAX_SCALE)
754
+ if (next === viewerScale) return
755
+ const rect = $("#viewer-stage").getBoundingClientRect()
756
+ const anchorX = (x ?? rect.left + rect.width / 2) - rect.left
757
+ const anchorY = (y ?? rect.top + rect.height / 2) - rect.top
758
+ const ratio = next / viewerScale
759
+ viewerX = anchorX - (anchorX - viewerX) * ratio
760
+ viewerY = anchorY - (anchorY - viewerY) * ratio
761
+ viewerScale = next
762
+ applyViewerZoom()
763
+ }
764
+
765
+ $("#viewer-img").addEventListener("load", resetViewerZoom)
766
+ $("#viewer-zoom-in").addEventListener("click", () => {
767
+ zoomViewer(viewerScale + VIEWER_BUTTON_STEP)
768
  })
769
 
770
  $("#viewer-zoom-out").addEventListener("click", () => {
771
+ zoomViewer(viewerScale - VIEWER_BUTTON_STEP)
 
772
  })
773
 
774
  $("#viewer-close").addEventListener("click", closeViewer)
 
777
  $("#viewer").addEventListener("click", e => { if (e.target.id === "viewer") closeViewer() })
778
  $("#viewer-stage").addEventListener("wheel", e => {
779
  e.preventDefault()
780
+ zoomViewer(viewerScale + (e.deltaY < 0 ? VIEWER_WHEEL_STEP : -VIEWER_WHEEL_STEP), e.clientX, e.clientY)
 
781
  })
782
  $("#viewer-stage").addEventListener("pointerdown", e => {
783
  if (e.button !== 0 || e.target.closest(".viewer-nav")) return
784
+ e.preventDefault()
785
  const stage = $("#viewer-stage")
786
  viewerDrag = {
787
  x: e.clientX,
788
  y: e.clientY,
789
+ left: viewerX,
790
+ top: viewerY,
791
  }
792
  stage.classList.add("dragging")
793
  stage.setPointerCapture(e.pointerId)
794
  })
795
  $("#viewer-stage").addEventListener("pointermove", e => {
796
  if (!viewerDrag) return
797
+ viewerX = viewerDrag.left + e.clientX - viewerDrag.x
798
+ viewerY = viewerDrag.top + e.clientY - viewerDrag.y
799
+ applyViewerZoom()
800
  })
801
  $("#viewer-stage").addEventListener("pointerup", e => {
802
  viewerDrag = null
frontend/index.html CHANGED
@@ -5,7 +5,7 @@
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <link rel="icon" href="data:;base64,=">
7
  <title>Pixif2</title>
8
- <link rel="stylesheet" href="style.css?v=20260716-1">
9
  </head>
10
  <body>
11
  <div class="topbar">
@@ -115,10 +115,10 @@
115
  </div>
116
  <div id="viewer-stage">
117
  <button id="viewer-prev" class="viewer-nav" aria-label="Previous result page">&lsaquo;</button>
118
- <img id="viewer-img" alt="">
119
  <button id="viewer-next" class="viewer-nav" aria-label="Next result page">&rsaquo;</button>
120
  </div>
121
  </div>
122
- <script src="app.js?v=20260718-1"></script>
123
  </body>
124
  </html>
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <link rel="icon" href="data:;base64,=">
7
  <title>Pixif2</title>
8
+ <link rel="stylesheet" href="style.css?v=20260720-1">
9
  </head>
10
  <body>
11
  <div class="topbar">
 
115
  </div>
116
  <div id="viewer-stage">
117
  <button id="viewer-prev" class="viewer-nav" aria-label="Previous result page">&lsaquo;</button>
118
+ <img id="viewer-img" draggable="false" alt="">
119
  <button id="viewer-next" class="viewer-nav" aria-label="Next result page">&rsaquo;</button>
120
  </div>
121
  </div>
122
+ <script src="app.js?v=20260720-1"></script>
123
  </body>
124
  </html>
frontend/style.css CHANGED
@@ -550,11 +550,7 @@ input:focus, select:focus, textarea:focus { outline: none; border-color: var(--a
550
  #viewer-stage {
551
  position: relative;
552
  flex: 1;
553
- display: flex;
554
- align-items: flex-start;
555
- justify-content: center;
556
- overflow: auto;
557
- padding: 1rem;
558
  cursor: grab;
559
  touch-action: none;
560
  }
@@ -576,10 +572,14 @@ input:focus, select:focus, textarea:focus { outline: none; border-color: var(--a
576
  #viewer-next { right: 1rem; }
577
  #viewer-stage.dragging { cursor: grabbing; }
578
  #viewer-img {
 
 
 
579
  max-width: none;
580
  max-height: none;
581
- transform-origin: top center;
582
  user-select: none;
 
583
  }
584
  @media (max-width: 860px) {
585
  .topbar { align-items: stretch; min-height: 64px; padding: .7rem 4vw; flex-direction: column; }
 
550
  #viewer-stage {
551
  position: relative;
552
  flex: 1;
553
+ overflow: hidden;
 
 
 
 
554
  cursor: grab;
555
  touch-action: none;
556
  }
 
572
  #viewer-next { right: 1rem; }
573
  #viewer-stage.dragging { cursor: grabbing; }
574
  #viewer-img {
575
+ position: absolute;
576
+ top: 0;
577
+ left: 0;
578
  max-width: none;
579
  max-height: none;
580
+ transform-origin: top left;
581
  user-select: none;
582
+ -webkit-user-drag: none;
583
  }
584
  @media (max-width: 860px) {
585
  .topbar { align-items: stretch; min-height: 64px; padding: .7rem 4vw; flex-direction: column; }