UNPKG

perfect-scrollbar

Version:

Minimalistic but perfect custom scrollbar plugin

220 lines (181 loc) 5.82 kB
import updateGeometry from '../update-geometry'; import cls from '../lib/class-names'; import * as CSS from '../lib/css'; import { env } from '../lib/util'; export default function (i) { if (!env.supportsTouch && !env.supportsIePointer) { return; } const element = i.element; const state = { startOffset: {}, startTime: 0, speed: {}, easingLoop: null, }; function shouldPrevent(deltaX, deltaY) { const scrollTop = Math.floor(element.scrollTop); const scrollLeft = element.scrollLeft; const magnitudeX = Math.abs(deltaX); const magnitudeY = Math.abs(deltaY); if (magnitudeY > magnitudeX) { // user is perhaps trying to swipe up/down the page if ( (deltaY < 0 && scrollTop === i.contentHeight - i.containerHeight) || (deltaY > 0 && scrollTop === 0) ) { // set prevent for mobile Chrome refresh return window.scrollY === 0 && deltaY > 0 && env.isChrome; } } else if (magnitudeX > magnitudeY) { // user is perhaps trying to swipe left/right across the page if ( (deltaX < 0 && scrollLeft === i.contentWidth - i.containerWidth) || (deltaX > 0 && scrollLeft === 0) ) { return true; } } return true; } function applyTouchMove(differenceX, differenceY) { element.scrollTop -= differenceY; element.scrollLeft -= differenceX; updateGeometry(i); } function getTouch(e) { if (e.targetTouches) { return e.targetTouches[0]; } // Maybe IE pointer return e; } function shouldHandle(e) { if (e.target === i.scrollbarX || e.target === i.scrollbarY) { return false; } if (e.pointerType && e.pointerType === 'pen' && e.buttons === 0) { return false; } if (e.targetTouches && e.targetTouches.length === 1) { return true; } if (e.pointerType && e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) { return true; } return false; } function touchStart(e) { if (!shouldHandle(e)) { return; } const touch = getTouch(e); state.startOffset.pageX = touch.pageX; state.startOffset.pageY = touch.pageY; state.startTime = new Date().getTime(); if (state.easingLoop !== null) { clearInterval(state.easingLoop); } } function shouldBeConsumedByChild(target, deltaX, deltaY) { if (!element.contains(target)) { return false; } let cursor = target; while (cursor && cursor !== element) { if (cursor.classList.contains(cls.element.consuming)) { return true; } const style = CSS.get(cursor); // if deltaY && vertical scrollable if (deltaY && style.overflowY.match(/(scroll|auto)/)) { const maxScrollTop = cursor.scrollHeight - cursor.clientHeight; if (maxScrollTop > 0) { if ( (cursor.scrollTop > 0 && deltaY < 0) || (cursor.scrollTop < maxScrollTop && deltaY > 0) ) { return true; } } } // if deltaX && horizontal scrollable if (deltaX && style.overflowX.match(/(scroll|auto)/)) { const maxScrollLeft = cursor.scrollWidth - cursor.clientWidth; if (maxScrollLeft > 0) { if ( (cursor.scrollLeft > 0 && deltaX < 0) || (cursor.scrollLeft < maxScrollLeft && deltaX > 0) ) { return true; } } } cursor = cursor.parentNode; } return false; } function touchMove(e) { if (shouldHandle(e)) { const touch = getTouch(e); const currentOffset = { pageX: touch.pageX, pageY: touch.pageY }; const differenceX = currentOffset.pageX - state.startOffset.pageX; const differenceY = currentOffset.pageY - state.startOffset.pageY; if (shouldBeConsumedByChild(e.target, differenceX, differenceY)) { return; } applyTouchMove(differenceX, differenceY); state.startOffset = currentOffset; const currentTime = new Date().getTime(); const timeGap = currentTime - state.startTime; if (timeGap > 0) { state.speed.x = differenceX / timeGap; state.speed.y = differenceY / timeGap; state.startTime = currentTime; } if (shouldPrevent(differenceX, differenceY)) { // Prevent the default behavior if the event is cancelable if (e.cancelable) { e.preventDefault(); } } } } function touchEnd() { if (i.settings.swipeEasing) { clearInterval(state.easingLoop); state.easingLoop = setInterval(() => { if (i.isInitialized) { clearInterval(state.easingLoop); return; } if (!state.speed.x && !state.speed.y) { clearInterval(state.easingLoop); return; } if (Math.abs(state.speed.x) < 0.01 && Math.abs(state.speed.y) < 0.01) { clearInterval(state.easingLoop); return; } applyTouchMove(state.speed.x * 30, state.speed.y * 30); state.speed.x *= 0.8; state.speed.y *= 0.8; }, 10); } } if (env.supportsTouch) { i.event.bind(element, 'touchstart', touchStart); i.event.bind(element, 'touchmove', touchMove); i.event.bind(element, 'touchend', touchEnd); } else if (env.supportsIePointer) { if (window.PointerEvent) { i.event.bind(element, 'pointerdown', touchStart); i.event.bind(element, 'pointermove', touchMove); i.event.bind(element, 'pointerup', touchEnd); } else if (window.MSPointerEvent) { i.event.bind(element, 'MSPointerDown', touchStart); i.event.bind(element, 'MSPointerMove', touchMove); i.event.bind(element, 'MSPointerUp', touchEnd); } } }