Files
juzi/h5/js/swipe-track.js
T

88 lines
4.9 KiB
JavaScript

/* Shared finger-following track for skin choices and report detail panels. */
window.SwipeTrack = (function () {
'use strict';
function create(viewport, track, options) {
var target = options.gestureTarget || viewport, index = 0;
var drag = { id: null, horizontal: false, vertical: false }, suppressUntil = 0;
function release() {
var id = drag.id;
drag.id = null; drag.horizontal = drag.vertical = false;
viewport.classList.remove('dragging'); track.classList.remove('dragging');
try { if (id != null && target.hasPointerCapture(id)) target.releasePointerCapture(id); } catch (_) {}
}
function go(next, instant) {
index = Math.max(0, Math.min(options.count - 1, next));
release();
if (instant) track.style.transition = 'none';
track.style.transform = 'translateX(-' + index * 100 + '%)';
if (instant) { void track.offsetWidth; track.style.transition = ''; }
}
function cancel() { go(index, true); if (options.onCancel) options.onCancel(index); }
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.
if (options.gestureSelector && (!e.target.closest || !e.target.closest(options.gestureSelector))) return;
drag.id = e.pointerId; drag.startX = e.clientX; drag.startY = e.clientY;
drag.offset = track.getBoundingClientRect().left - viewport.getBoundingClientRect().left;
drag.width = viewport.clientWidth; drag.lastX = e.clientX; drag.lastAt = e.timeStamp; drag.velocity = 0;
drag.horizontal = drag.vertical = false;
// Capture only after direction lock: an ordinary image tap must still zoom.
});
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;
// Ambiguous diagonal movement belongs to page scrolling. 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 (e.cancelable) e.preventDefault();
var elapsed = e.timeStamp - drag.lastAt;
if (elapsed > 0) drag.velocity = (e.clientX - drag.lastX) / elapsed;
drag.lastX = e.clientX; drag.lastAt = e.timeStamp;
var offset = drag.offset + dx, min = -(options.count - 1) * drag.width;
var edgeLimit = options.maxOverscroll == null ? Infinity : options.maxOverscroll;
if (offset > 0) offset = Math.min(edgeLimit, offset * .25);
else if (offset < min) offset = min - Math.min(edgeLimit, (min - offset) * .25);
track.style.transform = 'translate3d(' + offset + 'px,0,0)';
if (options.onMove) options.onMove(Math.max(0, Math.min(options.count - 1, -offset / drag.width)));
});
window.addEventListener('pointerup', function (e) {
if (e.pointerId !== drag.id) return;
var horizontal = drag.horizontal, dx = e.clientX - drag.startX, next = index;
if (horizontal) {
var start = Math.round(-drag.offset / drag.width);
next = start;
var speed = e.timeStamp - drag.lastAt > 100 ? 0 : drag.velocity;
if (Math.abs(dx) >= Math.min(50, drag.width * .2) || (Math.abs(dx) > 12 && Math.abs(speed) > .4)) {
next = Math.round(-(drag.offset + dx) / drag.width);
if (next === start) next += (Math.abs(speed) > .4 ? speed : dx) < 0 ? 1 : -1;
}
suppressUntil = Date.now() + 350;
}
go(next);
if (horizontal && options.onSelect) options.onSelect(index);
});
window.addEventListener('pointercancel', function (e) { if (e.pointerId === drag.id) cancel(); });
target.addEventListener('lostpointercapture', function (e) {
// Touch starts with implicit capture on an image/button. Its bubbled loss
// when we take horizontal capture is not a cancellation of this track.
if (e.target === target && e.pointerId === drag.id) cancel();
});
target.addEventListener('dragstart', function (e) { e.preventDefault(); });
target.addEventListener('click', function (e) {
if (Date.now() < suppressUntil) { e.preventDefault(); e.stopPropagation(); }
}, true);
window.addEventListener('resize', cancel);
go(0, true);
return { go: go, cancel: cancel, isDragging: function () { return drag.horizontal; } };
}
return { create: create };
})();