Skip to content

Commit 9dabd71

Browse files
committed
Improve scroll and pinch
1 parent ad75a35 commit 9dabd71

6 files changed

Lines changed: 42 additions & 38 deletions

File tree

packages/plugin-zoom/src/preact/adapter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export { Fragment } from 'preact';
2-
export { useEffect, useRef, useState, useCallback, useMemo } from 'preact/hooks';
2+
export { useEffect, useRef, useState, useCallback, useMemo, useLayoutEffect } from 'preact/hooks';
33
export type { ComponentChildren as ReactNode } from 'preact';
44

55
export type CSSProperties = import('preact').JSX.CSSProperties;
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
1-
export { Fragment, useEffect, useRef, useState, useCallback, useMemo } from 'react';
1+
export {
2+
Fragment,
3+
useEffect,
4+
useRef,
5+
useState,
6+
useCallback,
7+
useMemo,
8+
useLayoutEffect,
9+
} from 'react';
210
export type { ReactNode, HTMLAttributes, CSSProperties } from 'react';

packages/plugin-zoom/src/shared/components/pinch-wrapper.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,9 @@ export function PinchWrapper({ children, documentId, style, ...props }: PinchWra
1616
{...props}
1717
style={{
1818
...style,
19-
display: 'block',
20-
width: 'fit-content',
19+
display: 'inline-block',
2120
overflow: 'visible',
2221
boxSizing: 'border-box',
23-
margin: '0px auto',
2422
}}
2523
>
2624
{children}

packages/plugin-zoom/src/shared/utils/pinch-zoom-logic.ts

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,8 @@ export function setupPinchZoom({
5858
let containerRectHeight = 0;
5959

6060
// Layout Dimensions (Client Box from Metrics)
61-
// This is the actual space the CSS uses for centering.
6261
let layoutWidth = 0;
63-
let layoutCenterX = 0; // Relative to the container Rect origin
62+
let layoutCenterX = 0;
6463

6564
let pointerLocalY = 0;
6665
let pointerContainerX = 0;
@@ -71,34 +70,36 @@ export function setupPinchZoom({
7170

7271
const clamp = (val: number, min: number, max: number) => Math.min(Math.max(val, min), max);
7372

73+
// --- Margin calculation (no scroll plugin needed!) ---
74+
const updateMargin = () => {
75+
const metrics = viewportScope.getMetrics();
76+
const vpGap = viewportProvides.getViewportGap() || 0;
77+
const availableWidth = metrics.clientWidth - 2 * vpGap;
78+
79+
// Use element's actual rendered width - no need for scroll plugin!
80+
const elementWidth = element.offsetWidth;
81+
82+
const newMargin = elementWidth < availableWidth ? (availableWidth - elementWidth) / 2 : 0;
83+
84+
element.style.marginLeft = `${newMargin}px`;
85+
};
86+
7487
const calculateTransform = (scale: number) => {
7588
const finalWidth = initialElementWidth * scale;
7689
const finalHeight = initialElementHeight * scale;
7790

7891
let ty = pointerLocalY * (1 - scale);
7992

80-
// --- 1. Center-based Transform (The "Structural" Center) ---
81-
// Instead of using containerRectWidth, we use the layoutCenterX derived from Metrics.
82-
// layoutCenterX is the specific pixel where the content center should align.
83-
84-
// Target X position relative to Container Rect Left:
8593
const targetX = layoutCenterX - finalWidth / 2;
86-
87-
// Convert to translation (tx) relative to initial position:
8894
const txCenter = targetX - initialElementLeft;
89-
90-
// --- 2. Mouse-based Transform ---
9195
const txMouse = pointerContainerX - pivotLocalX * scale - initialElementLeft;
9296

93-
// --- 3. Blending ---
94-
// Compare finalWidth against layoutWidth (actual available space).
9597
const overflow = Math.max(0, finalWidth - layoutWidth);
9698
const blendRange = layoutWidth * 0.3;
9799
const blend = Math.min(1, overflow / blendRange);
98100

99101
let tx = txCenter + (txMouse - txCenter) * blend;
100102

101-
// --- 4. Gap-Aware Clamping ---
102103
const safeHeight = containerRectHeight - currentGap * 2;
103104
if (finalHeight > safeHeight) {
104105
const currentTop = initialElementTop + ty;
@@ -134,17 +135,13 @@ export function setupPinchZoom({
134135
};
135136

136137
const commitZoom = () => {
137-
const { tx, ty, finalWidth } = calculateTransform(currentScale);
138+
const { tx, finalWidth } = calculateTransform(currentScale);
138139
const delta = (currentScale - 1) * initialZoom;
139140

140141
let anchorX: number;
141142
let anchorY: number = pointerContainerY;
142143

143-
// --- CRITICAL FIX ---
144-
// If the content fits within the LAYOUT width (not just rect width),
145-
// we force the anchor to be the Layout Center.
146144
if (finalWidth <= layoutWidth) {
147-
// anchorX is relative to the Container Rect Origin (which zoomScope uses)
148145
anchorX = layoutCenterX;
149146
} else {
150147
const scaleDiff = 1 - currentScale;
@@ -160,8 +157,6 @@ export function setupPinchZoom({
160157
const initializeGestureState = (clientX: number, clientY: number) => {
161158
const contRect = viewportScope.getBoundingRect();
162159
const innerRect = element.getBoundingClientRect();
163-
164-
// FETCH METRICS (Single Source of Truth)
165160
const metrics = viewportScope.getMetrics();
166161

167162
currentGap = viewportProvides.getViewportGap() || 0;
@@ -173,12 +168,7 @@ export function setupPinchZoom({
173168
containerRectWidth = contRect.size.width;
174169
containerRectHeight = contRect.size.height;
175170

176-
// --- CLEAN LAYOUT CALCULATION ---
177-
// We use the viewport metrics to determine the layout geometry.
178-
// clientWidth: The width available for content (excludes scrollbars/borders)
179-
// clientLeft: The width of the left border (offset from Rect origin to Content origin)
180171
const clientLeft = metrics.clientLeft;
181-
182172
layoutWidth = metrics.clientWidth;
183173
layoutCenterX = clientLeft + layoutWidth / 2;
184174

@@ -187,7 +177,6 @@ export function setupPinchZoom({
187177
pointerContainerX = clientX - contRect.origin.x;
188178
pointerContainerY = clientY - contRect.origin.y;
189179

190-
// Pivot Calculation based on Layout Width
191180
if (initialElementWidth < layoutWidth) {
192181
pivotLocalX = (pointerContainerX * initialElementWidth) / layoutWidth;
193182
} else {
@@ -245,6 +234,16 @@ export function setupPinchZoom({
245234
}, 150);
246235
};
247236

237+
// Subscribe to zoom changes to update margin
238+
const unsubZoom = zoomScope.onStateChange(() => updateMargin());
239+
240+
// Use ResizeObserver to update margin when element size changes
241+
const resizeObserver = new ResizeObserver(() => updateMargin());
242+
resizeObserver.observe(element);
243+
244+
// Initial margin calculation
245+
updateMargin();
246+
248247
element.addEventListener('touchstart', handleTouchStart, { passive: false });
249248
element.addEventListener('touchmove', handleTouchMove, { passive: false });
250249
element.addEventListener('touchend', handleTouchEnd);
@@ -260,6 +259,9 @@ export function setupPinchZoom({
260259
if (wheelZoomTimeout) {
261260
clearTimeout(wheelZoomTimeout);
262261
}
262+
unsubZoom();
263+
resizeObserver.disconnect();
263264
resetTransform();
265+
element.style.marginLeft = '';
264266
};
265267
}

packages/plugin-zoom/src/svelte/components/PinchWrapper.svelte

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,9 @@
1616
<div
1717
bind:this={pinch.elementRef}
1818
{...restProps}
19-
style:display="block"
20-
style:width="fit-content"
19+
style:display="inline-block"
2120
style:overflow="visible"
2221
style:box-sizing="border-box"
23-
style:margin="0px auto"
2422
class={propsClass}
2523
>
2624
{@render children()}

packages/plugin-zoom/src/vue/components/pinch-wrapper.vue

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
<div
33
ref="elementRef"
44
:style="{
5-
display: 'block',
6-
width: 'fit-content',
5+
display: 'inline-block',
76
overflow: 'visible',
87
boxSizing: 'border-box',
9-
margin: '0px auto',
108
}"
119
v-bind="$attrs"
1210
>

0 commit comments

Comments
 (0)