aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2026-05-28 23:12:46 +0200
committerNathan Reiner <nathan@nathanreiner.xyz>2026-05-28 23:12:46 +0200
commitef2f7d38d64b2be9f388be3809ba1d07e2bc2282 (patch)
tree3ea1776afa6c436433d4525aacf7aa95825f49af
parentcd7883d16956d80c67a91c4e07be5335f4de5c39 (diff)
timeline: fix pinch-zoom offset
-rw-r--r--src/web/component/timeline/index.css2
-rw-r--r--src/web/component/timeline/index.js40
-rw-r--r--src/web/index.css11
-rw-r--r--src/web/pinch.js21
4 files changed, 60 insertions, 14 deletions
diff --git a/src/web/component/timeline/index.css b/src/web/component/timeline/index.css
index 8e61fcf..6d58f59 100644
--- a/src/web/component/timeline/index.css
+++ b/src/web/component/timeline/index.css
@@ -2,7 +2,7 @@
width: 100%;
display: inline-grid;
gap: 0px;
- transition: transform 0.1s ease;
+ overflow-y: auto;
}
.image {
diff --git a/src/web/component/timeline/index.js b/src/web/component/timeline/index.js
index 0e237a1..fa0601b 100644
--- a/src/web/component/timeline/index.js
+++ b/src/web/component/timeline/index.js
@@ -26,6 +26,9 @@ const zoom = new (class {
this.count = 5;
container.style.gridTemplateColumns = `repeat(5, 1fr)`;
container.classList.toggle('tiling', this.count != 1);
+ this.por = { x: 0, y: 0 };
+ this.eor = null;
+ this.y_offset = 0;
}
update(delta, point) {
@@ -33,23 +36,37 @@ const zoom = new (class {
this.count = Math.min(Math.max(this.count, 1), 20);
const scale = this.committed / this.count;
- const rects = Z.document.getClientRects()[0];
- const x = point.x;
- const y = point.y - rects.y;
+ const x = point.x - this.por.x;
- container.style.transformOrigin = `${x}px ${y}px`;
- container.style.transform = `scale(${scale})`;
+ container.style.transform = `translate(${x}px, 0px) scale(${scale})`;
+ }
+
+ set point_of_reference(point) {
+ const rect = container.getClientRects()[0];
+ this.por = { x: point.x, y: point.y - rect.y };
+ container.style.transformOrigin = `${this.por.x}px ${this.por.y}px`;
+ this.eor = Z.document.shadowRoot.elementFromPoint(point.x, point.y);
+ this.y_offset = this.eor.getBoundingClientRect().y;
}
commit() {
console.log('commit');
this.count = Math.round(this.count);
- container.style.transform = 'scale(1)';
container.style.gridTemplateColumns = `repeat(${this.count}, 1fr)`;
container.classList.toggle('tiling', this.count != 1);
+ container.style.transform = 'scale(1)';
this.committed = this.count;
+
+ void container.offsetHeight;
+
+ const rect = this.eor.getBoundingClientRect();
+
+ Z.document.scrollBy({
+ top: rect.y - this.y_offset,
+ behavior: 'instant',
+ });
}
});
@@ -63,10 +80,15 @@ Z.document.onwheel = (event) => {
};
const pinch = new Pinch(Z.document);
-pinch.onpinch = (delta, point) => {
- zoom.update(delta / 20, point);
+
+pinch.onstart = (event) => {
+ zoom.point_of_reference = event.point;
+};
+
+pinch.onpinch = (event) => {
+ zoom.update(event.zoom_delta / 20, event.point);
};
-pinch.onfinish = (delta) => {
+pinch.onfinish = () => {
zoom.commit();
};
diff --git a/src/web/index.css b/src/web/index.css
index 3c6b0d6..934c713 100644
--- a/src/web/index.css
+++ b/src/web/index.css
@@ -1,12 +1,19 @@
+html {
+ height: 100vh;
+ width: 100vw;
+}
+
body {
padding: 0;
margin: 0;
width: 100vw;
height: 100vh;
- touch-action: pan-y; /* Prevent default touch behavior */
+ overflow: hidden;
+ touch-action: pan-y;
}
#timeline {
display: block;
- height: 100%;
+ height: 100vh;
+ overflow-y: auto;
}
diff --git a/src/web/pinch.js b/src/web/pinch.js
index 14f843a..935dfa0 100644
--- a/src/web/pinch.js
+++ b/src/web/pinch.js
@@ -4,6 +4,7 @@ class Pinch {
this.previous_diff = null;
this.onpinch = () => {};
this.onfinish = () => {};
+ this.onstart = () => {};
element.onpointerdown = (e) => this.pointer_down_handler(e);
element.onpointermove = (e) => this.pointer_move_handler(e);
@@ -15,6 +16,15 @@ class Pinch {
pointer_down_handler(event) {
this.event_cache.push(event);
+
+ if (this.event_cache.length === 2) {
+ const point = {
+ x: (this.event_cache[0].clientX + this.event_cache[1].clientX) / 2,
+ y: (this.event_cache[0].clientY + this.event_cache[1].clientY) / 2,
+ };
+
+ this.onstart({ point });
+ }
}
pointer_move_handler(event) {
@@ -38,7 +48,7 @@ class Pinch {
};
const delta = this.previous_diff - diff;
- this.onpinch(delta, point);
+ this.onpinch({ zoom_delta: delta, point });
}
this.previous_diff = diff;
@@ -46,6 +56,11 @@ class Pinch {
}
pointer_up_handler(event) {
+ let cleanup = false;
+ if (this.event_cache.length === 2) {
+ cleanup = true;
+ }
+
const index = this.event_cache.findIndex(
(cached) => cached.pointerId === event.pointerId,
);
@@ -55,6 +70,8 @@ class Pinch {
this.previous_diff = null;
}
- this.onfinish();
+ if (cleanup) {
+ this.onfinish();
+ }
}
}