From 4204fb8fb9b820335e603073e4ebfed01db3588e Mon Sep 17 00:00:00 2001 From: Maurice Conrad Date: Sat, 13 Apr 2024 19:48:30 +0200 Subject: [PATCH] ok --- docs/src/guide/README.md | 23 ++- zoompinch-vue/README.md | 160 ++++++++++++++++++ zoompinch-vue/package.json | 2 +- .../{ProjectionLayer.vue => Zoompinch.vue} | 70 +++++--- zoompinch-vue/src/controllers/gesture.ts | 35 ++++ zoompinch-vue/src/controllers/helpers.ts | 5 + zoompinch-vue/src/controllers/zoom.ts | 108 +++++++----- zoompinch-vue/src/index.ts | 2 +- 8 files changed, 332 insertions(+), 73 deletions(-) create mode 100644 zoompinch-vue/README.md rename zoompinch-vue/src/components/{ProjectionLayer.vue => Zoompinch.vue} (83%) create mode 100644 zoompinch-vue/src/controllers/gesture.ts diff --git a/docs/src/guide/README.md b/docs/src/guide/README.md index fc82aec..176d395 100755 --- a/docs/src/guide/README.md +++ b/docs/src/guide/README.md @@ -1,5 +1,24 @@ # Introduction -VuePress is composed of two parts: a [minimalistic static site generator](https://github.com/vuejs/vuepress/tree/master/packages/%40vuepress/core) with a Vue-powered [theming system](https://v1.vuepress.vuejs.org/theme/) and [Plugin API](https://v1.vuepress.vuejs.org/plugin/), and a [default theme](https://v1.vuepress.vuejs.org/theme/default-theme-config.html) optimized for writing technical documentation. It was created to support the documentation needs of Vue's own sub projects. +Zoompinch let's you scale, translate and rotate any element with native-like multi-touch, mouse, wheel and gesture support. -Each page generated by VuePress has its own pre-rendered static HTML, providing great loading performance and is SEO-friendly. Once the page is loaded, however, Vue takes over the static content and turns it into a full Single-Page Application (SPA). Additional pages are fetched on demand as the user navigates around the site. +## Reactive + +The `transform` property is fully reactive. {{1 +1}} + + diff --git a/zoompinch-vue/README.md b/zoompinch-vue/README.md new file mode 100644 index 0000000..fad1ac5 --- /dev/null +++ b/zoompinch-vue/README.md @@ -0,0 +1,160 @@ +## Zoompinch + +## Try it out! + +You will get the idea of this project by trying it out and play with the reactive properties. + +### Install + +```bash +$ npm install zoompinch +``` + +### Example usage + +```typescript +import { ProjectionLayer } from 'zoompinch'; +import 'zoompich/style.css'; + +// Just the ref instance in which the component instance will live +const projectionLayerRef = ref>(); + +// A reactive transform object +const transform = ref({ + x: 0, + y: 0, + scale: 1, + rotate: 1, +}); + +function handleClickOnLayer(event: MouseEvent) { + const [x, y] = projectionLayerRef.value?.normalizeMatrixCoordinates(event.clientX, event.clientY); + + console.log('clicked at', x, y); +} +``` + +```vue + + + + +``` + +#### Properties + +- `width` and `height`: Just inner dimensions of the element in the canvas.In fact they have to fit the aspect ratio of the actual element (otherwise you will have offsets) +- `offset`: A padding that affects the "real view box" that will be used for calculations. This is important because of course the initial scale of `1` such as the initial translate of `0, 0` will be affected by this offset. Fitting your canvas into `{ scale: 1, x: 0, y: 0, rotate: 0 }` will be always the center of the view box without the offset. +- `transform`: A reactive property (that can be used via `v-model`) that holds and accepts the relative transform from the center +- `minScale` and `maxScale`: Minimum and maximum scale +- `bounds`: Boolean value wether the canvas whould fit into the bounds of the view layer. If there is an offset, it will stick to the center. Default is `false` +- `rotation`: Boolean value wether rotation is enabled. Default is `true` +- `mouse`: Boolean value, wether mouse events will be connected +- `touch`: Boolean value, wether touch events will be connected +- `wheel`: Boolean value wether wheel events will be connected +- `gesture`: Boolean value, wether gesture events will be connected + +## Scale, Move and Rotate + +Apply a pinch-and-zoom experience that’s feels native and communicates the transform reactively in both directions. + +## Mathematical correct pinch on touch + +Unlike other libraries, _Pinchzoom_ does not just uses the center point between two fingers as projection center. The fingers get correctly projected on the virtual canvas. This makes rotation + +## Matrix Layer on top + +You can use a matrix layer on top that projects any virtual point within the canvas to the real absolute coordinates. + +## Helper functions + +Because different use cases need different ways of modifying a transform, there exist a lot of helper functions + +The most easiest way to interact with the transformations is to use the reactive transform object. + +```typescript +console.log(transform.value); +/* +{ + x: 0, + y: 0, + scale: 1, + rotate: 0 +} +*/ + +transform.value = { x: 42, y: -42, scale: 1.5, rotate: 45 }; +``` + +### applyTransform + +The `applyTransform` function accepts a given scale, a given relative position within the canvas, a relative position within the layer view and scales + positions the canvas. + +Make the canvas be centered at scale 1 + +```typescript +applyTransform(1, [0.5, 0.5], [0.5, 0.5]); +``` + +Make the center of the canvas be at top-bottom of the layer + +```typescript +applyTransform(1, [0.5, 0.5], [1, 1]); +``` + +### rotateCanvas + +The `rotateCanvas` function accepts a relative x and y anchor within the canvas and a given rotation in radians. The canvas will be rotated around the relative anchor. + +Rotate canvas around its center + +```typescript +rotateCanvas(0.5, 0.5, Math.PI / 4); +``` + +### normalizeMatrixCoordinates + +The `normalizeMatrixCoordinates` method convert given clientX and clientY coordinates to relative inner-canvas coordinates + +```typescript +normalizeMatrixCoordinates(event.clientX, event.clientY); +``` + +### composePoint + +The `composePoint` returns absolute coordinates within the layer (from 0,0) for given relative inner-canvas coordinates. + +Example usage: + +```vue + + + +``` diff --git a/zoompinch-vue/package.json b/zoompinch-vue/package.json index 0efb21e..31482e1 100644 --- a/zoompinch-vue/package.json +++ b/zoompinch-vue/package.json @@ -1,7 +1,7 @@ { "name": "zoompinch", "private": false, - "version": "0.0.5", + "version": "0.0.6", "type": "module", "files": [ "package.json", diff --git a/zoompinch-vue/src/components/ProjectionLayer.vue b/zoompinch-vue/src/components/Zoompinch.vue similarity index 83% rename from zoompinch-vue/src/components/ProjectionLayer.vue rename to zoompinch-vue/src/components/Zoompinch.vue index 7e3898e..7429900 100644 --- a/zoompinch-vue/src/components/ProjectionLayer.vue +++ b/zoompinch-vue/src/components/Zoompinch.vue @@ -1,7 +1,7 @@ @@ -70,6 +76,7 @@ const props = withDefaults( mouse?: boolean; wheel?: boolean; touch?: boolean; + gesture?: boolean; }>(), { transform: () => ({ x: 0, y: 0, scale: 1, rotate: 0 }), @@ -81,13 +88,14 @@ const props = withDefaults( mouse: true, wheel: true, touch: true, + gesture: true, } ); const emit = defineEmits<{ 'update:transform': [transform: Transform]; }>(); -const projectionLayerRef = ref(); +const zoompinchRef = ref(); const canvasRef = ref(); const matrixRef = ref(); @@ -116,16 +124,19 @@ const { handleGestureend, applyTransform, rotateCanvas, + compose, exposedTransform, calcProjectionTranslate, clientCoordinatesToCanvasCoordinates, + normalizeMatrixCoordinates, transitionEnabled, transitionDuration, } = useZoom({ - wrapperElementRef: projectionLayerRef, + wrapperElementRef: zoompinchRef, canvasNaturalWidth, canvasNaturalHeight, offset, + bounds: toRef(props, 'bounds'), rotationEnabled, minScale: toRef(props, 'minScale'), maxScale: toRef(props, 'maxScale'), @@ -145,19 +156,12 @@ watch(exposedTransform, () => { watch( () => props.transform, () => { - if ( - props.transform.x !== exposedTransform.value.x || - props.transform.y !== exposedTransform.value.y || - props.transform.scale !== exposedTransform.value.scale || - props.transform.rotate !== exposedTransform.value.rotate - ) { - exposedTransform.value = { - x: props.transform.x, - y: props.transform.y, - scale: props.transform.scale, - rotate: props.transform.rotate, - }; - } + exposedTransform.value = { + x: props.transform.x, + y: props.transform.y, + scale: props.transform.scale, + rotate: props.transform.rotate, + }; }, { deep: true } ); @@ -206,24 +210,42 @@ const mouseupProxy = (event: MouseEvent) => { } }; +const gesturestartProxy = (event: any) => { + if (props.gesture) { + handleGesturestart(event); + } +}; +const gesturechangeProxy = (event: any) => { + if (props.gesture) { + handleGesturechange(event); + } +}; +const gestureendProxy = (event: any) => { + if (props.gesture) { + handleGestureend(event); + } +}; + window.addEventListener('touchmove', touchmoveProxy); window.addEventListener('touchend', touchendProxy); window.addEventListener('mouseup', mouseupProxy); window.addEventListener('mousemove', mousemoveProxy); -window.addEventListener('gesturechange', handleGesturechange); -window.addEventListener('gestureend', handleGestureend); +window.addEventListener('gesturechange', gesturechangeProxy); +window.addEventListener('gestureend', gestureendProxy); onUnmounted(() => { window.removeEventListener('touchmove', touchmoveProxy); window.removeEventListener('touchend', touchendProxy); window.removeEventListener('mouseup', mouseupProxy); window.removeEventListener('mousemove', mousemoveProxy); - window.removeEventListener('gesturechange', handleGesturechange); - window.removeEventListener('gestureend', handleGestureend); + window.removeEventListener('gesturechange', gesturechangeProxy); + window.removeEventListener('gestureend', gestureendProxy); }); defineExpose({ + compose, composePoint, clientCoordinatesToCanvasCoordinates, + normalizeMatrixCoordinates, applyTransform, calcProjectionTranslate, rotateCanvas, @@ -231,7 +253,7 @@ defineExpose({