kuliso
Version:
Tiny library for performant pointer-driven or gyroscope-driven effects
156 lines (132 loc) • 3.55 kB
JavaScript
/**
* Clamps a value between limits.
*
* @private
* @param {number} min lower limit
* @param {number} max upper limit
* @param {number} value value to clamp
* @return {number} clamped value
*
* @example
* const x = clamp(0, 1, 1.5); // returns 1
*/
function clamp (min, max, value) {
return Math.min(Math.max(min, value), max);
}
/**
* Throttle a function to trigger once per animation frame.
* Keeps the arguments from last call, even if that call gets ignored.
*
* @private
* @param {function} fn function to throttle
* @return {(function(): number)} a function that will trigger the throttled function on next animation frame,
* returns the requestAnimationFrame id, so it can be cancelled
*/
function frameThrottle (fn) {
let throttled = false;
return function () {
if (!throttled) {
throttled = true;
return window.requestAnimationFrame(() => {
throttled = false;
fn();
});
}
};
}
/**
* Returns an object containing the layout properties (left, top, width, and height) of the given element.
*
* @private
* @param {HTMLElement} element
* @returns {{top: number, left: number, width: number, height: number}}
*/
function getRect (element) {
let el = element;
let left = 0;
let top = 0;
if (el.offsetParent) {
do {
left += el.offsetLeft;
top += el.offsetTop;
el = el.offsetParent;
} while (el);
}
return {
left,
top,
width: element.offsetWidth,
height: element.offsetHeight
};
}
/**
* @see https://issues.chromium.org/issues/40887601?pli=1
*/
function testPointerOffsetDprBug () {
const DPR = window.devicePixelRatio;
let fixRequired = false;
if (DPR === 1) {
// we can immediately return here, not required to fix
return false;
}
document.body.addEventListener('pointerdown', (e) => {
fixRequired = e.offsetX !== 10;
}, { once: true });
const event = new PointerEvent('pointerdown', {
clientX: 10
});
document.body.dispatchEvent(event);
return fixRequired;
}
function testPointerOffsetScrollBug () {
return new Promise((resolve) => {
const startY = window.scrollY;
let fixRequired = false;
let currentY;
function sampleOffsetY () {
document.body.addEventListener('pointerdown', (e) => {
if (currentY === undefined) {
currentY = e.offsetY;
} else {
// when dispatching on BODY in different scroll it gives the same offsetY value only on WebKit
fixRequired = e.offsetY === currentY;
}
}, { once: true });
const event = new PointerEvent('pointerdown', {
clientY: 500
});
document.body.dispatchEvent(event);
}
function scrollHandler () {
if (window.scrollY !== startY) {
window.removeEventListener('scroll', scrollHandler);
sampleOffsetY();
resolve(fixRequired);
}
}
sampleOffsetY();
window.addEventListener('scroll', scrollHandler);
if (window.scrollY > 0) {
window.scrollBy(0, -1);
}
});
}
/**
* @see https://bugs.webkit.org/show_bug.cgi?id=287799
*/
function testScrollOffsetsForWebKitPointerBug (scrollOffsets) {
testPointerOffsetScrollBug().then((fixRequired) => {
scrollOffsets.fixRequired = fixRequired;
if (fixRequired) {
window.addEventListener('scroll', scrollOffsets.scrollHandler);
scrollOffsets.scrollHandler();
}
});
}
export {
getRect,
clamp,
frameThrottle,
testPointerOffsetDprBug,
testScrollOffsetsForWebKitPointerBug
};