fix(h5): prevent native scrolling from stealing diagonal image swipes
This commit is contained in:
@@ -1522,6 +1522,7 @@
|
||||
}
|
||||
var swipe = SwipeTrack.create(viewport, track, { count: panels.length, gestureTarget: card,
|
||||
gestureSelector: '.viz-frame', startThreshold: 12, directionRatio: 2 / 3, maxOverscroll: 16,
|
||||
preventNativeHorizontalPan: true,
|
||||
onStart: function () { viewport.style.height = viewport.getBoundingClientRect().height + 'px'; },
|
||||
onCancel: height, onSelect: function (i) { setDetailTab(card, String(i)); } });
|
||||
detailSwipes.set(card, { swipe: swipe, height: height });
|
||||
|
||||
+26
-11
@@ -18,6 +18,20 @@ window.SwipeTrack = (function () {
|
||||
if (instant) { void track.offsetWidth; track.style.transition = ''; }
|
||||
}
|
||||
function cancel() { go(index, true); if (options.onCancel) options.onCancel(index); }
|
||||
function lockDirection(dx, dy) {
|
||||
if (drag.vertical) return false;
|
||||
if (drag.horizontal) return true;
|
||||
var threshold = options.startThreshold || 8, ratio = options.directionRatio || 1;
|
||||
if (Math.max(Math.abs(dx), Math.abs(dy)) <= threshold) return false;
|
||||
// Touch and pointer events must share the same decision, regardless of
|
||||
// which event stream the browser dispatches first.
|
||||
if (Math.abs(dy) * ratio >= Math.abs(dx)) { drag.vertical = true; return false; }
|
||||
drag.horizontal = true;
|
||||
if (options.onStart) options.onStart(index);
|
||||
viewport.classList.add('dragging'); track.classList.add('dragging');
|
||||
try { target.setPointerCapture(drag.id); } catch (_) {}
|
||||
return true;
|
||||
}
|
||||
target.addEventListener('pointerdown', function (e) {
|
||||
if (drag.id != null || e.isPrimary === false || e.button !== 0 || !viewport.clientWidth) return;
|
||||
suppressUntil = 0; // A fresh tap is intentional, not the preceding drag's click.
|
||||
@@ -31,17 +45,7 @@ window.SwipeTrack = (function () {
|
||||
target.addEventListener('pointermove', function (e) {
|
||||
if (e.pointerId !== drag.id || drag.vertical) return;
|
||||
var dx = e.clientX - drag.startX, dy = e.clientY - drag.startY;
|
||||
if (!drag.horizontal) {
|
||||
var threshold = options.startThreshold || 8, ratio = options.directionRatio || 1;
|
||||
if (Math.max(Math.abs(dx), Math.abs(dy)) <= threshold) return;
|
||||
// Each caller chooses its diagonal bias. Once locked, neither direction
|
||||
// can steal this gesture before the finger lifts.
|
||||
if (Math.abs(dy) * ratio >= Math.abs(dx)) { drag.vertical = true; return; }
|
||||
drag.horizontal = true;
|
||||
if (options.onStart) options.onStart(index);
|
||||
viewport.classList.add('dragging'); track.classList.add('dragging');
|
||||
try { target.setPointerCapture(e.pointerId); } catch (_) {}
|
||||
}
|
||||
if (!lockDirection(dx, dy)) return;
|
||||
if (e.cancelable) e.preventDefault();
|
||||
var elapsed = e.timeStamp - drag.lastAt;
|
||||
if (elapsed > 0) drag.velocity = (e.clientX - drag.lastX) / elapsed;
|
||||
@@ -53,6 +57,17 @@ window.SwipeTrack = (function () {
|
||||
track.style.transform = 'translate3d(' + offset + 'px,0,0)';
|
||||
if (options.onMove) options.onMove(Math.max(0, Math.min(options.count - 1, -offset / drag.width)));
|
||||
});
|
||||
if (options.preventNativeHorizontalPan) {
|
||||
// preventDefault on pointermove does not stop native viewport panning.
|
||||
// Cancel only the single-touch horizontal gesture before the browser
|
||||
// starts scrolling and sends pointercancel. Vertical/text/multi-touch
|
||||
// gestures keep their native behavior; taps still open image zoom.
|
||||
target.addEventListener('touchmove', function (e) {
|
||||
if (drag.id == null || e.touches.length !== 1) return;
|
||||
var touch = e.touches[0];
|
||||
if (lockDirection(touch.clientX - drag.startX, touch.clientY - drag.startY) && e.cancelable) e.preventDefault();
|
||||
}, { passive: false });
|
||||
}
|
||||
window.addEventListener('pointerup', function (e) {
|
||||
if (e.pointerId !== drag.id) return;
|
||||
var horizontal = drag.horizontal, dx = e.clientX - drag.startX, next = index;
|
||||
|
||||
@@ -65,7 +65,7 @@ try {
|
||||
const touch = (type, x, y, timestamp) => cdp.send('Input.dispatchTouchEvent', {
|
||||
type, ...(timestamp ? { timestamp } : {}), touchPoints: /touchEnd|touchCancel/.test(type) ? [] : [{ x, y, id: 1 }]
|
||||
});
|
||||
async function drag(card, dx, { dy = 0, cancel = false, pause = 0, flick = false, verify = false, text = false } = {}) {
|
||||
async function drag(card, dx, { dy = 0, cancel = false, pause = 0, flick = false, verify = false, text = false, steps = 2, horizontal = false } = {}) {
|
||||
const frame = card.locator('.dpanel.active ' + (text ? '.ditem-text' : '.viz-frame'));
|
||||
await frame.scrollIntoViewIfNeeded(); await page.waitForTimeout(400);
|
||||
const box = await frame.boundingBox(), x = box.x + box.width / 2, y = box.y + box.height / 2;
|
||||
@@ -73,10 +73,17 @@ try {
|
||||
const scrollBefore = await page.locator('#s-report').evaluate(el => el.scrollTop);
|
||||
const beforeHeight = await card.locator('.detail-viewport').evaluate(el => el.getBoundingClientRect().height);
|
||||
await touch('touchStart', x, y, now);
|
||||
await touch('touchMove', x + dx / 2, y + dy / 2, now && now + .02);
|
||||
await touch('touchMove', x + dx, y + dy, now && now + .04);
|
||||
for (let step = 1; step <= steps; step++) {
|
||||
await touch('touchMove', x + dx * step / steps, y + dy * step / steps, now && now + .04 * step / steps);
|
||||
if (steps > 2) await page.waitForTimeout(20);
|
||||
}
|
||||
const liveOffset = await offset(card), limit = await card.locator('.detail-viewport').evaluate(el => el.clientWidth * (el.querySelectorAll('.dpanel').length - 1));
|
||||
assert(liveOffset <= 17 && liveOffset >= -limit - 17, 'edge resistance caps overscroll at 16px');
|
||||
if (horizontal) {
|
||||
assert(Math.abs(liveOffset - before - dx) < 3, 'diagonal drag remains finger-following before release');
|
||||
assert.equal(await card.locator('.detail-panels.dragging').count(), 1, 'browser does not cancel diagonal image gesture');
|
||||
assert(Math.abs(await page.locator('#s-report').evaluate(el => el.scrollTop) - scrollBefore) < 1, 'diagonal image gesture does not scroll the report');
|
||||
}
|
||||
if (verify) {
|
||||
assert(Math.abs(await offset(card) - before - dx) < 3, 'report follows finger before release');
|
||||
assert.equal(await selected(card), previous, 'selection commits only on release');
|
||||
@@ -97,6 +104,7 @@ try {
|
||||
if (pause) await page.waitForTimeout(pause);
|
||||
await touch(cancel ? 'touchCancel' : 'touchEnd', 0, 0, now && now + .05);
|
||||
await page.waitForTimeout(400);
|
||||
if (horizontal) assert(Math.abs(await page.locator('#s-report').evaluate(el => el.scrollTop) - scrollBefore) < 1, 'horizontal release has no vertical momentum');
|
||||
if (dy <= -80 && Math.abs(dx) < 10) {
|
||||
assert(await page.locator('#s-report').evaluate(el => el.scrollTop) > scrollBefore + 10, 'vertical gesture actually scrolls the report');
|
||||
}
|
||||
@@ -121,6 +129,11 @@ try {
|
||||
await drag(card, 100, { cancel: true }); assert.equal(await selected(card), '1', 'cancellation restores selection');
|
||||
await drag(card, 2, { dy: -90 }); assert.equal(await selected(card), '1', 'vertical scrolling preserves tab');
|
||||
await drag(card, 65, { dy: -60 }); assert.equal(await selected(card), '0', 'diagonal image gesture prioritizes horizontal switching');
|
||||
for (const [dx, dy] of [[-60, -75], [-80, -90]]) {
|
||||
await drag(card, dx, { dy, steps: 6, horizontal: true });
|
||||
assert.equal(await selected(card), '1', 'vertical-heavy diagonal still uses configured horizontal priority');
|
||||
await card.locator('.dtab').first().click(); await page.waitForTimeout(400);
|
||||
}
|
||||
await card.locator('.dtab').nth(1).click(); await page.waitForTimeout(400);
|
||||
await drag(card, 100, { text: true }); assert.equal(await selected(card), '1', 'horizontal dragging on analysis text does not switch tabs');
|
||||
await drag(card, 2, { dy: -90, text: true }); assert.equal(await selected(card), '1', 'reading text keeps vertical scrolling and current tab');
|
||||
|
||||
Reference in New Issue
Block a user