Pan with trackpad scrolling and zoom with pinch gestures

This commit is contained in:
Olive Vaughn 2026-10-04 00:44:47 -04:00
parent 484b4f1698
commit 6df315b73d

View file

@ -648,24 +648,52 @@
(.addEventListener js/window "keyup" #(when (= "Space" (.-code %)) (reset! space-held? false))) (.addEventListener js/window "keyup" #(when (= "Space" (.-code %)) (reset! space-held? false)))
(.addEventListener js/window "blur" #(do (reset! space-held? false) (reset! pan nil))))) (.addEventListener js/window "blur" #(do (reset! space-held? false) (reset! pan nil)))))
(defonce pinch-start-zoom (atom nil))
(defn- zoom-at! [el client-x client-y scale]
(let [z @(rf/subscribe [::layout/zoom :stage])
wrap (.querySelector el ".stage-wrap")
wb (.getBoundingClientRect wrap)
x (- client-x (.-left wb)) y (- client-y (.-top wb))
next (max 0.1 (min 16 scale))]
(rf/dispatch-sync [::layout/set :stage next])
(js/requestAnimationFrame
(fn []
(let [nb (.getBoundingClientRect wrap)]
(set! (.-scrollLeft el) (+ (.-scrollLeft el) (- (+ (.-left nb) (* x (/ next z))) client-x)))
(set! (.-scrollTop el) (+ (.-scrollTop el) (- (+ (.-top nb) (* y (/ next z))) client-y))))))))
(defn- navigation [] (defn- navigation []
{:title "Wheel to zoom · Space-drag or middle-drag to pan" {:title "Two-finger scroll to pan · Pinch or ⌘/Ctrl-wheel to zoom · Space-drag or middle-drag to pan"
:ref (fn [el] :ref (fn [el]
(when el (when el
(set! (.-onwheel el) (set! (.-onwheel el)
(fn [e] (fn [e]
(.preventDefault e) (.preventDefault e)
(let [z @(rf/subscribe [::layout/zoom :stage]) ;; Chromium and Firefox deliver trackpad pinch as Ctrl-wheel.
wrap (.querySelector el ".stage-wrap") ;; Ordinary scrolling pans without guessing the input device.
wb (.getBoundingClientRect wrap) (when-not @pinch-start-zoom
x (- (.-clientX e) (.-left wb)) y (- (.-clientY e) (.-top wb)) (let [unit (case (.-deltaMode e) 1 16 2 (.-clientHeight el) 1)
next (max 0.1 (min 16 (* z (js/Math.exp (* -0.002 (.-deltaY e))))))] dx (* unit (.-deltaX e)) dy (* unit (.-deltaY e))]
(rf/dispatch-sync [::layout/set :stage next]) (if (or (.-ctrlKey e) (.-metaKey e))
(js/requestAnimationFrame (zoom-at! el (.-clientX e) (.-clientY e)
(fn [] (* @(rf/subscribe [::layout/zoom :stage])
(let [nb (.getBoundingClientRect wrap)] (js/Math.exp (* (if (.-ctrlKey e) -0.01 -0.002) dy))))
(set! (.-scrollLeft el) (+ (.-scrollLeft el) (- (+ (.-left nb) (* x (/ next z))) (.-clientX e)))) (do
(set! (.-scrollTop el) (+ (.-scrollTop el) (- (+ (.-top nb) (* y (/ next z))) (.-clientY e))))))))))) (set! (.-scrollLeft el) (+ (.-scrollLeft el) dx))
(set! (.-scrollTop el) (+ (.-scrollTop el) dy))))))))
;; Safari exposes pinch as GestureEvents, with cumulative scale.
(set! (.-ongesturestart el)
(fn [e]
(.preventDefault e)
(reset! pinch-start-zoom @(rf/subscribe [::layout/zoom :stage]))))
(set! (.-ongesturechange el)
(fn [e]
(.preventDefault e)
(when-let [z @pinch-start-zoom]
(zoom-at! el (.-clientX e) (.-clientY e) (* z (.-scale e))))))
(set! (.-ongestureend el)
(fn [e] (.preventDefault e) (reset! pinch-start-zoom nil))))
nil) nil)
:on-pointer-down-capture :on-pointer-down-capture
(fn [e] (when (or (= 1 (.-button e)) (and (= 0 (.-button e)) @space-held?)) (fn [e] (when (or (= 1 (.-button e)) (and (= 0 (.-button e)) @space-held?))