diff --git a/.vscode/settings.json b/.vscode/settings.json index 5c2a6e7b..41438750 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,7 +16,11 @@ "mindmap", "Pageview", "pako", + "panmove", + "panstart", "panzoom", + "pinchmove", + "pinchstart", "pzoom", "roughjs", "Serde", diff --git a/package.json b/package.json index b7ce7b12..9f9f3604 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@sveltejs/adapter-static": "3.0.8", "@sveltejs/kit": "2.20.3", "@sveltejs/vite-plugin-svelte": "^4.0.4", + "@types/hammerjs": "^2.0.46", "@types/lodash-es": "^4.17.12", "@types/node": "^22.14.1", "@types/pako": "2.0.3", @@ -90,6 +91,7 @@ "@mermaid-js/mermaid-zenuml": "^0.2.0", "codemirror": "^6.0.1", "dayjs": "^1.11.13", + "hammerjs": "^2.0.8", "js-base64": "3.7.7", "lodash-es": "^4.17.21", "mermaid": "^11.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79cf390f..07c3ab16 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: dayjs: specifier: ^1.11.13 version: 1.11.13 + hammerjs: + specifier: ^2.0.8 + version: 2.0.8 js-base64: specifier: 3.7.7 version: 3.7.7 @@ -90,6 +93,9 @@ importers: '@sveltejs/vite-plugin-svelte': specifier: ^4.0.4 version: 4.0.4(svelte@5.26.1)(vite@5.4.16(@types/node@22.14.1)) + '@types/hammerjs': + specifier: ^2.0.46 + version: 2.0.46 '@types/lodash-es': specifier: ^4.17.12 version: 4.17.12 @@ -907,6 +913,9 @@ packages: '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/hammerjs@2.0.46': + resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -2070,6 +2079,10 @@ packages: hachure-fill@0.5.2: resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + hammerjs@2.0.8: + resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==} + engines: {node: '>=0.8.0'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -4493,6 +4506,8 @@ snapshots: '@types/geojson@7946.0.16': {} + '@types/hammerjs@2.0.46': {} + '@types/istanbul-lib-coverage@2.0.6': {} '@types/json-schema@7.0.15': {} @@ -5891,6 +5906,8 @@ snapshots: hachure-fill@0.5.2: {} + hammerjs@2.0.8: {} + has-flag@4.0.0: {} has-symbols@1.1.0: {} diff --git a/src/lib/util/panZoom.ts b/src/lib/util/panZoom.ts index 33d50a87..e1d664e0 100644 --- a/src/lib/util/panZoom.ts +++ b/src/lib/util/panZoom.ts @@ -1,4 +1,5 @@ import type { State } from '$/types'; +import Hammer from 'hammerjs'; import type { Point } from 'mermaid/dist/types.js'; import panzoom from 'svg-pan-zoom'; type PanZoom = typeof panzoom; @@ -24,9 +25,50 @@ export class PanZoomState { } public updateElement(diagramView: SVGElement, { pan, zoom }: Pick) { + this.pzoom?.destroy(); + let hammer: HammerManager | undefined; this.pzoom = panzoom(diagramView, { center: true, controlIconsEnabled: false, + customEventsHandler: { + haltEventListeners: ['touchstart', 'touchend', 'touchmove', 'touchleave', 'touchcancel'], + init: function (options) { + const instance = options.instance; + let initialScale = 1; + let pannedX = 0; + let pannedY = 0; + hammer = new Hammer(options.svgElement); + hammer.get('pinch').set({ enable: true }); + hammer.on('panstart panmove', function (event) { + if (event.type === 'panstart') { + pannedX = 0; + pannedY = 0; + } + instance.panBy({ x: event.deltaX - pannedX, y: event.deltaY - pannedY }); + pannedX = event.deltaX; + pannedY = event.deltaY; + }); + hammer.on('pinchstart pinchmove', function (event) { + if (event.type === 'pinchstart') { + initialScale = instance.getZoom(); + instance.zoomAtPoint(initialScale * event.scale, { + x: event.center.x, + y: event.center.y + }); + } + instance.zoomAtPoint(initialScale * event.scale, { + x: event.center.x, + y: event.center.y + }); + }); + options.svgElement.addEventListener('touchmove', function (event) { + event.preventDefault(); + }); + }, + destroy: function () { + hammer?.destroy(); + } + }, fit: true, maxZoom: 12, minZoom: 0.2,