UNPKG

trello-smooth-dnd

Version:

Drag and Drop library to support react-trello project

197 lines (176 loc) 4.69 kB
import { isScrolling, getScrollingAxis, getVisibleRect, debounce } from './utils'; const maxSpeed = 1500; // px/s const minSpeed = 20; // px/s function addScrollValue(element, axis, value) { if (element) { if (element !== window) { if (axis === 'x') { element.scrollLeft += value; } else { element.scrollTop += value; } } else { if (axis === 'x') { element.scrollBy(value, 0); } else { element.scrollBy(0, value); } } } } const createAnimator = (element, axis = 'y') => { let isAnimating = false; let request = null; let startTime = null; let direction = null; let speed = null; function animate(_direction, _speed) { direction = _direction; speed = _speed; isAnimating = true; if (isAnimating) { start(); } } function start() { if (request === null) { request = requestAnimationFrame((timestamp) => { if (startTime === null) { startTime = timestamp; } const timeDiff = timestamp - startTime; startTime = timestamp; let distanceDiff = (timeDiff / 1000) * speed; distanceDiff = direction === 'begin' ? (0 - distanceDiff) : distanceDiff; addScrollValue(element, axis, distanceDiff); request = null; start(); }); } } function stop() { if (isAnimating) { cancelAnimationFrame(request); isAnimating = false; startTime = null; request = null; } } return { animate, stop }; } function getAutoScrollInfo(position, scrollableInfo) { const { left, right, top, bottom } = scrollableInfo.rect; const { x, y } = position; if (x < left || x > right || y < top || y > bottom) { return null; } let begin; let end; let pos; if (scrollableInfo.axis === 'x') { begin = left; end = right; pos = x; } else { begin = top; end = bottom; pos = y; } const moveDistance = 100; if (end - pos < moveDistance) { return { direction: 'end', speedFactor: (moveDistance - (end - pos)) / moveDistance }; } else if (pos - begin < moveDistance) { // console.log(pos - begin); return { direction: 'begin', speedFactor: (moveDistance - (pos - begin)) / moveDistance }; } } function scrollableInfo(element) { var result = { element, rect: getVisibleRect(element, element.getBoundingClientRect()), descendants: [], invalidate, axis: null, dispose }; function dispose() { element.removeEventListener('scroll', invalidate); } function invalidate() { result.rect = getVisibleRect(element, element.getBoundingClientRect()); result.descendants.forEach(p => p.invalidate()); } element.addEventListener('scroll', invalidate); return result; } function getScrollableElements(containerElements) { const scrollables = []; let firstDescendentScrollable = null; containerElements.forEach(el => { let current = el; firstDescendentScrollable = null; while (current) { const scrollingAxis = getScrollingAxis(current); if (scrollingAxis) { if (!scrollables.some(p => p.element === current)) { const info = scrollableInfo(current); if (firstDescendentScrollable) { info.descendants.push(firstDescendentScrollable); } firstDescendentScrollable = info; if (scrollingAxis === 'xy') { scrollables.push(Object.assign({}, info, { axis: 'x' })); scrollables.push(Object.assign({}, info, { axis: 'y' }, { descendants: [] })); } else { scrollables.push(Object.assign({}, info, { axis: scrollingAxis })); } } } current = current.parentElement; } }); return scrollables; } function getScrollableAnimator(scrollableInfo) { return Object.assign(scrollableInfo, createAnimator(scrollableInfo.element, scrollableInfo.axis)); } function getWindowAnimators() { function getWindowRect() { return { left: 0, right: global.innerWidth, top: 0, bottom: global.innerHeight } } return [ Object.assign({ rect: getWindowRect(), axis: 'y' }, createAnimator(global)), Object.assign({ rect: getWindowRect(), axis: 'x' }, createAnimator(global, 'x')) ] } export default (containers) => { const scrollablesInfo = getScrollableElements(containers.map(p => p.element)); const animators = [...scrollablesInfo.map(getScrollableAnimator), ...getWindowAnimators()]; return ({ draggableInfo, reset }) => { if (animators.length) { if (reset) { animators.forEach(p => p.stop()); scrollablesInfo.forEach(p => p.dispose()); return null; } animators.forEach(animator => { const scrollParams = getAutoScrollInfo(draggableInfo.mousePosition, animator); if (scrollParams) { animator.animate(scrollParams.direction, scrollParams.speedFactor * maxSpeed); } else { animator.stop(); } }); } } return null; }