88 lines
4.9 KiB
JavaScript
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 };
|
|
})();
|