Last active
February 5, 2019 08:04
-
-
Save ivansky/d8a51e0b2294f5a7939f44cd201de4b9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type ScrollDirection = 'vertical' | 'horizontal' | 'both'; | |
/** | |
* It prevents default behaviour above the scrollable areas | |
* until it's in available scrollable direction. | |
* | |
* Example of checkScrollableElement: | |
* | |
* var cleanBlocking = ( | |
* blockOverScroll( | |
* mostParentElement, | |
* (element) => { | |
* return element.attributes && el.attributes['data-scroll'] | |
* } | |
* ) | |
* ); | |
* | |
* unsubscribeStep() { | |
* cleanBlocking(); // remove subscribes to prevent memory leak | |
* } | |
*/ | |
export function blockOverScroll( | |
element: Element, | |
getScrollDirection: (element: Element) => ScrollDirection | false, | |
): () => void { | |
let clientX = null; // remember X position on touch start | |
let clientY = null; // remember Y position on touch start | |
function touchStart(event) { | |
if (event.targetTouches.length === 1) { | |
clientX = event.targetTouches[0].clientX; | |
clientY = event.targetTouches[0].clientY; | |
} | |
} | |
function touchMove(event) { | |
if (event.targetTouches.length === 1 && event.cancelable !== false) { | |
return disableRubberBand(event); | |
} | |
} | |
function disableRubberBand(event: TouchEvent) { | |
const scrollableElement = findClosestParent( | |
event.target as any, | |
getScrollDirection as any, | |
); | |
if (!scrollableElement) { | |
event.preventDefault(); | |
return false; | |
} | |
const direction = getScrollDirection(scrollableElement) as ScrollDirection; | |
// @todo Make another logic for `both` direction | |
if (direction === 'vertical' || direction === 'both') { | |
const nextY = event.targetTouches[0].clientY - clientY; | |
if (scrollableElement.scrollTop === 0 && nextY > 0) { | |
// element is at the top of its scroll | |
event.preventDefault(); | |
} else if (isElementVerticalScrolled(scrollableElement) && nextY < 0) { | |
event.preventDefault(); | |
} | |
} else if (direction === 'horizontal') { | |
const nextX = event.targetTouches[0].clientX - clientX; | |
if (scrollableElement.scrollLeft === 0 && nextX > 0) { | |
event.preventDefault(); | |
} else if (isElementHorizontalScrolled(scrollableElement) && nextX < 0) { | |
event.preventDefault(); | |
} | |
} | |
} | |
element.addEventListener('touchstart', touchStart, false); | |
element.addEventListener('touchmove', touchMove, false); | |
return function cleanupBlockOverScroll() { | |
element.removeEventListener('touchstart', touchStart); | |
element.removeEventListener('touchmove', touchMove); | |
} | |
} | |
/** | |
* Find closest parent element or return itself | |
* which is suitable for condition function. | |
* | |
* @param element Lowest element from which we will search. | |
* @param checkParent Condition function to check element are suitable. | |
*/ | |
function findClosestParent(element: Element, checkParent: (parent: Element) => boolean) { | |
if (checkParent(element)) { | |
return element; | |
} | |
let current: Element = element.parentNode as any; | |
while (current && !checkParent(current as any)) { | |
current = current.parentNode as any; | |
} | |
return current; | |
} | |
/** | |
* Check if element is scrolled to the end. | |
* | |
* @param element Element. | |
*/ | |
function isElementVerticalScrolled(element: Element) { | |
const { | |
scrollHeight, | |
scrollTop, | |
clientHeight, | |
} = element; | |
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#Problems_and_solutions | |
return scrollHeight - scrollTop <= clientHeight; | |
} | |
function isElementHorizontalScrolled(element: Element) { | |
const { | |
scrollWidth, | |
scrollLeft, | |
clientWidth, | |
} = element; | |
return scrollWidth - scrollLeft <= clientWidth; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment