@zoompinch/elements 0.0.1
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/dist/zoompinch-core.es.js +418 -0
- package/dist/zoompinch-core.umd.js +35 -0
- package/package.json +30 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
var x = Object.defineProperty;
|
|
2
|
+
var y = (r, t, e) => t in r ? x(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
|
|
3
|
+
var b = (r, t, e) => y(r, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
var E = Object.defineProperty, A = (r, t, e) => t in r ? E(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e, p = (r, t, e) => A(r, typeof t != "symbol" ? t + "" : t, e);
|
|
5
|
+
function z(r) {
|
|
6
|
+
return r * Math.PI / 180;
|
|
7
|
+
}
|
|
8
|
+
function T(r, t, e) {
|
|
9
|
+
return Math.min(Math.max(r, t), e);
|
|
10
|
+
}
|
|
11
|
+
function v(r, t, e) {
|
|
12
|
+
const [s, n] = r, [a, h] = t, o = Math.cos(e) * (s - a) - Math.sin(e) * (n - h) + a, c = Math.sin(e) * (s - a) + Math.cos(e) * (n - h) + h;
|
|
13
|
+
return [o, c];
|
|
14
|
+
}
|
|
15
|
+
function S(r, t) {
|
|
16
|
+
const e = Math.pow(10, t);
|
|
17
|
+
return Math.round(r * e) / e;
|
|
18
|
+
}
|
|
19
|
+
function I(r) {
|
|
20
|
+
var t = !1;
|
|
21
|
+
return r.wheelDeltaY ? r.wheelDeltaY === r.deltaY * -3 && (t = !0) : r.deltaMode === 0 && (t = !0), t;
|
|
22
|
+
}
|
|
23
|
+
function C(r, t) {
|
|
24
|
+
const e = t.find((s) => r % s === 0);
|
|
25
|
+
return e ? r / e : 1;
|
|
26
|
+
}
|
|
27
|
+
function W(r, t, e, s, n) {
|
|
28
|
+
let a = r.left - t, h = r.top - e;
|
|
29
|
+
const o = Math.cos(-n), c = Math.sin(-n);
|
|
30
|
+
let l = a * o - h * c, u = a * c + h * o;
|
|
31
|
+
const d = r.width / s, i = r.height / s;
|
|
32
|
+
return l /= s, u /= s, { x: S(l, 4), y: S(u, 4), width: S(d, 4), height: S(i, 4) };
|
|
33
|
+
}
|
|
34
|
+
class L extends EventTarget {
|
|
35
|
+
constructor(t, e, s, n, a, h, o = 0.1, c = 10) {
|
|
36
|
+
super(), p(this, "wrapperBounds"), p(this, "canvasBounds"), p(this, "gestureStartRadians", 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), p(this, "centeredTransform", {
|
|
37
|
+
translateX: 0,
|
|
38
|
+
translateY: 0,
|
|
39
|
+
scale: 1,
|
|
40
|
+
radians: 0
|
|
41
|
+
}), this.element = t, this.offset = e, this.translateX = s, this.translateY = n, this.scale = a, this.rotate = h, this.minScale = o, this.maxScale = c;
|
|
42
|
+
const l = new ResizeObserver(() => {
|
|
43
|
+
const { x: d, y: i, width: g, height: f } = this.element.getBoundingClientRect();
|
|
44
|
+
this.wrapperBounds = { x: d, y: i, width: g, height: f }, this.update();
|
|
45
|
+
}), u = new ResizeObserver(() => {
|
|
46
|
+
const { x: d, y: i, width: g, height: f } = W(
|
|
47
|
+
this.canvasElement.getBoundingClientRect(),
|
|
48
|
+
this.renderingTranslateX,
|
|
49
|
+
this.renderingTranslateY,
|
|
50
|
+
this.renderinScale,
|
|
51
|
+
this.renderingRotate
|
|
52
|
+
);
|
|
53
|
+
this.canvasBounds = { x: d, y: i, width: g, height: f }, this.update();
|
|
54
|
+
});
|
|
55
|
+
requestAnimationFrame(() => {
|
|
56
|
+
this.wrapperBounds = this.element.getBoundingClientRect(), this.canvasBounds = this.canvasElement.getBoundingClientRect(), this.update();
|
|
57
|
+
}), u.observe(this.canvasElement), l.observe(this.element);
|
|
58
|
+
}
|
|
59
|
+
get canvasElement() {
|
|
60
|
+
return this.element.querySelector(".canvas");
|
|
61
|
+
}
|
|
62
|
+
get wrapperInnerX() {
|
|
63
|
+
return this.wrapperBounds.x + this.offset.left;
|
|
64
|
+
}
|
|
65
|
+
get wrapperInnerY() {
|
|
66
|
+
return this.wrapperBounds.y + this.offset.top;
|
|
67
|
+
}
|
|
68
|
+
get wrapperInnerWidth() {
|
|
69
|
+
return this.wrapperBounds.width - this.offset.left - this.offset.right;
|
|
70
|
+
}
|
|
71
|
+
get wrapperInnerHeight() {
|
|
72
|
+
return this.wrapperBounds.height - this.offset.top - this.offset.bottom;
|
|
73
|
+
}
|
|
74
|
+
// Calculate the inner ratio
|
|
75
|
+
get wrapperInnerRatio() {
|
|
76
|
+
return this.wrapperInnerWidth / this.wrapperInnerHeight;
|
|
77
|
+
}
|
|
78
|
+
get canvasNaturalRatio() {
|
|
79
|
+
return this.canvasBounds.width / this.canvasBounds.height;
|
|
80
|
+
}
|
|
81
|
+
// Get natural scale
|
|
82
|
+
get naturalScale() {
|
|
83
|
+
return this.canvasNaturalRatio >= this.wrapperInnerRatio ? this.wrapperInnerWidth / this.canvasBounds.width : this.wrapperInnerHeight / this.canvasBounds.height;
|
|
84
|
+
}
|
|
85
|
+
handleGesturestart(t) {
|
|
86
|
+
this.gestureStartRadians = this.rotate;
|
|
87
|
+
}
|
|
88
|
+
handleGesturechange(t) {
|
|
89
|
+
const { clientX: e, clientY: s } = t, n = t.rotation;
|
|
90
|
+
if (n === 0)
|
|
91
|
+
return;
|
|
92
|
+
const a = this.normalizeMatrixCoordinates(e, s);
|
|
93
|
+
this.rotateCanvas(a[0], a[1], this.gestureStartRadians + z(n));
|
|
94
|
+
}
|
|
95
|
+
handleGestureend(t) {
|
|
96
|
+
}
|
|
97
|
+
handleMousedown(t) {
|
|
98
|
+
t.preventDefault(), this.dragStart = [t.clientX, t.clientY], this.dragStartFrozenX = this.translateX, this.dragStartFrozenY = this.translateY;
|
|
99
|
+
}
|
|
100
|
+
handleMouseup(t) {
|
|
101
|
+
t.preventDefault(), this.dragStart = null, this.dragStartFrozenX = null, this.dragStartFrozenY = null;
|
|
102
|
+
}
|
|
103
|
+
handleMousemove(t) {
|
|
104
|
+
if (t.preventDefault(), this.dragStart && this.dragStartFrozenX !== null && this.dragStartFrozenY !== null) {
|
|
105
|
+
const e = t.clientX - this.dragStart[0], s = t.clientY - this.dragStart[1], n = this.dragStartFrozenX - -e, a = this.dragStartFrozenY - -s;
|
|
106
|
+
this.translateX = n, this.translateY = a, this.update();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
handleWheel(t) {
|
|
110
|
+
let { deltaX: e, deltaY: s, ctrlKey: n } = t;
|
|
111
|
+
const a = [120, 100], h = 2;
|
|
112
|
+
I(t) || ((Math.abs(e) === 120 || Math.abs(e) === 200) && (e = e / (100 / h * C(e, a)) * Math.sign(e)), (Math.abs(s) === 120 || Math.abs(s) === 200) && (s = s / (100 / h * C(s, a)) * Math.sign(s)));
|
|
113
|
+
const o = this.scale;
|
|
114
|
+
if (n) {
|
|
115
|
+
const c = -s / 100 * o, l = T(o + c, this.minScale, this.maxScale), u = this.relativeWrapperCoordinatesFromClientCoords(t.clientX, t.clientY), [d, i] = this.calcProjectionTranslate(
|
|
116
|
+
l,
|
|
117
|
+
u,
|
|
118
|
+
this.normalizeMatrixCoordinates(t.clientX, t.clientY)
|
|
119
|
+
);
|
|
120
|
+
this.translateX = d, this.translateY = i, this.scale = l;
|
|
121
|
+
} else
|
|
122
|
+
this.translateX = this.translateX - e, this.translateY = this.translateY - s;
|
|
123
|
+
this.update(), t.preventDefault();
|
|
124
|
+
}
|
|
125
|
+
freezeTouches(t) {
|
|
126
|
+
return Array.from(t).map((e) => {
|
|
127
|
+
const s = this.clientCoordsToWrapperCoords(e.clientX, e.clientY);
|
|
128
|
+
return {
|
|
129
|
+
client: [e.clientX, e.clientY],
|
|
130
|
+
canvasRel: this.getCanvasCoordsRel(s[0], s[1])
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
handleTouchstart(t) {
|
|
135
|
+
this.touchStarts = this.freezeTouches(t.touches), this.touchStartTranslateX = this.translateX, this.touchStartTranslateY = this.translateY, t.preventDefault();
|
|
136
|
+
}
|
|
137
|
+
handleTouchmove(t) {
|
|
138
|
+
t.preventDefault();
|
|
139
|
+
const e = Array.from(t.touches).map(
|
|
140
|
+
(s) => this.clientCoordsToWrapperCoords(s.clientX, s.clientY)
|
|
141
|
+
);
|
|
142
|
+
if (this.touchStarts) {
|
|
143
|
+
if (e.length >= 2 && this.touchStarts.length >= 2) {
|
|
144
|
+
const s = [
|
|
145
|
+
this.touchStarts[0].canvasRel[0] * this.canvasBounds.width,
|
|
146
|
+
this.touchStarts[0].canvasRel[1] * this.canvasBounds.height
|
|
147
|
+
], n = [
|
|
148
|
+
this.touchStarts[1].canvasRel[0] * this.canvasBounds.width,
|
|
149
|
+
this.touchStarts[1].canvasRel[1] * this.canvasBounds.height
|
|
150
|
+
], a = Math.sqrt(
|
|
151
|
+
Math.pow(s[0] - n[0], 2) + Math.pow(s[1] - n[1], 2)
|
|
152
|
+
), h = Math.sqrt(
|
|
153
|
+
Math.pow(e[0][0] - e[1][0], 2) + Math.pow(e[0][1] - e[1][1], 2)
|
|
154
|
+
) / this.naturalScale, o = T(h / a, this.minScale, this.maxScale), c = [
|
|
155
|
+
e[0][0] / this.wrapperInnerWidth,
|
|
156
|
+
e[0][1] / this.wrapperInnerHeight
|
|
157
|
+
], l = this.touchStarts[0].canvasRel, [u, d] = this.calcProjectionTranslate(o, c, l, 0);
|
|
158
|
+
let i = 0, g = 0, f = 0;
|
|
159
|
+
const m = Math.atan2(
|
|
160
|
+
n[1] - s[1],
|
|
161
|
+
n[0] - s[0]
|
|
162
|
+
);
|
|
163
|
+
f = Math.atan2(e[1][1] - e[0][1], e[1][0] - e[0][0]) - m;
|
|
164
|
+
const Y = (R, N) => [
|
|
165
|
+
this.offset.left + this.canvasBounds.width * R * this.naturalScale * o + u,
|
|
166
|
+
this.offset.top + this.canvasBounds.height * N * this.naturalScale * o + d
|
|
167
|
+
], w = Y(0, 0), M = Y(
|
|
168
|
+
this.touchStarts[0].canvasRel[0],
|
|
169
|
+
this.touchStarts[0].canvasRel[1]
|
|
170
|
+
), X = v(
|
|
171
|
+
w,
|
|
172
|
+
M,
|
|
173
|
+
f
|
|
174
|
+
);
|
|
175
|
+
i = X[0] - w[0], g = X[1] - w[1], this.scale = o, this.rotate = f, this.translateX = u + i, this.translateY = d + g;
|
|
176
|
+
} else {
|
|
177
|
+
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, h = this.touchStartTranslateY + n;
|
|
178
|
+
this.translateX = a, this.translateY = h;
|
|
179
|
+
}
|
|
180
|
+
this.update();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
handleTouchend(t) {
|
|
184
|
+
t.touches.length === 0 ? this.touchStarts = null : (this.touchStarts = this.freezeTouches(t.touches), this.touchStartTranslateX = this.translateX, this.touchStartTranslateY = this.translateY);
|
|
185
|
+
}
|
|
186
|
+
calcProjectionTranslate(t, e, s, n) {
|
|
187
|
+
const a = this.canvasBounds.width * this.naturalScale, h = this.canvasBounds.height * this.naturalScale, o = s[0] * a * t, c = s[1] * h * t, l = v([o, c], [0, 0], n ?? this.rotate), u = e[0] * this.wrapperInnerWidth, d = e[1] * this.wrapperInnerHeight, i = u - l[0], g = d - l[1];
|
|
188
|
+
return [i, g];
|
|
189
|
+
}
|
|
190
|
+
applyTransform(t, e, s) {
|
|
191
|
+
const n = this.calcProjectionTranslate(t, e, s, 0);
|
|
192
|
+
this.scale = t, this.translateX = n[0], this.translateY = n[1], this.update();
|
|
193
|
+
}
|
|
194
|
+
composeRelPoint(t, e, s, n, a, h) {
|
|
195
|
+
s = s ?? this.scale, n = n ?? this.translateX, a = a ?? this.translateY, h = h ?? this.rotate;
|
|
196
|
+
const o = [this.offset.left, this.offset.top], c = [
|
|
197
|
+
this.offset.left + this.canvasBounds.width * (s * this.naturalScale) * t,
|
|
198
|
+
this.offset.top + this.canvasBounds.height * (s * this.naturalScale) * e
|
|
199
|
+
], l = v(c, o, h);
|
|
200
|
+
return [l[0] + n, l[1] + a];
|
|
201
|
+
}
|
|
202
|
+
composePoint(t, e) {
|
|
203
|
+
const s = t / this.canvasBounds.width, n = e / this.canvasBounds.height;
|
|
204
|
+
return this.composeRelPoint(s, n);
|
|
205
|
+
}
|
|
206
|
+
getAnchorOffset(t, e, s, n, a = [0.5, 0.5]) {
|
|
207
|
+
const h = this.calcProjectionTranslate(t, a, a, 0), o = [
|
|
208
|
+
this.offset.left + h[0] + this.canvasBounds.width * (t * this.naturalScale) * a[0],
|
|
209
|
+
this.offset.top + h[1] + this.canvasBounds.height * (t * this.naturalScale) * a[1]
|
|
210
|
+
], c = this.composeRelPoint(a[0], a[1], t, e, s, n), l = c[0] - o[0], u = c[1] - o[1];
|
|
211
|
+
return [l, u];
|
|
212
|
+
}
|
|
213
|
+
// Converts absolute inner wrapper coordinates to relative canvas coordinates (0-1, 0-1)
|
|
214
|
+
getCanvasCoordsRel(t, e) {
|
|
215
|
+
const s = [0, 0], n = [t - this.translateX, e - this.translateY], a = v(n, s, -this.rotate), h = [a[0] / this.renderinScale, a[1] / this.renderinScale];
|
|
216
|
+
return [h[0] / this.canvasBounds.width, h[1] / this.canvasBounds.height];
|
|
217
|
+
}
|
|
218
|
+
// Converts absolute client to coordinates to absolute inner-wrapper coorinates
|
|
219
|
+
clientCoordsToWrapperCoords(t, e) {
|
|
220
|
+
return [t - this.wrapperInnerX, e - this.wrapperInnerY];
|
|
221
|
+
}
|
|
222
|
+
// Converts absolute client coordinates to relative wrapper coordinates (0-1, 0-1)
|
|
223
|
+
relativeWrapperCoordinatesFromClientCoords(t, e) {
|
|
224
|
+
const [s, n] = this.clientCoordsToWrapperCoords(t, e);
|
|
225
|
+
return [s / this.wrapperInnerWidth, n / this.wrapperInnerHeight];
|
|
226
|
+
}
|
|
227
|
+
// Converts client coordinates to relative canvas coordinates (0-1, 0-1)
|
|
228
|
+
normalizeMatrixCoordinates(t, e) {
|
|
229
|
+
const s = this.clientCoordsToWrapperCoords(t, e);
|
|
230
|
+
return this.getCanvasCoordsRel(s[0], s[1]);
|
|
231
|
+
}
|
|
232
|
+
// Converts client coordinates to absolute canvas coordinates
|
|
233
|
+
normalizeClientCoords(t, e) {
|
|
234
|
+
const [s, n] = this.normalizeMatrixCoordinates(t, e);
|
|
235
|
+
return [s * this.canvasBounds.width, n * this.canvasBounds.height];
|
|
236
|
+
}
|
|
237
|
+
rotateCanvas(t, e, s) {
|
|
238
|
+
const n = this.composeRelPoint(t, e, this.scale, 0, 0, s), a = this.composeRelPoint(t, e);
|
|
239
|
+
this.translateX = a[0] - n[0], this.translateY = a[1] - n[1], this.rotate = s, this.update();
|
|
240
|
+
}
|
|
241
|
+
get renderinScale() {
|
|
242
|
+
return this.naturalScale * this.scale;
|
|
243
|
+
}
|
|
244
|
+
get renderingTranslateX() {
|
|
245
|
+
return this.offset.left + this.translateX;
|
|
246
|
+
}
|
|
247
|
+
get renderingTranslateY() {
|
|
248
|
+
return this.offset.top + this.translateY;
|
|
249
|
+
}
|
|
250
|
+
get renderingRotate() {
|
|
251
|
+
return this.rotate;
|
|
252
|
+
}
|
|
253
|
+
update() {
|
|
254
|
+
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"));
|
|
255
|
+
}
|
|
256
|
+
destroy() {
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
class B extends HTMLElement {
|
|
260
|
+
constructor() {
|
|
261
|
+
super();
|
|
262
|
+
b(this, "engine");
|
|
263
|
+
this.attachShadow({ mode: "open" });
|
|
264
|
+
}
|
|
265
|
+
get contentEl() {
|
|
266
|
+
return this.shadowRoot.querySelector(".content");
|
|
267
|
+
}
|
|
268
|
+
get canvasElement() {
|
|
269
|
+
return this.shadowRoot.querySelector(".canvas");
|
|
270
|
+
}
|
|
271
|
+
connectedCallback() {
|
|
272
|
+
this.shadowRoot.innerHTML = `
|
|
273
|
+
<style>
|
|
274
|
+
:host {
|
|
275
|
+
display: block;
|
|
276
|
+
touch-action: none;
|
|
277
|
+
}
|
|
278
|
+
.content {
|
|
279
|
+
display: inline-block;
|
|
280
|
+
will-change: transform;
|
|
281
|
+
width: 100%;
|
|
282
|
+
height: 100%;
|
|
283
|
+
overflow: hidden;
|
|
284
|
+
position: relative;
|
|
285
|
+
}
|
|
286
|
+
.canvas {
|
|
287
|
+
display: inline-block;
|
|
288
|
+
}
|
|
289
|
+
.matrix {
|
|
290
|
+
position: absolute;
|
|
291
|
+
top: 0;
|
|
292
|
+
left: 0;
|
|
293
|
+
pointer-events: none;
|
|
294
|
+
width: 100%;
|
|
295
|
+
height: 100%;
|
|
296
|
+
}
|
|
297
|
+
</style>
|
|
298
|
+
<div class="content" style="touch-action: none;">
|
|
299
|
+
<div class="canvas">
|
|
300
|
+
<slot></slot>
|
|
301
|
+
</div>
|
|
302
|
+
<div class="matrix">
|
|
303
|
+
<slot name="matrix"></slot>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
`;
|
|
307
|
+
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"), h = Number(this.getAttribute("min-scale")), o = 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"));
|
|
308
|
+
this.engine = new L(
|
|
309
|
+
this.contentEl,
|
|
310
|
+
{
|
|
311
|
+
top: isNaN(c) ? 100 : c,
|
|
312
|
+
left: isNaN(d) ? 0 : d,
|
|
313
|
+
right: isNaN(l) ? 0 : l,
|
|
314
|
+
bottom: isNaN(u) ? 0 : u
|
|
315
|
+
},
|
|
316
|
+
e,
|
|
317
|
+
s,
|
|
318
|
+
n,
|
|
319
|
+
a,
|
|
320
|
+
isNaN(h) ? void 0 : h,
|
|
321
|
+
isNaN(o) ? void 0 : o
|
|
322
|
+
), this.contentEl.addEventListener("wheel", (i) => this.engine.handleWheel(i)), this.contentEl.addEventListener(
|
|
323
|
+
"gesturestart",
|
|
324
|
+
(i) => this.engine.handleGesturestart(i)
|
|
325
|
+
), window.addEventListener(
|
|
326
|
+
"gesturechange",
|
|
327
|
+
(i) => this.engine.handleGesturechange(i)
|
|
328
|
+
), window.addEventListener(
|
|
329
|
+
"gestureend",
|
|
330
|
+
(i) => this.engine.handleGestureend(i)
|
|
331
|
+
), this.contentEl.addEventListener(
|
|
332
|
+
"mousedown",
|
|
333
|
+
(i) => this.engine.handleMousedown(i)
|
|
334
|
+
), window.addEventListener("mousemove", (i) => this.engine.handleMousemove(i)), window.addEventListener("mouseup", (i) => this.engine.handleMouseup(i)), this.contentEl.addEventListener(
|
|
335
|
+
"touchstart",
|
|
336
|
+
(i) => this.engine.handleTouchstart(i)
|
|
337
|
+
), window.addEventListener("touchmove", (i) => this.engine.handleTouchmove(i)), window.addEventListener("touchend", (i) => this.engine.handleTouchend(i)), this.engine.addEventListener("update", () => {
|
|
338
|
+
const i = this.engine.translateX.toString(), g = this.engine.translateY.toString(), f = this.engine.scale.toString(), m = this.engine.rotate.toString();
|
|
339
|
+
this.getAttribute("translate-x") !== i && this.setAttribute("translate-x", i), this.getAttribute("translate-y") !== g && this.setAttribute("translate-y", g), this.getAttribute("scale") !== f && this.setAttribute("scale", f), this.getAttribute("rotate") !== m && this.setAttribute("rotate", m), this.dispatchEvent(new Event("update"));
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
disconnectedCallback() {
|
|
343
|
+
this.engine.destroy();
|
|
344
|
+
}
|
|
345
|
+
attributeChangedCallback(e, s, n) {
|
|
346
|
+
if (this.engine)
|
|
347
|
+
switch (e) {
|
|
348
|
+
case "translate-x":
|
|
349
|
+
const a = Number(n);
|
|
350
|
+
this.engine.translateX !== a && (this.engine.translateX = a, this.engine.update());
|
|
351
|
+
break;
|
|
352
|
+
case "translate-y":
|
|
353
|
+
const h = Number(n);
|
|
354
|
+
this.engine.translateY !== h && (this.engine.translateY = h, this.engine.update());
|
|
355
|
+
break;
|
|
356
|
+
case "scale":
|
|
357
|
+
const o = Number(n);
|
|
358
|
+
this.engine.scale !== o && (this.engine.scale = o, this.engine.update());
|
|
359
|
+
break;
|
|
360
|
+
case "rotate":
|
|
361
|
+
const c = Number(n);
|
|
362
|
+
this.engine.rotate !== c && (this.engine.rotate = c, this.engine.update());
|
|
363
|
+
break;
|
|
364
|
+
case "min-scale":
|
|
365
|
+
const l = Number(n);
|
|
366
|
+
!isNaN(l) && this.engine.minScale !== l && (this.engine.minScale = l, this.engine.update());
|
|
367
|
+
break;
|
|
368
|
+
case "max-scale":
|
|
369
|
+
const u = Number(n);
|
|
370
|
+
!isNaN(u) && this.engine.maxScale !== u && (this.engine.maxScale = u, this.engine.update());
|
|
371
|
+
break;
|
|
372
|
+
case "offset-top":
|
|
373
|
+
case "offset-right":
|
|
374
|
+
case "offset-bottom":
|
|
375
|
+
case "offset-left":
|
|
376
|
+
const d = Number(this.getAttribute("offset-top") || "0"), i = Number(this.getAttribute("offset-right") || "0"), g = Number(this.getAttribute("offset-bottom") || "0"), f = Number(this.getAttribute("offset-left") || "0");
|
|
377
|
+
this.engine.offset = {
|
|
378
|
+
top: d,
|
|
379
|
+
right: i,
|
|
380
|
+
bottom: g,
|
|
381
|
+
left: f
|
|
382
|
+
}, this.engine.update();
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
get canvasWidth() {
|
|
387
|
+
return this.engine.canvasBounds.width;
|
|
388
|
+
}
|
|
389
|
+
get canvasHeight() {
|
|
390
|
+
return this.engine.canvasBounds.height;
|
|
391
|
+
}
|
|
392
|
+
applyTransform(e, s, n) {
|
|
393
|
+
this.engine.applyTransform(
|
|
394
|
+
e,
|
|
395
|
+
s,
|
|
396
|
+
n
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
normalizeClientCoords(e, s) {
|
|
400
|
+
return this.engine.normalizeClientCoords(e, s);
|
|
401
|
+
}
|
|
402
|
+
composePoint(e, s) {
|
|
403
|
+
return this.engine.composePoint(e, s);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
b(B, "observedAttributes", [
|
|
407
|
+
"translate-x",
|
|
408
|
+
"translate-y",
|
|
409
|
+
"scale",
|
|
410
|
+
"rotate",
|
|
411
|
+
"min-scale",
|
|
412
|
+
"max-scale",
|
|
413
|
+
"offset-top",
|
|
414
|
+
"offset-right",
|
|
415
|
+
"offset-bottom",
|
|
416
|
+
"offset-left"
|
|
417
|
+
]);
|
|
418
|
+
customElements.get("zoom-pinch") || customElements.define("zoom-pinch", B);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
(function(m){typeof define=="function"&&define.amd?define(m):m()})((function(){"use strict";var W=Object.defineProperty;var L=(m,v,g)=>v in m?W(m,v,{enumerable:!0,configurable:!0,writable:!0,value:g}):m[v]=g;var X=(m,v,g)=>L(m,typeof v!="symbol"?v+"":v,g);var m=Object.defineProperty,v=(h,t,e)=>t in h?m(h,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):h[t]=e,g=(h,t,e)=>v(h,typeof t!="symbol"?t+"":t,e);function N(h){return h*Math.PI/180}function T(h,t,e){return Math.min(Math.max(h,t),e)}function S(h,t,e){const[s,n]=h,[a,r]=t,o=Math.cos(e)*(s-a)-Math.sin(e)*(n-r)+a,c=Math.sin(e)*(s-a)+Math.cos(e)*(n-r)+r;return[o,c]}function w(h,t){const e=Math.pow(10,t);return Math.round(h*e)/e}function x(h){var t=!1;return h.wheelDeltaY?h.wheelDeltaY===h.deltaY*-3&&(t=!0):h.deltaMode===0&&(t=!0),t}function C(h,t){const e=t.find(s=>h%s===0);return e?h/e:1}function y(h,t,e,s,n){let a=h.left-t,r=h.top-e;const o=Math.cos(-n),c=Math.sin(-n);let l=a*o-r*c,u=a*c+r*o;const d=h.width/s,i=h.height/s;return l/=s,u/=s,{x:w(l,4),y:w(u,4),width:w(d,4),height:w(i,4)}}class E extends EventTarget{constructor(t,e,s,n,a,r,o=.1,c=10){super(),g(this,"wrapperBounds"),g(this,"canvasBounds"),g(this,"gestureStartRadians",0),g(this,"dragStart",null),g(this,"dragStartFrozenX",null),g(this,"dragStartFrozenY",null),g(this,"touchStarts",null),g(this,"touchStartTranslateX",0),g(this,"touchStartTranslateY",0),g(this,"centeredTransform",{translateX:0,translateY:0,scale:1,radians:0}),this.element=t,this.offset=e,this.translateX=s,this.translateY=n,this.scale=a,this.rotate=r,this.minScale=o,this.maxScale=c;const l=new ResizeObserver(()=>{const{x:d,y:i,width:f,height:p}=this.element.getBoundingClientRect();this.wrapperBounds={x:d,y:i,width:f,height:p},this.update()}),u=new ResizeObserver(()=>{const{x:d,y:i,width:f,height:p}=y(this.canvasElement.getBoundingClientRect(),this.renderingTranslateX,this.renderingTranslateY,this.renderinScale,this.renderingRotate);this.canvasBounds={x:d,y:i,width:f,height:p},this.update()});requestAnimationFrame(()=>{this.wrapperBounds=this.element.getBoundingClientRect(),this.canvasBounds=this.canvasElement.getBoundingClientRect(),this.update()}),u.observe(this.canvasElement),l.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}handleGesturestart(t){this.gestureStartRadians=this.rotate}handleGesturechange(t){const{clientX:e,clientY:s}=t,n=t.rotation;if(n===0)return;const a=this.normalizeMatrixCoordinates(e,s);this.rotateCanvas(a[0],a[1],this.gestureStartRadians+N(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.translateX=n,this.translateY=a,this.update()}}handleWheel(t){let{deltaX:e,deltaY:s,ctrlKey:n}=t;const a=[120,100],r=2;x(t)||((Math.abs(e)===120||Math.abs(e)===200)&&(e=e/(100/r*C(e,a))*Math.sign(e)),(Math.abs(s)===120||Math.abs(s)===200)&&(s=s/(100/r*C(s,a))*Math.sign(s)));const o=this.scale;if(n){const c=-s/100*o,l=T(o+c,this.minScale,this.maxScale),u=this.relativeWrapperCoordinatesFromClientCoords(t.clientX,t.clientY),[d,i]=this.calcProjectionTranslate(l,u,this.normalizeMatrixCoordinates(t.clientX,t.clientY));this.translateX=d,this.translateY=i,this.scale=l}else this.translateX=this.translateX-e,this.translateY=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)),r=Math.sqrt(Math.pow(e[0][0]-e[1][0],2)+Math.pow(e[0][1]-e[1][1],2))/this.naturalScale,o=T(r/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(o,c,l,0);let i=0,f=0,p=0;const b=Math.atan2(n[1]-s[1],n[0]-s[0]);p=Math.atan2(e[1][1]-e[0][1],e[1][0]-e[0][0])-b;const M=(z,I)=>[this.offset.left+this.canvasBounds.width*z*this.naturalScale*o+u,this.offset.top+this.canvasBounds.height*I*this.naturalScale*o+d],Y=M(0,0),A=M(this.touchStarts[0].canvasRel[0],this.touchStarts[0].canvasRel[1]),R=S(Y,A,p);i=R[0]-Y[0],f=R[1]-Y[1],this.scale=o,this.rotate=p,this.translateX=u+i,this.translateY=d+f}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,r=this.touchStartTranslateY+n;this.translateX=a,this.translateY=r}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,r=this.canvasBounds.height*this.naturalScale,o=s[0]*a*t,c=s[1]*r*t,l=S([o,c],[0,0],n??this.rotate),u=e[0]*this.wrapperInnerWidth,d=e[1]*this.wrapperInnerHeight,i=u-l[0],f=d-l[1];return[i,f]}applyTransform(t,e,s){const n=this.calcProjectionTranslate(t,e,s,0);this.scale=t,this.translateX=n[0],this.translateY=n[1],this.update()}composeRelPoint(t,e,s,n,a,r){s=s??this.scale,n=n??this.translateX,a=a??this.translateY,r=r??this.rotate;const o=[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=S(c,o,r);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 r=this.calcProjectionTranslate(t,a,a,0),o=[this.offset.left+r[0]+this.canvasBounds.width*(t*this.naturalScale)*a[0],this.offset.top+r[1]+this.canvasBounds.height*(t*this.naturalScale)*a[1]],c=this.composeRelPoint(a[0],a[1],t,e,s,n),l=c[0]-o[0],u=c[1]-o[1];return[l,u]}getCanvasCoordsRel(t,e){const s=[0,0],n=[t-this.translateX,e-this.translateY],a=S(n,s,-this.rotate),r=[a[0]/this.renderinScale,a[1]/this.renderinScale];return[r[0]/this.canvasBounds.width,r[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=this.composeRelPoint(t,e,this.scale,0,0,s),a=this.composeRelPoint(t,e);this.translateX=a[0]-n[0],this.translateY=a[1]-n[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}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 B extends HTMLElement{constructor(){super();X(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
|
+
<style>
|
|
3
|
+
:host {
|
|
4
|
+
display: block;
|
|
5
|
+
touch-action: none;
|
|
6
|
+
}
|
|
7
|
+
.content {
|
|
8
|
+
display: inline-block;
|
|
9
|
+
will-change: transform;
|
|
10
|
+
width: 100%;
|
|
11
|
+
height: 100%;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
position: relative;
|
|
14
|
+
}
|
|
15
|
+
.canvas {
|
|
16
|
+
display: inline-block;
|
|
17
|
+
}
|
|
18
|
+
.matrix {
|
|
19
|
+
position: absolute;
|
|
20
|
+
top: 0;
|
|
21
|
+
left: 0;
|
|
22
|
+
pointer-events: none;
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 100%;
|
|
25
|
+
}
|
|
26
|
+
</style>
|
|
27
|
+
<div class="content" style="touch-action: none;">
|
|
28
|
+
<div class="canvas">
|
|
29
|
+
<slot></slot>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="matrix">
|
|
32
|
+
<slot name="matrix"></slot>
|
|
33
|
+
</div>
|
|
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"),r=Number(this.getAttribute("min-scale")),o=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"));this.engine=new E(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(r)?void 0:r,isNaN(o)?void 0:o),this.contentEl.addEventListener("wheel",i=>this.engine.handleWheel(i)),this.contentEl.addEventListener("gesturestart",i=>this.engine.handleGesturestart(i)),window.addEventListener("gesturechange",i=>this.engine.handleGesturechange(i)),window.addEventListener("gestureend",i=>this.engine.handleGestureend(i)),this.contentEl.addEventListener("mousedown",i=>this.engine.handleMousedown(i)),window.addEventListener("mousemove",i=>this.engine.handleMousemove(i)),window.addEventListener("mouseup",i=>this.engine.handleMouseup(i)),this.contentEl.addEventListener("touchstart",i=>this.engine.handleTouchstart(i)),window.addEventListener("touchmove",i=>this.engine.handleTouchmove(i)),window.addEventListener("touchend",i=>this.engine.handleTouchend(i)),this.engine.addEventListener("update",()=>{const i=this.engine.translateX.toString(),f=this.engine.translateY.toString(),p=this.engine.scale.toString(),b=this.engine.rotate.toString();this.getAttribute("translate-x")!==i&&this.setAttribute("translate-x",i),this.getAttribute("translate-y")!==f&&this.setAttribute("translate-y",f),this.getAttribute("scale")!==p&&this.setAttribute("scale",p),this.getAttribute("rotate")!==b&&this.setAttribute("rotate",b),this.dispatchEvent(new Event("update"))})}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 r=Number(n);this.engine.translateY!==r&&(this.engine.translateY=r,this.engine.update());break;case"scale":const o=Number(n);this.engine.scale!==o&&(this.engine.scale=o,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"),i=Number(this.getAttribute("offset-right")||"0"),f=Number(this.getAttribute("offset-bottom")||"0"),p=Number(this.getAttribute("offset-left")||"0");this.engine.offset={top:d,right:i,bottom:f,left:p},this.engine.update();break}}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)}}X(B,"observedAttributes",["translate-x","translate-y","scale","rotate","min-scale","max-scale","offset-top","offset-right","offset-bottom","offset-left"]),customElements.get("zoom-pinch")||customElements.define("zoom-pinch",B)}));
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zoompinch/elements",
|
|
3
|
+
"description": "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
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/zoompinch-elements.umd.js",
|
|
7
|
+
"module": "./dist/zoompinch-elements.es.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/zoompinch-elements.es.js",
|
|
12
|
+
"require": "./dist/zoompinch-elements.umd.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "vite build",
|
|
21
|
+
"dev": "vite"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"typescript": "^5.9.3",
|
|
25
|
+
"vite": "^6.4.1"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@zoompinch/core": "^0.0.3"
|
|
29
|
+
}
|
|
30
|
+
}
|