Skip to content

Commit

Permalink
ok
Browse files Browse the repository at this point in the history
  • Loading branch information
ElyaConrad committed Apr 13, 2024
1 parent 3dde9e3 commit 4204fb8
Show file tree
Hide file tree
Showing 8 changed files with 332 additions and 73 deletions.
23 changes: 21 additions & 2 deletions docs/src/guide/README.md
Original file line number Diff line number Diff line change
@@ -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}}

<script>
export default {
data() {
return {
dynamicComponent: null
}
},

mounted () {

// import('./lib-that-access-window-on-import').then(module => {
// this.dynamicComponent = module.default
// })
}
}
</script>
160 changes: 160 additions & 0 deletions zoompinch-vue/README.md
Original file line number Diff line number Diff line change
@@ -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<InstanceType<typeof ProjectionLayer>>();

// 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
<projection-layer
ref="projectionLayerRef"
v-model:transform="transform"
:width="1536"
:height="2048"
:offset="{ top: 10, right: 10, bottom: 10, left: 10 }"
:min-scale="0.1"
:max-scale="10"
:rotation="true"
:bounds="false"
mouse
touch
wheel
gesture
>
<template #canvas>
<img
src="https://imagedelivery.net/mudX-CmAqIANL8bxoNCToA/489df5b2-38ce-46e7-32e0-d50170e8d800/public" style="width: 1536px; height: 2048px;" />
</template>
<template #matrix="{ composePoint }">
<svg xmlns="http://www.w3.org/2000/svg" @click="handleClickOnLayer">
<!-- This circle will stick to the center of the canvas -->
<circle :cx="composePoint(0.5, 0.5)[0]" :cx="composePoint(0.5, 0.5)[1]" r="5" style="fill: #f00;" />
</svg>
</template>
</projection-layer>
```

#### 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
<!-- ... -->
<template #matrix="{ composePoint }">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
<!-- This circle would stick to the center of the canvas -->
<circle :cx="composePoint(0.5, 0.5)[0]" :cy="composePoint(0.5, 0.5)[1]" />
</svg>
</template>
<!-- ... -->
```
2 changes: 1 addition & 1 deletion zoompinch-vue/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "zoompinch",
"private": false,
"version": "0.0.5",
"version": "0.0.6",
"type": "module",
"files": [
"package.json",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div
ref="projectionLayerRef"
class="projection-layer"
ref="zoompinchRef"
class="zoompinch"
:style="{
'--canvas-width': `${width}px`,
'--canvas-height': `${height}px`,
Expand All @@ -22,7 +22,7 @@
@wheel="wheelProxy"
@touchstart="touchstartProxy"
@mousedown="mousedownProxy"
@gesturestart="handleGesturestart"
@gesturestart="gesturestartProxy"
>
<div ref="canvasRef" class="canvas">
<slot name="canvas" />
Expand All @@ -35,7 +35,13 @@
<circle :cx="composePoint(1, 1)[0]" :cy="composePoint(1, 1)[1]" r="5" />
<circle :cx="composePoint(0, 1)[0]" :cy="composePoint(0, 1)[1]" r="5" />
</svg> -->
<slot name="matrix" />
<slot
name="matrix"
:compose-point="composePoint"
:compose="compose"
:clientCoordinatesToCanvasCoordinates="clientCoordinatesToCanvasCoordinates"
:normalizeMatrixCoordinates="normalizeMatrixCoordinates"
/>
</div>
</div>
</template>
Expand Down Expand Up @@ -70,6 +76,7 @@ const props = withDefaults(
mouse?: boolean;
wheel?: boolean;
touch?: boolean;
gesture?: boolean;
}>(),
{
transform: () => ({ x: 0, y: 0, scale: 1, rotate: 0 }),
Expand All @@ -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<HTMLElement>();
const zoompinchRef = ref<HTMLElement>();
const canvasRef = ref<HTMLElement>();
const matrixRef = ref<HTMLElement>();
Expand Down Expand Up @@ -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'),
Expand All @@ -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 }
);
Expand Down Expand Up @@ -206,32 +210,50 @@ 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,
});
</script>

<style scoped lang="scss">
.projection-layer {
.zoompinch {
width: 100%;
height: 100%;
position: relative;
Expand Down
35 changes: 35 additions & 0 deletions zoompinch-vue/src/controllers/gesture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Ref } from 'vue';
import { degreeToRadians } from './helpers';

export function useGesture(
rotate: Ref<number>,
rotationEnabled: Ref<boolean>,
rotateCanvas: (x: number, y: number, angle: number) => void,
normalizeMatrixCoordinates: (clientX: number, clientY: number) => [number, number]
) {
let gestureStartRotation = 0;
const handleGesturestart = (event: UIEvent) => {
gestureStartRotation = rotate.value;
};
const handleGesturechange = (event: any) => {
if (rotationEnabled.value === false) {
return;
}
const currRotation = (event as any).rotation as number;
if (currRotation === 0) {
return;
}

const relPos = normalizeMatrixCoordinates(event.clientX, event.clientY);
rotateCanvas(relPos[0], relPos[1], gestureStartRotation + degreeToRadians(currRotation));
};
const handleGestureend = (event: any) => {
//console.log('gestureend', event);
};

return {
handleGesturestart,
handleGesturechange,
handleGestureend,
};
}
Loading

0 comments on commit 4204fb8

Please sign in to comment.