Add touch support for pan and pinch-zoom on iOS
This commit is contained in:
parent
c3844db2bd
commit
575914b194
|
|
@ -6,7 +6,7 @@
|
|||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
|
||||
<link rel="icon" type="image/png" sizes="128x128" href="/favicon-128.png" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>OpenPencil</title>
|
||||
<style>
|
||||
body { margin: 0; background: #1e1e1e; }
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ const cursor = computed(() => {
|
|||
<template>
|
||||
<CanvasContextMenu>
|
||||
<div class="canvas-area relative flex-1 min-w-0 min-h-0 overflow-hidden">
|
||||
<canvas ref="canvasRef" :style="{ cursor }" class="block size-full" />
|
||||
<canvas ref="canvasRef" :style="{ cursor }" class="block size-full touch-none" />
|
||||
</div>
|
||||
</CanvasContextMenu>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1046,6 +1046,101 @@ export function useCanvasInput(
|
|||
})
|
||||
}
|
||||
|
||||
// Touch support for iOS/mobile: single-finger pan, two-finger pinch-zoom
|
||||
let activeTouches: Touch[] = []
|
||||
let pinchStartDist = 0
|
||||
let pinchStartZoom = 0
|
||||
let pinchMidX = 0
|
||||
let pinchMidY = 0
|
||||
|
||||
function touchDist(a: Touch, b: Touch) {
|
||||
return Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY)
|
||||
}
|
||||
|
||||
function onTouchStart(e: TouchEvent) {
|
||||
e.preventDefault()
|
||||
activeTouches = Array.from(e.touches)
|
||||
const canvas = canvasRef.value
|
||||
if (!canvas) return
|
||||
|
||||
if (activeTouches.length === 2) {
|
||||
drag.value = null
|
||||
const [a, b] = activeTouches
|
||||
pinchStartDist = touchDist(a, b)
|
||||
pinchStartZoom = store.state.zoom
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
pinchMidX = (a.clientX + b.clientX) / 2 - rect.left
|
||||
pinchMidY = (a.clientY + b.clientY) / 2 - rect.top
|
||||
} else if (activeTouches.length === 1) {
|
||||
const t = activeTouches[0]
|
||||
drag.value = {
|
||||
type: 'pan',
|
||||
startScreenX: t.clientX,
|
||||
startScreenY: t.clientY,
|
||||
startPanX: store.state.panX,
|
||||
startPanY: store.state.panY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onTouchMove(e: TouchEvent) {
|
||||
e.preventDefault()
|
||||
activeTouches = Array.from(e.touches)
|
||||
const canvas = canvasRef.value
|
||||
if (!canvas) return
|
||||
|
||||
if (activeTouches.length === 2) {
|
||||
const [a, b] = activeTouches
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
const newMidX = (a.clientX + b.clientX) / 2 - rect.left
|
||||
const newMidY = (a.clientY + b.clientY) / 2 - rect.top
|
||||
|
||||
const newDist = touchDist(a, b)
|
||||
if (pinchStartDist > 0) {
|
||||
const scale = newDist / pinchStartDist
|
||||
const newZoom = Math.max(0.02, Math.min(256, pinchStartZoom * scale))
|
||||
const zoomRatio = newZoom / store.state.zoom
|
||||
|
||||
const panDx = newMidX - pinchMidX
|
||||
const panDy = newMidY - pinchMidY
|
||||
|
||||
store.state.panX = pinchMidX - (pinchMidX - store.state.panX) * zoomRatio + panDx
|
||||
store.state.panY = pinchMidY - (pinchMidY - store.state.panY) * zoomRatio + panDy
|
||||
store.state.zoom = newZoom
|
||||
}
|
||||
|
||||
pinchMidX = newMidX
|
||||
pinchMidY = newMidY
|
||||
store.requestRepaint()
|
||||
} else if (activeTouches.length === 1 && drag.value?.type === 'pan') {
|
||||
const t = activeTouches[0]
|
||||
const d = drag.value
|
||||
store.state.panX = d.startPanX + (t.clientX - d.startScreenX)
|
||||
store.state.panY = d.startPanY + (t.clientY - d.startScreenY)
|
||||
store.requestRepaint()
|
||||
}
|
||||
}
|
||||
|
||||
function onTouchEnd(e: TouchEvent) {
|
||||
e.preventDefault()
|
||||
activeTouches = Array.from(e.touches)
|
||||
|
||||
if (activeTouches.length === 0) {
|
||||
drag.value = null
|
||||
pinchStartDist = 0
|
||||
} else if (activeTouches.length === 1) {
|
||||
const t = activeTouches[0]
|
||||
drag.value = {
|
||||
type: 'pan',
|
||||
startScreenX: t.clientX,
|
||||
startScreenY: t.clientY,
|
||||
startPanX: store.state.panX,
|
||||
startPanY: store.state.panY
|
||||
}
|
||||
pinchStartDist = 0
|
||||
}
|
||||
}
|
||||
|
||||
useEventListener(canvasRef, 'dblclick', onDblClick)
|
||||
useEventListener(canvasRef, 'mousedown', onMouseDown)
|
||||
useEventListener(canvasRef, 'mousemove', onMouseMove)
|
||||
|
|
@ -1055,6 +1150,10 @@ export function useCanvasInput(
|
|||
store.setHoveredNode(null)
|
||||
})
|
||||
useEventListener(canvasRef, 'wheel', onWheel, { passive: false })
|
||||
useEventListener(canvasRef, 'touchstart', onTouchStart, { passive: false })
|
||||
useEventListener(canvasRef, 'touchmove', onTouchMove, { passive: false })
|
||||
useEventListener(canvasRef, 'touchend', onTouchEnd, { passive: false })
|
||||
useEventListener(canvasRef, 'touchcancel', onTouchEnd, { passive: false })
|
||||
|
||||
return {
|
||||
drag,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ useEventListener(
|
|||
{ passive: false }
|
||||
)
|
||||
|
||||
// Prevent Safari's native pinch-zoom gesture on the page
|
||||
useEventListener(document, 'gesturestart', (e: Event) => e.preventDefault(), { passive: false })
|
||||
useEventListener(document, 'gesturechange', (e: Event) => e.preventDefault(), { passive: false })
|
||||
useEventListener(document, 'gestureend', (e: Event) => e.preventDefault(), { passive: false })
|
||||
|
||||
const params = useUrlSearchParams('history')
|
||||
const showChrome = !('no-chrome' in params)
|
||||
if (!('test' in params)) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue