@zoompinch/elements 0.0.15 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +364 -0
- package/dist/zoompinch-elements.es.js +104 -76
- package/dist/zoompinch-elements.umd.js +2 -2
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# @zoompinch/elements
|
|
2
|
+
|
|
3
|
+
Web Components (Custom Elements) for [@zoompinch/core](https://github.com/ElyaConrad/zoompinch) - Apply a pinch-and-zoom experience that’s feels native and communicates the transform reactively and lets you project any layer on top of the transformed canvas.
|
|
4
|
+
|
|
5
|
+
**Play with the demo:** [https://zoompinch.pages.dev](https://zoompinch.pages.dev)
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
### Mathematical correct pinch on touch
|
|
10
|
+
|
|
11
|
+
Unlike other libraries, _Zoompinch_ does not just uses the center point between two fingers as projection center. The fingers get correctly projected on the virtual canvas. This makes pinching on touch devices feel native-like.
|
|
12
|
+
|
|
13
|
+
### Touch, Wheelm, Mouse and Trackpad Gestures!
|
|
14
|
+
|
|
15
|
+
Adside of touch, mouse and wheel events, **gesture events** (Safari Desktop) are supported as well! Try it out on the [demo](https://zoompinch.pages.dev)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @zoompinch/elements
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Complete Example
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<!DOCTYPE html>
|
|
27
|
+
<html>
|
|
28
|
+
<head>
|
|
29
|
+
<script type="module">
|
|
30
|
+
import '@zoompinch/elements';
|
|
31
|
+
</script>
|
|
32
|
+
<style>
|
|
33
|
+
zoom-pinch {
|
|
34
|
+
display: block;
|
|
35
|
+
width: 800px;
|
|
36
|
+
height: 600px;
|
|
37
|
+
border: 1px solid #ddd;
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<zoom-pinch
|
|
43
|
+
id="zoomPinch"
|
|
44
|
+
translate-x="0"
|
|
45
|
+
translate-y="0"
|
|
46
|
+
scale="1"
|
|
47
|
+
rotate="0"
|
|
48
|
+
min-scale="0.5"
|
|
49
|
+
max-scale="4"
|
|
50
|
+
offset-top="0"
|
|
51
|
+
offset-right="0"
|
|
52
|
+
offset-bottom="0"
|
|
53
|
+
offset-left="0"
|
|
54
|
+
clamp-bounds="false"
|
|
55
|
+
rotation="true"
|
|
56
|
+
>
|
|
57
|
+
<img width="1536" height="2048" src="https://imagedelivery.net/mudX-CmAqIANL8bxoNCToA/489df5b2-38ce-46e7-32e0-d50170e8d800/public" />
|
|
58
|
+
|
|
59
|
+
<svg slot="matrix" width="100%" height="100%">
|
|
60
|
+
<!-- Matrix overlay content -->
|
|
61
|
+
<circle id="centerMarker" r="8" fill="red" />
|
|
62
|
+
</svg>
|
|
63
|
+
</zoom-pinch>
|
|
64
|
+
|
|
65
|
+
<script type="module">
|
|
66
|
+
const zoomPinch = document.getElementById('zoomPinch');
|
|
67
|
+
|
|
68
|
+
// Listen for updates
|
|
69
|
+
zoomPinch.addEventListener('update', () => {
|
|
70
|
+
console.log('Transform:', {
|
|
71
|
+
translateX: zoomPinch.getAttribute('translate-x'),
|
|
72
|
+
translateY: zoomPinch.getAttribute('translate-y'),
|
|
73
|
+
scale: zoomPinch.getAttribute('scale'),
|
|
74
|
+
rotate: zoomPinch.getAttribute('rotate')
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Update matrix overlay
|
|
78
|
+
updateMatrix();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Center on load
|
|
82
|
+
zoomPinch.addEventListener('init', () => {
|
|
83
|
+
zoomPinch.applyTransform(1, [0.5, 0.5], [0.5, 0.5]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Handle clicks
|
|
87
|
+
zoomPinch.addEventListener('click', (e) => {
|
|
88
|
+
const [x, y] = zoomPinch.normalizeClientCoords(e.clientX, e.clientY);
|
|
89
|
+
console.log('Canvas position:', x, y);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
function updateMatrix() {
|
|
93
|
+
const centerMarker = document.getElementById('centerMarker');
|
|
94
|
+
const [cx, cy] = zoomPinch.composePoint(
|
|
95
|
+
zoomPinch.canvasWidth / 2,
|
|
96
|
+
zoomPinch.canvasHeight / 2
|
|
97
|
+
);
|
|
98
|
+
centerMarker.setAttribute('cx', cx);
|
|
99
|
+
centerMarker.setAttribute('cy', cy);
|
|
100
|
+
}
|
|
101
|
+
</script>
|
|
102
|
+
</body>
|
|
103
|
+
</html>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## API Reference
|
|
107
|
+
|
|
108
|
+
### Attributes
|
|
109
|
+
|
|
110
|
+
| Attribute | Type | Default | Description |
|
|
111
|
+
|-----------|------|---------|-------------|
|
|
112
|
+
| `translate-x` | `number` | `0` | X translation in pixels |
|
|
113
|
+
| `translate-y` | `number` | `0` | Y translation in pixels |
|
|
114
|
+
| `scale` | `number` | `1` | Current scale factor |
|
|
115
|
+
| `rotate` | `number` | `0` | Rotation in radians |
|
|
116
|
+
| `min-scale` | `number` | `0.1` | Minimum scale (user gestures only) |
|
|
117
|
+
| `max-scale` | `number` | `10` | Maximum scale (user gestures only) |
|
|
118
|
+
| `offset-top` | `number` | `100` | Top padding in pixels |
|
|
119
|
+
| `offset-right` | `number` | `0` | Right padding in pixels |
|
|
120
|
+
| `offset-bottom` | `number` | `0` | Bottom padding in pixels |
|
|
121
|
+
| `offset-left` | `number` | `0` | Left padding in pixels |
|
|
122
|
+
| `clamp-bounds` | `"true"` \| `"false"` | `"false"` | Clamp panning within bounds (user gestures only) |
|
|
123
|
+
| `rotation` | `"true"` \| `"false"` | `"true"` | Enable rotation gestures |
|
|
124
|
+
|
|
125
|
+
**Note:** `min-scale`, `max-scale`, `rotation`, and `clamp-bounds` only apply during user interaction. Programmatic changes via methods are unrestricted.
|
|
126
|
+
|
|
127
|
+
### Events
|
|
128
|
+
|
|
129
|
+
| Event | Description |
|
|
130
|
+
|-------|-------------|
|
|
131
|
+
| `update` | Fired when transform changes (attributes are updated) |
|
|
132
|
+
| `init` | Fired when the engine is ready |
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
zoomPinch.addEventListener('update', () => {
|
|
136
|
+
const translateX = zoomPinch.getAttribute('translate-x');
|
|
137
|
+
const translateY = zoomPinch.getAttribute('translate-y');
|
|
138
|
+
const scale = zoomPinch.getAttribute('scale');
|
|
139
|
+
const rotate = zoomPinch.getAttribute('rotate');
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Methods
|
|
144
|
+
|
|
145
|
+
Access methods directly on the element:
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
const zoomPinch = document.querySelector('zoom-pinch');
|
|
149
|
+
|
|
150
|
+
// Call methods
|
|
151
|
+
zoomPinch.applyTransform(scale, wrapperCoords, canvasCoords);
|
|
152
|
+
zoomPinch.normalizeClientCoords(clientX, clientY);
|
|
153
|
+
zoomPinch.composePoint(x, y);
|
|
154
|
+
|
|
155
|
+
// Access properties
|
|
156
|
+
zoomPinch.canvasWidth;
|
|
157
|
+
zoomPinch.canvasHeight;
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### `applyTransform(scale, wrapperCoords, canvasCoords)`
|
|
161
|
+
|
|
162
|
+
Apply transform by anchoring a canvas point to a wrapper point.
|
|
163
|
+
|
|
164
|
+
**Parameters:**
|
|
165
|
+
- `scale: number` - Target scale
|
|
166
|
+
- `wrapperCoords: [number, number]` - Wrapper position (0-1, 0.5 = center)
|
|
167
|
+
- `canvasCoords: [number, number]` - Canvas position (0-1, 0.5 = center)
|
|
168
|
+
|
|
169
|
+
**Examples:**
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
// Center canvas at scale 1
|
|
173
|
+
zoomPinch.applyTransform(1, [0.5, 0.5], [0.5, 0.5]);
|
|
174
|
+
|
|
175
|
+
// Zoom to 2x, keep centered
|
|
176
|
+
zoomPinch.applyTransform(2, [0.5, 0.5], [0.5, 0.5]);
|
|
177
|
+
|
|
178
|
+
// Anchor canvas top-left to wrapper center
|
|
179
|
+
zoomPinch.applyTransform(1.5, [0.5, 0.5], [0, 0]);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### `normalizeClientCoords(clientX, clientY)`
|
|
183
|
+
|
|
184
|
+
Convert global client coordinates to canvas coordinates.
|
|
185
|
+
|
|
186
|
+
**Parameters:**
|
|
187
|
+
- `clientX: number` - Global X from event
|
|
188
|
+
- `clientY: number` - Global Y from event
|
|
189
|
+
|
|
190
|
+
**Returns:** `[number, number]` - Canvas coordinates in pixels
|
|
191
|
+
|
|
192
|
+
**Example:**
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
zoomPinch.addEventListener('click', (e) => {
|
|
196
|
+
const [x, y] = zoomPinch.normalizeClientCoords(e.clientX, e.clientY);
|
|
197
|
+
console.log('Canvas position:', x, y);
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### `composePoint(x, y)`
|
|
202
|
+
|
|
203
|
+
Convert canvas coordinates to wrapper coordinates (accounts for transform).
|
|
204
|
+
|
|
205
|
+
**Parameters:**
|
|
206
|
+
- `x: number` - Canvas X in pixels
|
|
207
|
+
- `y: number` - Canvas Y in pixels
|
|
208
|
+
|
|
209
|
+
**Returns:** `[number, number]` - Wrapper coordinates in pixels
|
|
210
|
+
|
|
211
|
+
**Example:**
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
// Get wrapper position for canvas center
|
|
215
|
+
const [wrapperX, wrapperY] = zoomPinch.composePoint(
|
|
216
|
+
zoomPinch.canvasWidth / 2,
|
|
217
|
+
zoomPinch.canvasHeight / 2
|
|
218
|
+
);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Properties
|
|
222
|
+
|
|
223
|
+
Access current canvas dimensions:
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
const width = zoomPinch.canvasWidth; // number
|
|
227
|
+
const height = zoomPinch.canvasHeight; // number
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Matrix Slot
|
|
231
|
+
|
|
232
|
+
Use `slot="matrix"` for overlay elements that follow the canvas transform.
|
|
233
|
+
|
|
234
|
+
**Note:** Matrix elements must be updated manually on the `update` event.
|
|
235
|
+
|
|
236
|
+
**Example:**
|
|
237
|
+
|
|
238
|
+
```html
|
|
239
|
+
<zoom-pinch id="zoomPinch">
|
|
240
|
+
<img width="1920" height="1080" src="image.jpg" />
|
|
241
|
+
|
|
242
|
+
<svg slot="matrix" width="100%" height="100%">
|
|
243
|
+
<circle id="marker" r="8" fill="red" />
|
|
244
|
+
</svg>
|
|
245
|
+
</zoom-pinch>
|
|
246
|
+
|
|
247
|
+
<script>
|
|
248
|
+
const viewer = document.getElementById('zoomPinch');
|
|
249
|
+
const marker = document.getElementById('marker');
|
|
250
|
+
|
|
251
|
+
zoomPinch.addEventListener('update', () => {
|
|
252
|
+
const [cx, cy] = zoomPinch.composePoint(
|
|
253
|
+
zoomPinch.canvasWidth / 2,
|
|
254
|
+
zoomPinch.canvasHeight / 2
|
|
255
|
+
);
|
|
256
|
+
marker.setAttribute('cx', cx);
|
|
257
|
+
marker.setAttribute('cy', cy);
|
|
258
|
+
});
|
|
259
|
+
</script>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Coordinate Systems
|
|
263
|
+
|
|
264
|
+
### 1. Canvas Coordinates (Absolute)
|
|
265
|
+
|
|
266
|
+
Absolute pixels within canvas content.
|
|
267
|
+
- Origin: `(0, 0)` at top-left
|
|
268
|
+
- Range: `0` to `canvasWidth`, `0` to `canvasHeight`
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
const [canvasX, canvasY] = zoomPinch.normalizeClientCoords(event.clientX, event.clientY);
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### 2. Wrapper Coordinates (Absolute)
|
|
275
|
+
|
|
276
|
+
Absolute pixels within viewport/wrapper.
|
|
277
|
+
- Origin: `(0, 0)` at top-left (accounting for offset)
|
|
278
|
+
- Range: `0` to `wrapperWidth`, `0` to `wrapperHeight`
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
const [wrapperX, wrapperY] = zoomPinch.composePoint(canvasX, canvasY);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### 3. Relative Coordinates (0-1)
|
|
285
|
+
|
|
286
|
+
Normalized coordinates for `applyTransform`.
|
|
287
|
+
- Range: `0.0` to `1.0`
|
|
288
|
+
- `0.5` = center, `1.0` = bottom-right
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
[0, 0] // top-left
|
|
292
|
+
[0.5, 0.5] // center
|
|
293
|
+
[1, 1] // bottom-right
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Conversion Flow:**
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
Client Coords → normalizeClientCoords() → Canvas Coords → composePoint() → Wrapper Coords
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Best Practices
|
|
303
|
+
|
|
304
|
+
1. **Always specify image dimensions** to avoid layout shifts:
|
|
305
|
+
```html
|
|
306
|
+
<img width="1920" height="1080" src="image.jpg" />
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
2. **Center content on init:**
|
|
310
|
+
```javascript
|
|
311
|
+
zoomPinch.addEventListener('init', () => {
|
|
312
|
+
zoomPinch.applyTransform(1, [0.5, 0.5], [0.5, 0.5]);
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
3. **Prevent image drag:**
|
|
317
|
+
```html
|
|
318
|
+
<img src="image.jpg" draggable="false" style="user-select: none;" />
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
4. **Update matrix overlays on transform change:**
|
|
322
|
+
```javascript
|
|
323
|
+
zoomPinch.addEventListener('update', updateMatrix);
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Styling
|
|
327
|
+
|
|
328
|
+
The element uses Shadow DOM. Style the host:
|
|
329
|
+
|
|
330
|
+
```css
|
|
331
|
+
zoom-pinch {
|
|
332
|
+
display: block;
|
|
333
|
+
width: 800px;
|
|
334
|
+
height: 600px;
|
|
335
|
+
border: 1px solid #ccc;
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Internal structure (Shadow DOM):**
|
|
340
|
+
|
|
341
|
+
```css
|
|
342
|
+
:host /* Container */
|
|
343
|
+
.content /* Wrapper */
|
|
344
|
+
.canvas /* Canvas wrapper */
|
|
345
|
+
.matrix /* Matrix overlay */
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Browser Support
|
|
349
|
+
|
|
350
|
+
- ✅ Chrome/Edge (latest)
|
|
351
|
+
- ✅ Firefox (latest)
|
|
352
|
+
- ✅ Safari (latest, including iOS)
|
|
353
|
+
- ✅ Mobile browsers (iOS Safari, Chrome Mobile)
|
|
354
|
+
|
|
355
|
+
## License
|
|
356
|
+
|
|
357
|
+
MIT
|
|
358
|
+
|
|
359
|
+
## Related
|
|
360
|
+
|
|
361
|
+
- [@zoompinch/core](https://www.npmjs.com/package/@zoompinch/core) - Core engine
|
|
362
|
+
- [@zoompinch/vue](https://www.npmjs.com/package/@zoompinch/vue) - Vue 3 bindings
|
|
363
|
+
|
|
364
|
+
Built with ❤️ by Elya Maurice Conrad
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
var
|
|
1
|
+
var N = Object.defineProperty;
|
|
2
|
+
var x = (r, t, e) => t in r ? N(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
|
|
3
|
+
var T = (r, t, e) => x(r, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
var y = Object.defineProperty, A = (r, t, e) => t in r ? y(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e, v = (r, t, e) => A(r, typeof t != "symbol" ? t + "" : t, e);
|
|
5
5
|
function z(r) {
|
|
6
6
|
return r * Math.PI / 180;
|
|
7
7
|
}
|
|
8
|
-
function
|
|
8
|
+
function Y(r, t, e) {
|
|
9
9
|
return Math.min(Math.max(r, t), e);
|
|
10
10
|
}
|
|
11
|
-
function
|
|
12
|
-
const [s, n] = r, [a,
|
|
13
|
-
return [
|
|
11
|
+
function w(r, t, e) {
|
|
12
|
+
const [s, n] = r, [a, i] = t, h = Math.cos(e) * (s - a) - Math.sin(e) * (n - i) + a, c = Math.sin(e) * (s - a) + Math.cos(e) * (n - i) + i;
|
|
13
|
+
return [h, c];
|
|
14
14
|
}
|
|
15
|
-
function
|
|
15
|
+
function S(r, t) {
|
|
16
16
|
const e = Math.pow(10, t);
|
|
17
17
|
return Math.round(r * e) / e;
|
|
18
18
|
}
|
|
19
|
-
function
|
|
19
|
+
function F(r) {
|
|
20
20
|
var t = !1;
|
|
21
21
|
return r.wheelDeltaY ? r.wheelDeltaY === r.deltaY * -3 && (t = !0) : r.deltaMode === 0 && (t = !0), t;
|
|
22
22
|
}
|
|
23
|
-
function
|
|
23
|
+
function X(r, t) {
|
|
24
24
|
const e = t.find((s) => r % s === 0);
|
|
25
25
|
return e ? r / e : 1;
|
|
26
26
|
}
|
|
27
|
-
function
|
|
28
|
-
let a = r.left - t,
|
|
29
|
-
const
|
|
30
|
-
let l = a *
|
|
31
|
-
const d = r.width / s,
|
|
32
|
-
return l /= s, u /= s, { x:
|
|
27
|
+
function I(r, t, e, s, n) {
|
|
28
|
+
let a = r.left - t, i = r.top - e;
|
|
29
|
+
const h = Math.cos(-n), c = Math.sin(-n);
|
|
30
|
+
let l = a * h - i * c, u = a * c + i * h;
|
|
31
|
+
const d = r.width / s, g = r.height / s;
|
|
32
|
+
return l /= s, u /= s, { x: S(l, 4), y: S(u, 4), width: S(d, 4), height: S(g, 4) };
|
|
33
33
|
}
|
|
34
|
-
class
|
|
35
|
-
constructor(t, e, s, n, a,
|
|
36
|
-
super(),
|
|
37
|
-
const
|
|
38
|
-
const { x:
|
|
39
|
-
this.wrapperBounds = { x:
|
|
40
|
-
}),
|
|
41
|
-
const { x:
|
|
42
|
-
this.canvasBounds = { x:
|
|
34
|
+
class W extends EventTarget {
|
|
35
|
+
constructor(t, e, s, n, a, i, h = 0.1, c = 10, l = !1, u = !0) {
|
|
36
|
+
super(), v(this, "wrapperBounds"), v(this, "canvasBounds"), v(this, "gestureStartRotate", 0), v(this, "dragStart", null), v(this, "dragStartFrozenX", null), v(this, "dragStartFrozenY", null), v(this, "touchStarts", null), v(this, "touchStartTranslateX", 0), v(this, "touchStartTranslateY", 0), this.element = t, this.offset = e, this.translateX = s, this.translateY = n, this.scale = a, this.rotate = i, this.minScale = h, this.maxScale = c, this.clampBounds = l, this.rotation = u;
|
|
37
|
+
const d = new ResizeObserver(() => {
|
|
38
|
+
const { x: o, y: p, width: m, height: f } = this.element.getBoundingClientRect();
|
|
39
|
+
this.wrapperBounds = { x: o, y: p, width: m, height: f }, this.update();
|
|
40
|
+
}), g = new ResizeObserver(() => {
|
|
41
|
+
const { x: o, y: p, width: m, height: f } = I(this.canvasElement.getBoundingClientRect(), this.renderingTranslateX, this.renderingTranslateY, this.renderinScale, this.renderingRotate);
|
|
42
|
+
this.canvasBounds = { x: o, y: p, width: m, height: f }, this.update();
|
|
43
43
|
});
|
|
44
44
|
requestAnimationFrame(() => {
|
|
45
45
|
this.wrapperBounds = this.element.getBoundingClientRect(), this.canvasBounds = this.canvasElement.getBoundingClientRect(), this.update(), this.dispatchEvent(new Event("init"));
|
|
46
|
-
}),
|
|
46
|
+
}), g.observe(this.canvasElement), d.observe(this.element);
|
|
47
47
|
}
|
|
48
48
|
get canvasElement() {
|
|
49
49
|
return this.element.querySelector(".canvas");
|
|
@@ -71,14 +71,23 @@ class L extends EventTarget {
|
|
|
71
71
|
get naturalScale() {
|
|
72
72
|
return this.canvasNaturalRatio >= this.wrapperInnerRatio ? this.wrapperInnerWidth / this.canvasBounds.width : this.wrapperInnerHeight / this.canvasBounds.height;
|
|
73
73
|
}
|
|
74
|
+
// The clamping is an explicit user intention
|
|
75
|
+
// The reason is that we do not want side effects when toggling the clamp
|
|
76
|
+
setTranslateFromUserGesture(t, e) {
|
|
77
|
+
if (this.clampBounds) {
|
|
78
|
+
const s = this.clampTranslate({ translateX: t, translateY: e, scale: this.scale, rotate: this.rotate });
|
|
79
|
+
this.translateX = s.translateX, this.translateY = s.translateY, this.rotate = 0;
|
|
80
|
+
} else
|
|
81
|
+
this.translateX = t, this.translateY = e;
|
|
82
|
+
}
|
|
74
83
|
handleGesturestart(t) {
|
|
75
84
|
this.gestureStartRotate = this.rotate;
|
|
76
85
|
}
|
|
77
86
|
handleGesturechange(t) {
|
|
78
87
|
const { clientX: e, clientY: s } = t, n = t.rotation;
|
|
79
|
-
if (n === 0)
|
|
88
|
+
if (n === 0 || !this.rotation)
|
|
80
89
|
return;
|
|
81
|
-
const a = this.
|
|
90
|
+
const a = this.normalizeClientCoords(e, s);
|
|
82
91
|
this.rotateCanvas(a[0], a[1], this.gestureStartRotate + z(n));
|
|
83
92
|
}
|
|
84
93
|
handleGestureend(t) {
|
|
@@ -92,19 +101,19 @@ class L extends EventTarget {
|
|
|
92
101
|
handleMousemove(t) {
|
|
93
102
|
if (t.preventDefault(), this.dragStart && this.dragStartFrozenX !== null && this.dragStartFrozenY !== null) {
|
|
94
103
|
const e = t.clientX - this.dragStart[0], s = t.clientY - this.dragStart[1], n = this.dragStartFrozenX - -e, a = this.dragStartFrozenY - -s;
|
|
95
|
-
this.
|
|
104
|
+
this.setTranslateFromUserGesture(n, a), this.update();
|
|
96
105
|
}
|
|
97
106
|
}
|
|
98
107
|
handleWheel(t) {
|
|
99
108
|
let { deltaX: e, deltaY: s, ctrlKey: n } = t;
|
|
100
|
-
const a = [120, 100],
|
|
101
|
-
|
|
102
|
-
const
|
|
109
|
+
const a = [120, 100], i = 2;
|
|
110
|
+
F(t) || ((Math.abs(e) === 120 || Math.abs(e) === 200) && (e = e / (100 / i * X(e, a)) * Math.sign(e)), (Math.abs(s) === 120 || Math.abs(s) === 200) && (s = s / (100 / i * X(s, a)) * Math.sign(s)));
|
|
111
|
+
const h = this.scale;
|
|
103
112
|
if (n) {
|
|
104
|
-
const c = -s / 100 *
|
|
105
|
-
this.
|
|
113
|
+
const c = -s / 100 * h, l = Y(h + c, this.minScale, this.maxScale), u = this.relativeWrapperCoordinatesFromClientCoords(t.clientX, t.clientY), [d, g] = this.calcProjectionTranslate(l, u, this.normalizeMatrixCoordinates(t.clientX, t.clientY));
|
|
114
|
+
this.setTranslateFromUserGesture(d, g), this.scale = l;
|
|
106
115
|
} else
|
|
107
|
-
this.
|
|
116
|
+
this.setTranslateFromUserGesture(this.translateX - e, this.translateY - s);
|
|
108
117
|
this.update(), t.preventDefault();
|
|
109
118
|
}
|
|
110
119
|
freezeTouches(t) {
|
|
@@ -124,15 +133,18 @@ class L extends EventTarget {
|
|
|
124
133
|
const e = Array.from(t.touches).map((s) => this.clientCoordsToWrapperCoords(s.clientX, s.clientY));
|
|
125
134
|
if (this.touchStarts) {
|
|
126
135
|
if (e.length >= 2 && this.touchStarts.length >= 2) {
|
|
127
|
-
const s = [this.touchStarts[0].canvasRel[0] * this.canvasBounds.width, this.touchStarts[0].canvasRel[1] * this.canvasBounds.height], n = [this.touchStarts[1].canvasRel[0] * this.canvasBounds.width, this.touchStarts[1].canvasRel[1] * this.canvasBounds.height], a = Math.sqrt(Math.pow(s[0] - n[0], 2) + Math.pow(s[1] - n[1], 2)),
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
136
|
+
const s = [this.touchStarts[0].canvasRel[0] * this.canvasBounds.width, this.touchStarts[0].canvasRel[1] * this.canvasBounds.height], n = [this.touchStarts[1].canvasRel[0] * this.canvasBounds.width, this.touchStarts[1].canvasRel[1] * this.canvasBounds.height], a = Math.sqrt(Math.pow(s[0] - n[0], 2) + Math.pow(s[1] - n[1], 2)), i = Math.sqrt(Math.pow(e[0][0] - e[1][0], 2) + Math.pow(e[0][1] - e[1][1], 2)) / this.naturalScale, h = Y(i / a, this.minScale, this.maxScale), c = [e[0][0] / this.wrapperInnerWidth, e[0][1] / this.wrapperInnerHeight], l = this.touchStarts[0].canvasRel, [u, d] = this.calcProjectionTranslate(h, c, l, 0);
|
|
137
|
+
if (this.rotation) {
|
|
138
|
+
let g = 0, o = 0, p = 0;
|
|
139
|
+
const m = Math.atan2(n[1] - s[1], n[0] - s[0]);
|
|
140
|
+
p = Math.atan2(e[1][1] - e[0][1], e[1][0] - e[0][0]) - m;
|
|
141
|
+
const f = (R, E) => [this.offset.left + this.canvasBounds.width * R * this.naturalScale * h + u, this.offset.top + this.canvasBounds.height * E * this.naturalScale * h + d], b = f(0, 0), C = f(this.touchStarts[0].canvasRel[0], this.touchStarts[0].canvasRel[1]), B = w(b, C, p);
|
|
142
|
+
g = B[0] - b[0], o = B[1] - b[1], this.scale = h, this.rotate = p, this.setTranslateFromUserGesture(u + g, d + o);
|
|
143
|
+
} else
|
|
144
|
+
this.scale = h, this.setTranslateFromUserGesture(u, d);
|
|
133
145
|
} else {
|
|
134
|
-
const s = t.touches[0].clientX - this.touchStarts[0].client[0], n = t.touches[0].clientY - this.touchStarts[0].client[1], a = this.touchStartTranslateX + s,
|
|
135
|
-
this.
|
|
146
|
+
const s = t.touches[0].clientX - this.touchStarts[0].client[0], n = t.touches[0].clientY - this.touchStarts[0].client[1], a = this.touchStartTranslateX + s, i = this.touchStartTranslateY + n;
|
|
147
|
+
this.setTranslateFromUserGesture(a, i);
|
|
136
148
|
}
|
|
137
149
|
this.update();
|
|
138
150
|
}
|
|
@@ -141,17 +153,16 @@ class L extends EventTarget {
|
|
|
141
153
|
t.touches.length === 0 ? this.touchStarts = null : (this.touchStarts = this.freezeTouches(t.touches), this.touchStartTranslateX = this.translateX, this.touchStartTranslateY = this.translateY);
|
|
142
154
|
}
|
|
143
155
|
calcProjectionTranslate(t, e, s, n) {
|
|
144
|
-
const a = this.canvasBounds.width * this.naturalScale,
|
|
145
|
-
return [
|
|
156
|
+
const a = this.canvasBounds.width * this.naturalScale, i = this.canvasBounds.height * this.naturalScale, h = s[0] * a * t, c = s[1] * i * t, l = w([h, c], [0, 0], n ?? this.rotate), u = e[0] * this.wrapperInnerWidth, d = e[1] * this.wrapperInnerHeight, g = u - l[0], o = d - l[1];
|
|
157
|
+
return [g, o];
|
|
146
158
|
}
|
|
147
159
|
applyTransform(t, e, s) {
|
|
148
|
-
console.log("....apply transform");
|
|
149
160
|
const n = this.calcProjectionTranslate(t, e, s, 0);
|
|
150
|
-
this.scale = t, this.
|
|
161
|
+
this.scale = t, this.setTranslateFromUserGesture(n[0], n[1]), this.update();
|
|
151
162
|
}
|
|
152
|
-
composeRelPoint(t, e, s, n, a,
|
|
153
|
-
s = s ?? this.scale, n = n ?? this.translateX, a = a ?? this.translateY,
|
|
154
|
-
const
|
|
163
|
+
composeRelPoint(t, e, s, n, a, i) {
|
|
164
|
+
s = s ?? this.scale, n = n ?? this.translateX, a = a ?? this.translateY, i = i ?? this.rotate;
|
|
165
|
+
const h = [this.offset.left, this.offset.top], c = [this.offset.left + this.canvasBounds.width * (s * this.naturalScale) * t, this.offset.top + this.canvasBounds.height * (s * this.naturalScale) * e], l = w(c, h, i);
|
|
155
166
|
return [l[0] + n, l[1] + a];
|
|
156
167
|
}
|
|
157
168
|
composePoint(t, e) {
|
|
@@ -159,16 +170,16 @@ class L extends EventTarget {
|
|
|
159
170
|
return this.composeRelPoint(s, n);
|
|
160
171
|
}
|
|
161
172
|
getAnchorOffset(t, e, s, n, a = [0.5, 0.5]) {
|
|
162
|
-
const
|
|
163
|
-
this.offset.left +
|
|
164
|
-
this.offset.top +
|
|
165
|
-
], c = this.composeRelPoint(a[0], a[1], t, e, s, n), l = c[0] -
|
|
173
|
+
const i = this.calcProjectionTranslate(t, a, a, 0), h = [
|
|
174
|
+
this.offset.left + i[0] + this.canvasBounds.width * (t * this.naturalScale) * a[0],
|
|
175
|
+
this.offset.top + i[1] + this.canvasBounds.height * (t * this.naturalScale) * a[1]
|
|
176
|
+
], c = this.composeRelPoint(a[0], a[1], t, e, s, n), l = c[0] - h[0], u = c[1] - h[1];
|
|
166
177
|
return [l, u];
|
|
167
178
|
}
|
|
168
179
|
// Converts absolute inner wrapper coordinates to relative canvas coordinates (0-1, 0-1)
|
|
169
180
|
getCanvasCoordsRel(t, e) {
|
|
170
|
-
const s = [0, 0], n = [t - this.translateX, e - this.translateY], a =
|
|
171
|
-
return [
|
|
181
|
+
const s = [0, 0], n = [t - this.translateX, e - this.translateY], a = w(n, s, -this.rotate), i = [a[0] / this.renderinScale, a[1] / this.renderinScale];
|
|
182
|
+
return [i[0] / this.canvasBounds.width, i[1] / this.canvasBounds.height];
|
|
172
183
|
}
|
|
173
184
|
// Converts absolute client to coordinates to absolute inner-wrapper coorinates
|
|
174
185
|
clientCoordsToWrapperCoords(t, e) {
|
|
@@ -190,8 +201,8 @@ class L extends EventTarget {
|
|
|
190
201
|
return [s * this.canvasBounds.width, n * this.canvasBounds.height];
|
|
191
202
|
}
|
|
192
203
|
rotateCanvas(t, e, s) {
|
|
193
|
-
const n = this.composeRelPoint(
|
|
194
|
-
this.
|
|
204
|
+
const n = t / this.canvasBounds.width, a = e / this.canvasBounds.height, i = this.composeRelPoint(n, a, this.scale, 0, 0, s), h = this.composeRelPoint(n, a);
|
|
205
|
+
this.setTranslateFromUserGesture(h[0] - i[0], h[1] - i[1]), this.rotate = s, this.update();
|
|
195
206
|
}
|
|
196
207
|
get renderinScale() {
|
|
197
208
|
return this.naturalScale * this.scale;
|
|
@@ -205,16 +216,23 @@ class L extends EventTarget {
|
|
|
205
216
|
get renderingRotate() {
|
|
206
217
|
return this.rotate;
|
|
207
218
|
}
|
|
219
|
+
clampTranslate(t, e = [0.5, 0.5]) {
|
|
220
|
+
const s = this.canvasBounds.width * this.naturalScale * t.scale, n = this.canvasBounds.height * this.naturalScale * t.scale, a = s - this.wrapperInnerWidth, i = n - this.wrapperInnerHeight, h = a > 0 ? -a : 0, c = Math.min(0, Math.max(t.translateX, h)), l = i > 0 ? -i : 0, u = Math.min(0, Math.max(t.translateY, l)), d = -Math.min(0, a) * e[0], g = -Math.min(0, i) * e[1];
|
|
221
|
+
return {
|
|
222
|
+
translateX: c + d,
|
|
223
|
+
translateY: u + g
|
|
224
|
+
};
|
|
225
|
+
}
|
|
208
226
|
update() {
|
|
209
227
|
this.canvasElement.style.transformOrigin = "top left", this.canvasElement.style.transform = `translateX(${this.renderingTranslateX}px) translateY(${this.renderingTranslateY}px) scale(${this.renderinScale}) rotate(${this.renderingRotate}rad)`, this.dispatchEvent(new Event("update"));
|
|
210
228
|
}
|
|
211
229
|
destroy() {
|
|
212
230
|
}
|
|
213
231
|
}
|
|
214
|
-
class
|
|
232
|
+
class M extends HTMLElement {
|
|
215
233
|
constructor() {
|
|
216
234
|
super();
|
|
217
|
-
|
|
235
|
+
T(this, "engine");
|
|
218
236
|
this.attachShadow({ mode: "open" });
|
|
219
237
|
}
|
|
220
238
|
get contentEl() {
|
|
@@ -259,8 +277,8 @@ class B extends HTMLElement {
|
|
|
259
277
|
</div>
|
|
260
278
|
</div>
|
|
261
279
|
`;
|
|
262
|
-
const e = Number(this.getAttribute("translate-x") || "0"), s = Number(this.getAttribute("translate-y") || "0"), n = Number(this.getAttribute("scale") || "1"), a = Number(this.getAttribute("rotate") || "0"),
|
|
263
|
-
this.engine = new
|
|
280
|
+
const e = Number(this.getAttribute("translate-x") || "0"), s = Number(this.getAttribute("translate-y") || "0"), n = Number(this.getAttribute("scale") || "1"), a = Number(this.getAttribute("rotate") || "0"), i = Number(this.getAttribute("min-scale")), h = Number(this.getAttribute("max-scale")), c = Number(this.getAttribute("offset-top")), l = Number(this.getAttribute("offset-right")), u = Number(this.getAttribute("offset-bottom")), d = Number(this.getAttribute("offset-left")), g = this.getAttribute("clamp-bounds") === "true";
|
|
281
|
+
this.getAttribute("rotation"), this.engine = new W(
|
|
264
282
|
this.contentEl,
|
|
265
283
|
{
|
|
266
284
|
top: isNaN(c) ? 100 : c,
|
|
@@ -272,11 +290,14 @@ class B extends HTMLElement {
|
|
|
272
290
|
s,
|
|
273
291
|
n,
|
|
274
292
|
a,
|
|
293
|
+
isNaN(i) ? void 0 : i,
|
|
275
294
|
isNaN(h) ? void 0 : h,
|
|
276
|
-
|
|
277
|
-
), this.contentEl.addEventListener("wheel", (
|
|
278
|
-
const
|
|
279
|
-
this.getAttribute("translate-x") !==
|
|
295
|
+
g
|
|
296
|
+
), this.contentEl.addEventListener("wheel", (o) => this.engine.handleWheel(o)), this.contentEl.addEventListener("gesturestart", (o) => this.engine.handleGesturestart(o)), window.addEventListener("gesturechange", (o) => this.engine.handleGesturechange(o)), window.addEventListener("gestureend", (o) => this.engine.handleGestureend(o)), this.contentEl.addEventListener("mousedown", (o) => this.engine.handleMousedown(o)), window.addEventListener("mousemove", (o) => this.engine.handleMousemove(o)), window.addEventListener("mouseup", (o) => this.engine.handleMouseup(o)), this.contentEl.addEventListener("touchstart", (o) => this.engine.handleTouchstart(o)), window.addEventListener("touchmove", (o) => this.engine.handleTouchmove(o)), window.addEventListener("touchend", (o) => this.engine.handleTouchend(o)), this.engine.addEventListener("update", () => {
|
|
297
|
+
const o = this.engine.translateX.toString(), p = this.engine.translateY.toString(), m = this.engine.scale.toString(), f = this.engine.rotate.toString();
|
|
298
|
+
this.getAttribute("translate-x") !== o && this.setAttribute("translate-x", o), this.getAttribute("translate-y") !== p && this.setAttribute("translate-y", p), this.getAttribute("scale") !== m && this.setAttribute("scale", m), this.getAttribute("rotate") !== f && this.setAttribute("rotate", f), this.dispatchEvent(new Event("update"));
|
|
299
|
+
}), this.engine.addEventListener("init", () => {
|
|
300
|
+
this.dispatchEvent(new Event("init"));
|
|
280
301
|
});
|
|
281
302
|
}
|
|
282
303
|
disconnectedCallback() {
|
|
@@ -290,12 +311,12 @@ class B extends HTMLElement {
|
|
|
290
311
|
this.engine.translateX !== a && (this.engine.translateX = a, this.engine.update());
|
|
291
312
|
break;
|
|
292
313
|
case "translate-y":
|
|
293
|
-
const
|
|
294
|
-
this.engine.translateY !==
|
|
314
|
+
const i = Number(n);
|
|
315
|
+
this.engine.translateY !== i && (this.engine.translateY = i, this.engine.update());
|
|
295
316
|
break;
|
|
296
317
|
case "scale":
|
|
297
|
-
const
|
|
298
|
-
this.engine.scale !==
|
|
318
|
+
const h = Number(n);
|
|
319
|
+
this.engine.scale !== h && (this.engine.scale = h, this.engine.update());
|
|
299
320
|
break;
|
|
300
321
|
case "rotate":
|
|
301
322
|
const c = Number(n);
|
|
@@ -313,14 +334,21 @@ class B extends HTMLElement {
|
|
|
313
334
|
case "offset-right":
|
|
314
335
|
case "offset-bottom":
|
|
315
336
|
case "offset-left":
|
|
316
|
-
const d = Number(this.getAttribute("offset-top") || "0"),
|
|
337
|
+
const d = Number(this.getAttribute("offset-top") || "0"), g = Number(this.getAttribute("offset-right") || "0"), o = Number(this.getAttribute("offset-bottom") || "0"), p = Number(this.getAttribute("offset-left") || "0");
|
|
317
338
|
this.engine.offset = {
|
|
318
339
|
top: d,
|
|
319
|
-
right:
|
|
320
|
-
bottom:
|
|
340
|
+
right: g,
|
|
341
|
+
bottom: o,
|
|
321
342
|
left: p
|
|
322
343
|
}, this.engine.update();
|
|
323
344
|
break;
|
|
345
|
+
case "clamp-bounds":
|
|
346
|
+
const m = n === "true";
|
|
347
|
+
this.engine.clampBounds !== m && (this.engine.clampBounds = m, this.engine.setTranslateFromUserGesture(this.engine.translateX, this.engine.translateY), this.engine.update());
|
|
348
|
+
break;
|
|
349
|
+
case "rotation":
|
|
350
|
+
const f = n === "true";
|
|
351
|
+
this.engine.rotation !== f && (this.engine.rotation = f, this.engine.update());
|
|
324
352
|
}
|
|
325
353
|
}
|
|
326
354
|
get canvasWidth() {
|
|
@@ -339,5 +367,5 @@ class B extends HTMLElement {
|
|
|
339
367
|
return this.engine.composePoint(e, s);
|
|
340
368
|
}
|
|
341
369
|
}
|
|
342
|
-
|
|
343
|
-
customElements.get("zoom-pinch") || customElements.define("zoom-pinch",
|
|
370
|
+
T(M, "observedAttributes", ["translate-x", "translate-y", "scale", "rotate", "min-scale", "max-scale", "offset-top", "offset-right", "offset-bottom", "offset-left", "clamp-bounds", "rotation"]);
|
|
371
|
+
customElements.get("zoom-pinch") || customElements.define("zoom-pinch", M);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(w){typeof define=="function"&&define.amd?define(w):w()})((function(){"use strict";var I=Object.defineProperty;var W=(w,S,p)=>S in w?I(w,S,{enumerable:!0,configurable:!0,writable:!0,value:p}):w[S]=p;var Y=(w,S,p)=>W(w,typeof S!="symbol"?S+"":S,p);var w=Object.defineProperty,S=(o,t,e)=>t in o?w(o,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):o[t]=e,p=(o,t,e)=>S(o,typeof t!="symbol"?t+"":t,e);function E(o){return o*Math.PI/180}function X(o,t,e){return Math.min(Math.max(o,t),e)}function b(o,t,e){const[s,n]=o,[a,i]=t,r=Math.cos(e)*(s-a)-Math.sin(e)*(n-i)+a,c=Math.sin(e)*(s-a)+Math.cos(e)*(n-i)+i;return[r,c]}function T(o,t){const e=Math.pow(10,t);return Math.round(o*e)/e}function N(o){var t=!1;return o.wheelDeltaY?o.wheelDeltaY===o.deltaY*-3&&(t=!0):o.deltaMode===0&&(t=!0),t}function M(o,t){const e=t.find(s=>o%s===0);return e?o/e:1}function x(o,t,e,s,n){let a=o.left-t,i=o.top-e;const r=Math.cos(-n),c=Math.sin(-n);let l=a*r-i*c,u=a*c+i*r;const d=o.width/s,g=o.height/s;return l/=s,u/=s,{x:T(l,4),y:T(u,4),width:T(d,4),height:T(g,4)}}class y extends EventTarget{constructor(t,e,s,n,a,i,r=.1,c=10,l=!1,u=!0){super(),p(this,"wrapperBounds"),p(this,"canvasBounds"),p(this,"gestureStartRotate",0),p(this,"dragStart",null),p(this,"dragStartFrozenX",null),p(this,"dragStartFrozenY",null),p(this,"touchStarts",null),p(this,"touchStartTranslateX",0),p(this,"touchStartTranslateY",0),this.element=t,this.offset=e,this.translateX=s,this.translateY=n,this.scale=a,this.rotate=i,this.minScale=r,this.maxScale=c,this.clampBounds=l,this.rotation=u;const d=new ResizeObserver(()=>{const{x:h,y:f,width:v,height:m}=this.element.getBoundingClientRect();this.wrapperBounds={x:h,y:f,width:v,height:m},this.update()}),g=new ResizeObserver(()=>{const{x:h,y:f,width:v,height:m}=x(this.canvasElement.getBoundingClientRect(),this.renderingTranslateX,this.renderingTranslateY,this.renderinScale,this.renderingRotate);this.canvasBounds={x:h,y:f,width:v,height:m},this.update()});requestAnimationFrame(()=>{this.wrapperBounds=this.element.getBoundingClientRect(),this.canvasBounds=this.canvasElement.getBoundingClientRect(),this.update(),this.dispatchEvent(new Event("init"))}),g.observe(this.canvasElement),d.observe(this.element)}get canvasElement(){return this.element.querySelector(".canvas")}get wrapperInnerX(){return this.wrapperBounds.x+this.offset.left}get wrapperInnerY(){return this.wrapperBounds.y+this.offset.top}get wrapperInnerWidth(){return this.wrapperBounds.width-this.offset.left-this.offset.right}get wrapperInnerHeight(){return this.wrapperBounds.height-this.offset.top-this.offset.bottom}get wrapperInnerRatio(){return this.wrapperInnerWidth/this.wrapperInnerHeight}get canvasNaturalRatio(){return this.canvasBounds.width/this.canvasBounds.height}get naturalScale(){return this.canvasNaturalRatio>=this.wrapperInnerRatio?this.wrapperInnerWidth/this.canvasBounds.width:this.wrapperInnerHeight/this.canvasBounds.height}setTranslateFromUserGesture(t,e){if(this.clampBounds){const s=this.clampTranslate({translateX:t,translateY:e,scale:this.scale,rotate:this.rotate});this.translateX=s.translateX,this.translateY=s.translateY,this.rotate=0}else this.translateX=t,this.translateY=e}handleGesturestart(t){this.gestureStartRotate=this.rotate}handleGesturechange(t){const{clientX:e,clientY:s}=t,n=t.rotation;if(n===0||!this.rotation)return;const a=this.normalizeClientCoords(e,s);this.rotateCanvas(a[0],a[1],this.gestureStartRotate+E(n))}handleGestureend(t){}handleMousedown(t){t.preventDefault(),this.dragStart=[t.clientX,t.clientY],this.dragStartFrozenX=this.translateX,this.dragStartFrozenY=this.translateY}handleMouseup(t){t.preventDefault(),this.dragStart=null,this.dragStartFrozenX=null,this.dragStartFrozenY=null}handleMousemove(t){if(t.preventDefault(),this.dragStart&&this.dragStartFrozenX!==null&&this.dragStartFrozenY!==null){const e=t.clientX-this.dragStart[0],s=t.clientY-this.dragStart[1],n=this.dragStartFrozenX- -e,a=this.dragStartFrozenY- -s;this.setTranslateFromUserGesture(n,a),this.update()}}handleWheel(t){let{deltaX:e,deltaY:s,ctrlKey:n}=t;const a=[120,100],i=2;N(t)||((Math.abs(e)===120||Math.abs(e)===200)&&(e=e/(100/i*M(e,a))*Math.sign(e)),(Math.abs(s)===120||Math.abs(s)===200)&&(s=s/(100/i*M(s,a))*Math.sign(s)));const r=this.scale;if(n){const c=-s/100*r,l=X(r+c,this.minScale,this.maxScale),u=this.relativeWrapperCoordinatesFromClientCoords(t.clientX,t.clientY),[d,g]=this.calcProjectionTranslate(l,u,this.normalizeMatrixCoordinates(t.clientX,t.clientY));this.setTranslateFromUserGesture(d,g),this.scale=l}else this.setTranslateFromUserGesture(this.translateX-e,this.translateY-s);this.update(),t.preventDefault()}freezeTouches(t){return Array.from(t).map(e=>{const s=this.clientCoordsToWrapperCoords(e.clientX,e.clientY);return{client:[e.clientX,e.clientY],canvasRel:this.getCanvasCoordsRel(s[0],s[1])}})}handleTouchstart(t){this.touchStarts=this.freezeTouches(t.touches),this.touchStartTranslateX=this.translateX,this.touchStartTranslateY=this.translateY,t.preventDefault()}handleTouchmove(t){t.preventDefault();const e=Array.from(t.touches).map(s=>this.clientCoordsToWrapperCoords(s.clientX,s.clientY));if(this.touchStarts){if(e.length>=2&&this.touchStarts.length>=2){const s=[this.touchStarts[0].canvasRel[0]*this.canvasBounds.width,this.touchStarts[0].canvasRel[1]*this.canvasBounds.height],n=[this.touchStarts[1].canvasRel[0]*this.canvasBounds.width,this.touchStarts[1].canvasRel[1]*this.canvasBounds.height],a=Math.sqrt(Math.pow(s[0]-n[0],2)+Math.pow(s[1]-n[1],2)),i=Math.sqrt(Math.pow(e[0][0]-e[1][0],2)+Math.pow(e[0][1]-e[1][1],2))/this.naturalScale,r=X(i/a,this.minScale,this.maxScale),c=[e[0][0]/this.wrapperInnerWidth,e[0][1]/this.wrapperInnerHeight],l=this.touchStarts[0].canvasRel,[u,d]=this.calcProjectionTranslate(r,c,l,0);if(this.rotation){let g=0,h=0,f=0;const v=Math.atan2(n[1]-s[1],n[0]-s[0]);f=Math.atan2(e[1][1]-e[0][1],e[1][0]-e[0][0])-v;const m=(z,F)=>[this.offset.left+this.canvasBounds.width*z*this.naturalScale*r+u,this.offset.top+this.canvasBounds.height*F*this.naturalScale*r+d],B=m(0,0),A=m(this.touchStarts[0].canvasRel[0],this.touchStarts[0].canvasRel[1]),R=b(B,A,f);g=R[0]-B[0],h=R[1]-B[1],this.scale=r,this.rotate=f,this.setTranslateFromUserGesture(u+g,d+h)}else this.scale=r,this.setTranslateFromUserGesture(u,d)}else{const s=t.touches[0].clientX-this.touchStarts[0].client[0],n=t.touches[0].clientY-this.touchStarts[0].client[1],a=this.touchStartTranslateX+s,i=this.touchStartTranslateY+n;this.setTranslateFromUserGesture(a,i)}this.update()}}handleTouchend(t){t.touches.length===0?this.touchStarts=null:(this.touchStarts=this.freezeTouches(t.touches),this.touchStartTranslateX=this.translateX,this.touchStartTranslateY=this.translateY)}calcProjectionTranslate(t,e,s,n){const a=this.canvasBounds.width*this.naturalScale,i=this.canvasBounds.height*this.naturalScale,r=s[0]*a*t,c=s[1]*i*t,l=b([r,c],[0,0],n??this.rotate),u=e[0]*this.wrapperInnerWidth,d=e[1]*this.wrapperInnerHeight,g=u-l[0],h=d-l[1];return[g,h]}applyTransform(t,e,s){const n=this.calcProjectionTranslate(t,e,s,0);this.scale=t,this.setTranslateFromUserGesture(n[0],n[1]),this.update()}composeRelPoint(t,e,s,n,a,i){s=s??this.scale,n=n??this.translateX,a=a??this.translateY,i=i??this.rotate;const r=[this.offset.left,this.offset.top],c=[this.offset.left+this.canvasBounds.width*(s*this.naturalScale)*t,this.offset.top+this.canvasBounds.height*(s*this.naturalScale)*e],l=b(c,r,i);return[l[0]+n,l[1]+a]}composePoint(t,e){const s=t/this.canvasBounds.width,n=e/this.canvasBounds.height;return this.composeRelPoint(s,n)}getAnchorOffset(t,e,s,n,a=[.5,.5]){const i=this.calcProjectionTranslate(t,a,a,0),r=[this.offset.left+i[0]+this.canvasBounds.width*(t*this.naturalScale)*a[0],this.offset.top+i[1]+this.canvasBounds.height*(t*this.naturalScale)*a[1]],c=this.composeRelPoint(a[0],a[1],t,e,s,n),l=c[0]-r[0],u=c[1]-r[1];return[l,u]}getCanvasCoordsRel(t,e){const s=[0,0],n=[t-this.translateX,e-this.translateY],a=b(n,s,-this.rotate),i=[a[0]/this.renderinScale,a[1]/this.renderinScale];return[i[0]/this.canvasBounds.width,i[1]/this.canvasBounds.height]}clientCoordsToWrapperCoords(t,e){return[t-this.wrapperInnerX,e-this.wrapperInnerY]}relativeWrapperCoordinatesFromClientCoords(t,e){const[s,n]=this.clientCoordsToWrapperCoords(t,e);return[s/this.wrapperInnerWidth,n/this.wrapperInnerHeight]}normalizeMatrixCoordinates(t,e){const s=this.clientCoordsToWrapperCoords(t,e);return this.getCanvasCoordsRel(s[0],s[1])}normalizeClientCoords(t,e){const[s,n]=this.normalizeMatrixCoordinates(t,e);return[s*this.canvasBounds.width,n*this.canvasBounds.height]}rotateCanvas(t,e,s){const n=t/this.canvasBounds.width,a=e/this.canvasBounds.height,i=this.composeRelPoint(n,a,this.scale,0,0,s),r=this.composeRelPoint(n,a);this.setTranslateFromUserGesture(r[0]-i[0],r[1]-i[1]),this.rotate=s,this.update()}get renderinScale(){return this.naturalScale*this.scale}get renderingTranslateX(){return this.offset.left+this.translateX}get renderingTranslateY(){return this.offset.top+this.translateY}get renderingRotate(){return this.rotate}clampTranslate(t,e=[.5,.5]){const s=this.canvasBounds.width*this.naturalScale*t.scale,n=this.canvasBounds.height*this.naturalScale*t.scale,a=s-this.wrapperInnerWidth,i=n-this.wrapperInnerHeight,r=a>0?-a:0,c=Math.min(0,Math.max(t.translateX,r)),l=i>0?-i:0,u=Math.min(0,Math.max(t.translateY,l)),d=-Math.min(0,a)*e[0],g=-Math.min(0,i)*e[1];return{translateX:c+d,translateY:u+g}}update(){this.canvasElement.style.transformOrigin="top left",this.canvasElement.style.transform=`translateX(${this.renderingTranslateX}px) translateY(${this.renderingTranslateY}px) scale(${this.renderinScale}) rotate(${this.renderingRotate}rad)`,this.dispatchEvent(new Event("update"))}destroy(){}}class C extends HTMLElement{constructor(){super();Y(this,"engine");this.attachShadow({mode:"open"})}get contentEl(){return this.shadowRoot.querySelector(".content")}get canvasElement(){return this.shadowRoot.querySelector(".canvas")}connectedCallback(){this.shadowRoot.innerHTML=`
|
|
2
2
|
<style>
|
|
3
3
|
:host {
|
|
4
4
|
display: block;
|
|
@@ -32,4 +32,4 @@
|
|
|
32
32
|
<slot name="matrix"></slot>
|
|
33
33
|
</div>
|
|
34
34
|
</div>
|
|
35
|
-
`;const e=Number(this.getAttribute("translate-x")||"0"),s=Number(this.getAttribute("translate-y")||"0"),n=Number(this.getAttribute("scale")||"1"),a=Number(this.getAttribute("rotate")||"0"),
|
|
35
|
+
`;const e=Number(this.getAttribute("translate-x")||"0"),s=Number(this.getAttribute("translate-y")||"0"),n=Number(this.getAttribute("scale")||"1"),a=Number(this.getAttribute("rotate")||"0"),i=Number(this.getAttribute("min-scale")),r=Number(this.getAttribute("max-scale")),c=Number(this.getAttribute("offset-top")),l=Number(this.getAttribute("offset-right")),u=Number(this.getAttribute("offset-bottom")),d=Number(this.getAttribute("offset-left")),g=this.getAttribute("clamp-bounds")==="true";this.getAttribute("rotation"),this.engine=new y(this.contentEl,{top:isNaN(c)?100:c,left:isNaN(d)?0:d,right:isNaN(l)?0:l,bottom:isNaN(u)?0:u},e,s,n,a,isNaN(i)?void 0:i,isNaN(r)?void 0:r,g),this.contentEl.addEventListener("wheel",h=>this.engine.handleWheel(h)),this.contentEl.addEventListener("gesturestart",h=>this.engine.handleGesturestart(h)),window.addEventListener("gesturechange",h=>this.engine.handleGesturechange(h)),window.addEventListener("gestureend",h=>this.engine.handleGestureend(h)),this.contentEl.addEventListener("mousedown",h=>this.engine.handleMousedown(h)),window.addEventListener("mousemove",h=>this.engine.handleMousemove(h)),window.addEventListener("mouseup",h=>this.engine.handleMouseup(h)),this.contentEl.addEventListener("touchstart",h=>this.engine.handleTouchstart(h)),window.addEventListener("touchmove",h=>this.engine.handleTouchmove(h)),window.addEventListener("touchend",h=>this.engine.handleTouchend(h)),this.engine.addEventListener("update",()=>{const h=this.engine.translateX.toString(),f=this.engine.translateY.toString(),v=this.engine.scale.toString(),m=this.engine.rotate.toString();this.getAttribute("translate-x")!==h&&this.setAttribute("translate-x",h),this.getAttribute("translate-y")!==f&&this.setAttribute("translate-y",f),this.getAttribute("scale")!==v&&this.setAttribute("scale",v),this.getAttribute("rotate")!==m&&this.setAttribute("rotate",m),this.dispatchEvent(new Event("update"))}),this.engine.addEventListener("init",()=>{this.dispatchEvent(new Event("init"))})}disconnectedCallback(){this.engine.destroy()}attributeChangedCallback(e,s,n){if(this.engine)switch(e){case"translate-x":const a=Number(n);this.engine.translateX!==a&&(this.engine.translateX=a,this.engine.update());break;case"translate-y":const i=Number(n);this.engine.translateY!==i&&(this.engine.translateY=i,this.engine.update());break;case"scale":const r=Number(n);this.engine.scale!==r&&(this.engine.scale=r,this.engine.update());break;case"rotate":const c=Number(n);this.engine.rotate!==c&&(this.engine.rotate=c,this.engine.update());break;case"min-scale":const l=Number(n);!isNaN(l)&&this.engine.minScale!==l&&(this.engine.minScale=l,this.engine.update());break;case"max-scale":const u=Number(n);!isNaN(u)&&this.engine.maxScale!==u&&(this.engine.maxScale=u,this.engine.update());break;case"offset-top":case"offset-right":case"offset-bottom":case"offset-left":const d=Number(this.getAttribute("offset-top")||"0"),g=Number(this.getAttribute("offset-right")||"0"),h=Number(this.getAttribute("offset-bottom")||"0"),f=Number(this.getAttribute("offset-left")||"0");this.engine.offset={top:d,right:g,bottom:h,left:f},this.engine.update();break;case"clamp-bounds":const v=n==="true";this.engine.clampBounds!==v&&(this.engine.clampBounds=v,this.engine.setTranslateFromUserGesture(this.engine.translateX,this.engine.translateY),this.engine.update());break;case"rotation":const m=n==="true";this.engine.rotation!==m&&(this.engine.rotation=m,this.engine.update())}}get canvasWidth(){return this.engine.canvasBounds.width}get canvasHeight(){return this.engine.canvasBounds.height}applyTransform(e,s,n){this.engine.applyTransform(e,s,n)}normalizeClientCoords(e,s){return this.engine.normalizeClientCoords(e,s)}composePoint(e,s){return this.engine.composePoint(e,s)}}Y(C,"observedAttributes",["translate-x","translate-y","scale","rotate","min-scale","max-scale","offset-top","offset-right","offset-bottom","offset-left","clamp-bounds","rotation"]),customElements.get("zoom-pinch")||customElements.define("zoom-pinch",C)}));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zoompinch/elements",
|
|
3
3
|
"description": "Custom elements wrapper ZoomPinch - reactive pinch & zoom component",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.18",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/zoompinch-elements.umd.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"vite": "^6.4.1"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@zoompinch/core": "^0.0.
|
|
29
|
+
"@zoompinch/core": "^0.0.18"
|
|
30
30
|
},
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public"
|