react-scroll-box
Version:
Charged cross-browser and cross-platform scrollable container implementation with no external dependencies but React 0.13+
1,082 lines (879 loc) • 29.3 kB
JavaScript
import React from 'react';
import {findDOMNode} from 'react-dom';
import {any, bool, element, func, node, number, object, oneOf, string} from 'prop-types';
function isCoordinate(value) {
return value != null && !isNaN(value);
}
export const FastTrackMode = {
PAGING: 'paging',
GOTO: 'goto'
};
export const ScrollCause = {
HANDLE_DRAG: 0,
MOUSE_WHEEL: 1,
FAST_TRACK: 2,
KEYBOARD: 3,
TOUCH: 4
};
export const FastTrackModeShape = oneOf([FastTrackMode.GOTO, FastTrackMode.PAGING]);
export function easeQuadOut(percent, elapsed, min, max, duration) {
percent -= 1;
return min + max * Math.sqrt(1 - Math.pow(percent, 2));
}
export class GenericScrollBox extends React.Component {
static propTypes = {
// Viewport element.
children: element.isRequired,
// Use client scroll bars.
clientScrollBars: bool,
className: any,
style: object,
disabled: bool,
// Events
onScroll: func,
onScrollX: func,
onScrollY: func,
onScrollStart: func,
onScrollStartX: func,
onScrollStartY: func,
onScrollEnd: func,
onScrollEndX: func,
onScrollEndY: func,
// Allow scrolling in corresponding direction.
disableScrollX: bool,
disableScrollY: bool,
// Toggle scroll bar visibility for corresponding direction.
hideScrollBarX: bool,
hideScrollBarY: bool,
// Toggle show scroll bars outside of scroll box rectangle.
// This has no effect when client scroll bars are enabled and they are shown above content.
outsetScrollBarX: bool,
outsetScrollBarY: bool,
// Minimum number of pixels when scroll bars are required.
scrollMinX: number,
scrollMinY: number,
// Distance from cursor to edge of the scroll track when scroll bar is considered to be hovered.
trackHoverProximityX: number,
trackHoverProximityY: number,
// Default easing functions applied when scroll with non-zero duration is requested.
easingX: func,
easingY: func,
// Handle drag
captureHandleDragX: bool,
captureHandleDragY: bool,
permitHandleDragInterruption: bool,
// Fast tracking occurs when user clicks on scroll track.
captureFastTrackX: bool,
captureFastTrackY: bool,
fastTrackModeX: FastTrackModeShape,
fastTrackModeY: FastTrackModeShape,
fastTrackScrollDurationX: number,
fastTrackScrollDurationY: number,
// Keyboard
captureKeyboard: bool,
keyboardStepX: number,
keyboardStepY: number,
keyboardScrollDurationX: number,
keyboardScrollDurationY: number,
// Wheel
captureWheel: bool,
lineHeight: number,
wheelStepX: number,
wheelStepY: number,
propagateWheelScrollX: bool,
propagateWheelScrollY: bool,
swapWheelAxes: bool,
wheelScrollDurationX: number,
wheelScrollDurationY: number,
// Touch
captureTouch: bool,
propagateTouchScrollX: bool,
propagateTouchScrollY: bool,
// Layout
trackChildrenX: node,
trackChildrenY: node,
handleChildrenX: node,
handleChildrenY: node
};
static defaultProps = {
clientScrollBars: false,
className: 'scroll-box--wrapped',
disabled: false,
onScroll: (target, dx, dy, causeX, causeY) => {},
onScrollX: (target, dx, causeX) => {},
onScrollY: (target, dy, causeY) => {},
onScrollStart: (target, causeX, causeY) => {},
onScrollStartX: (target, causeX) => {},
onScrollStartY: (target, causeY) => {},
onScrollEnd: (target, causeX, causeY) => {},
onScrollEndX: (target, causeX) => {},
onScrollEndY: (target, causeY) => {},
disableScrollX: false,
disableScrollY: false,
hideScrollBarX: false,
hideScrollBarY: false,
outsetScrollBarX: false,
outsetScrollBarY: false,
scrollMinX: 2,
scrollMinY: 2,
trackHoverProximityX: 50,
trackHoverProximityY: 50,
easingX: easeQuadOut,
easingY: easeQuadOut,
// Handle drag
captureHandleDragX: true,
captureHandleDragY: true,
permitHandleDragInterruption: true,
// Fast track
captureFastTrackX: true,
captureFastTrackY: true,
fastTrackModeX: FastTrackMode.GOTO,
fastTrackModeY: FastTrackMode.GOTO,
fastTrackScrollDurationX: 500,
fastTrackScrollDurationY: 500,
// Keyboard
captureKeyboard: true,
keyboardStepX: 30,
keyboardStepY: 30,
keyboardScrollDuration: 200,
// Wheel
captureWheel: true,
lineHeight: 24,
wheelStepX: 100,
wheelStepY: 100,
propagateWheelScrollX: true,
propagateWheelScrollY: true,
swapWheelAxes: false,
wheelScrollDurationX: 100,
wheelScrollDurationY: 100,
// Touch
captureTouch: true,
propagateTouchScrollX: true,
propagateTouchScrollY: true,
};
constructor(props) {
super(props);
let _scrollX = 0,
_scrollY = 0,
_previousX = 0,
_previousY = 0,
_targetX = 0,
_targetY = 0,
_durationX = 0,
_durationY = 0,
_easingX,
_easingY,
_timestampX = 0,
_timestampY = 0,
_dispatchPrevented = false,
_scrollMaxX = 0,
_scrollMaxY = 0,
_trackMaxX = 0,
_trackMaxY = 0,
_requiresScrollBarX = false,
_requiresScrollBarY = false,
_requestId,
_scrollingX = false,
_scrollingY = false,
_causeX = null,
_causeY = null,
_tickX = 0,
_tickY = 0,
_root,
_viewport,
_handleX,
_handleY,
_trackX,
_trackY;
this.render = () => {
const {
className,
style,
disabled,
outsetScrollBarX,
outsetScrollBarY,
clientScrollBars,
disableScrollX,
disableScrollY,
hideScrollBarX,
hideScrollBarY,
children,
trackChildrenX,
trackChildrenY,
handleChildrenX,
handleChildrenY
} = this.props;
let classNames = ['scroll-box'];
if (className) {
classNames = classNames.concat(className);
}
if (disabled) {
classNames.push('scroll-box--disabled');
}
if (outsetScrollBarX) {
classNames.push('scroll-box--outset-x');
}
if (outsetScrollBarY) {
classNames.push('scroll-box--outset-y');
}
if (!disableScrollX && !hideScrollBarX) {
classNames.push('scroll-box--enable-x');
}
if (!disableScrollY && !hideScrollBarY) {
classNames.push('scroll-box--enable-y');
}
if (clientScrollBars) {
classNames.push('scroll-box--client-scroll-bars');
}
return (
<div style={style}
className={classNames.join(' ')}
onWheel={handleWheel}
onKeyDown={handleKeyDown}
onTouchStart={handleTouchStart}
tabIndex="-1">
{children}
<div className="scroll-box__track scroll-box__track--x"
onMouseDown={handleFastTrackX}
ref="trackX">
<div className="scroll-box__handle scroll-box__handle--x"
onMouseDown={handleDragStartX}
ref="handleX">
{handleChildrenX}
</div>
{trackChildrenX}
</div>
<div className="scroll-box__track scroll-box__track--y"
onMouseDown={handleFastTrackY}
ref="trackY">
<div className="scroll-box__handle scroll-box__handle--y"
onMouseDown={handleDragStartY}
ref="handleY">
{handleChildrenY}
</div>
{trackChildrenY}
</div>
</div>
);
};
this.componentDidMount = () => {
_root = findDOMNode(this);
const {handleX, handleY, trackX, trackY} = this.refs;
_handleX = findDOMNode(handleX);
_handleY = findDOMNode(handleY);
_trackX = findDOMNode(trackX);
_trackY = findDOMNode(trackY);
_viewport = _root.firstElementChild;
const requestPropagateChanges = () => {
if (window.cancelAnimationFrame) {
_requestId = requestAnimationFrame(requestPropagateChanges);
} else {
_requestId = setTimeout(requestPropagateChanges, 1000 / 30);
}
propagateChanges();
};
requestPropagateChanges();
addEventListener('mousemove', handleBarHover);
};
this.componentWillUnmount = () => {
_root = null;
if (window.cancelAnimationFrame) {
cancelAnimationFrame(_requestId);
} else {
clearTimeout(_requestId);
}
removeEventListener('mousemove', handleBarHover);
};
this.componentDidUpdate = () => {
_viewport = _root.firstElementChild;
propagateChanges();
};
this.scrollTo = ({
x, y,
easing,
easingX = easing || this.props.easingX,
easingY = easing || this.props.easingY,
duration = 0,
durationX = duration,
durationY = duration,
dispatchPrevented = false
} = {}) => {
if (isCoordinate(x)) {
_previousX = _scrollX;
_targetX = x | 0;
_easingX = easingX;
_durationX = durationX;
_timestampX = Date.now();
_dispatchPrevented = dispatchPrevented;
_tickX++;
}
if (isCoordinate(y)) {
_previousY = _scrollY;
_targetY = y | 0;
_easingY = easingY;
_durationY = durationY;
_timestampY = Date.now();
_dispatchPrevented = dispatchPrevented;
_tickY++;
}
propagateChanges();
};
this.scrollToX = (x, options = {}) => this.scrollTo({...options, x});
this.scrollToY = (y, options = {}) => this.scrollTo({...options, y});
this.scrollBy = ({dx, dy, ...options} = {}) => {
this.scrollTo({
...options,
x: _targetX + dx,
y: _targetY + dy
});
};
this.scrollByX = (dx, options = {}) => this.scrollBy({...options, dx});
this.scrollByY = (dy, options = {}) => this.scrollBy({...options, dy});
this.scrollToPage = ({
x, y,
...options
} = {}) => {
x *= this.getPageWidth();
y *= this.getPageHeight();
this.scrollTo({...options, x, y});
};
this.scrollToPageX = (x, options = {}) => this.scrollToPage({...options, x});
this.scrollToPageY = (y, options = {}) => this.scrollToPage({...options, y});
this.scrollByPage = ({
dx, dy,
...options
} = {}) => {
dx *= this.getPageWidth();
dy *= this.getPageHeight();
this.scrollBy({...options, dx, dy});
};
this.scrollByPageX = (dx, options = {}) => this.scrollByPage({...options, dx});
this.scrollByPageY = (dy, options = {}) => this.scrollByPage({...options, dy});
this.getPageWidth = () => _viewport.clientWidth;
this.getPageHeight = () => _viewport.clientHeight;
Object.defineProperties(this, {
targetX: {
get: () => _targetX,
set: x => this.scrollToX(x)
},
targetY: {
get: () => _targetY,
set: y => this.scrollToY(y)
},
scrollX: {
get: () => _scrollX,
set: x => this.scrollToX(x)
},
scrollY: {
get: () => _scrollY,
set: y => this.scrollToY(y)
},
scrollMaxX: {
get: () => _scrollMaxX
},
scrollMaxY: {
get: () => _scrollMaxY
}
});
const propagateChanges = () => {
const {
disableScrollX,
disableScrollY,
scrollMinX,
scrollMinY,
clientScrollBars,
outsetScrollBarX,
outsetScrollBarY,
onScroll,
onScrollX,
onScrollY,
onScrollStart,
onScrollStartX,
onScrollStartY,
onScrollEnd,
onScrollEndX,
onScrollEndY
} = this.props;
const {
clientWidth,
clientHeight,
offsetWidth,
offsetHeight,
scrollWidth,
scrollHeight,
scrollTop,
scrollLeft
} = _viewport;
_scrollMaxX = scrollWidth - clientWidth;
_scrollMaxY = scrollHeight - clientHeight;
_requiresScrollBarX = !disableScrollX && _scrollMaxX >= scrollMinX;
_requiresScrollBarY = !disableScrollY && _scrollMaxY >= scrollMinY;
_root.classList.toggle('scroll-box--requires-x', _requiresScrollBarX);
_root.classList.toggle('scroll-box--requires-y', _requiresScrollBarY);
if (clientScrollBars && outsetScrollBarX) {
_viewport.style.height = `calc(100% + ${offsetHeight - clientHeight}px)`;
} else {
_viewport.style.height = '100%';
}
if (clientScrollBars && outsetScrollBarY) {
_viewport.style.width = `calc(100% + ${offsetWidth - clientWidth}px)`;
} else {
_viewport.style.width = '100%';
}
_targetX = Math.max(0, Math.min(_targetX, _scrollMaxX));
_targetY = Math.max(0, Math.min(_targetY, _scrollMaxY));
let nextScrollX = _scrollX,
nextScrollY = _scrollY;
if (_scrollX == scrollLeft && _scrollY == scrollTop) {
// Controlled scroll position coincides with viewport position.
if (nextScrollX != _targetX) {
const elapsedX = Date.now() - _timestampX;
if (elapsedX < _durationX) {
nextScrollX = _previousX + _easingX(elapsedX / _durationX, elapsedX, 0, 1, _durationX) * (_targetX - _previousX) | 0;
} else {
nextScrollX = _targetX;
}
}
if (nextScrollY != _targetY) {
const elapsedY = Date.now() - _timestampY;
if (elapsedY < _durationY) {
nextScrollY = _previousY + _easingY(elapsedY / _durationY, elapsedY, 0, 1, _durationY) * (_targetY - _previousY) | 0;
} else {
nextScrollY = _targetY;
}
}
}
const clientScrollingX = _scrollX != scrollLeft,
clientScrollingY = _scrollY != scrollTop;
if (clientScrollingX) {
_targetX = nextScrollX = scrollLeft;
}
if (clientScrollingY) {
_targetY = nextScrollY = scrollTop;
}
const nextScrollingX = nextScrollX != _targetX,
nextScrollingY = nextScrollY != _targetY;
const dx = _scrollX - nextScrollX,
dy = _scrollY - nextScrollY;
const tickX = _tickX,
tickY = _tickY;
if (!_dispatchPrevented) {
// Events can be triggered.
// Checking ticks after each event callback invocation to ensure that any scroll
// methods were not called inside those callbacks.
if (
(nextScrollingX | nextScrollingY | clientScrollingX | clientScrollingY) &&
!_scrollingX && !_scrollingY
) {
onScrollStart(this, _causeX, _causeY);
}
if (tickX == _tickX && nextScrollingX && !_scrollingX) {
onScrollStartX(this, _causeX);
}
if (tickY == _tickY && nextScrollingY && !_scrollingY) {
onScrollStartY(this, _causeY);
}
if (
tickX == _tickX && tickY == _tickY &&
(dx | dy)
) {
onScroll(this, dx, dy, _causeX, _causeY);
}
if (tickX == _tickX && dx) {
onScrollX(this, dx, _causeX);
}
if (tickY == _tickY && dy) {
onScrollY(this, dy, _causeY);
}
if (
tickX == _tickX && tickY == _tickY &&
!nextScrollingX && !nextScrollingY &&
(_scrollingX | _scrollingY | clientScrollingX | clientScrollingY)
) {
onScrollEnd(this, _causeX, _causeY);
}
if (tickX == _tickX && !nextScrollingX && _scrollingX) {
onScrollEndX(this, _causeX);
}
if (tickY == _tickY && !nextScrollingY && _scrollingY) {
onScrollEndY(this, _causeY);
}
if (tickX == _tickX && _causeX != ScrollCause.TOUCH | _causeX != ScrollCause.HANDLE_DRAG) {
_causeX = null;
}
if (tickY == _tickY && _causeY != ScrollCause.TOUCH | _causeY != ScrollCause.HANDLE_DRAG) {
_causeY = null;
}
}
if (dx && tickX == _tickX) {
_viewport.scrollLeft = _scrollX = nextScrollX;
}
if (dy && tickY == _tickY) {
_viewport.scrollTop = _scrollY = nextScrollY;
}
if (!clientScrollBars) {
_trackMaxX = _trackX.clientWidth - _handleX.offsetWidth;
_handleX.style.width = clientWidth / scrollWidth * 100 + '%';
_handleX.style.left = _trackMaxX * nextScrollX / _scrollMaxX + 'px';
_trackMaxY = _trackY.clientHeight - _handleY.offsetHeight;
_handleY.style.height = clientHeight / scrollHeight * 100 + '%';
_handleY.style.top = _trackMaxY * nextScrollY / _scrollMaxY + 'px';
}
};
const handleKeyDown = event => {
const {target: {tagName}, keyCode, shiftKey} = event;
const {
disabled,
captureKeyboard,
keyboardStepX,
keyboardStepY,
keyboardScrollDuration
} = this.props;
if (disabled | !captureKeyboard | tagName == 'TEXTAREA' | tagName == 'INPUT') {
// Do not handle any keyboard events when text-related controls are focused.
return;
}
const options = {duration: keyboardScrollDuration};
switch (keyCode) {
case 36: // Home
event.preventDefault();
_causeY = ScrollCause.KEYBOARD;
this.scrollToY(0, options);
break;
case 35: // End
event.preventDefault();
_causeY = ScrollCause.KEYBOARD;
this.scrollToY(_scrollMaxY, options);
break;
case 33: // Page Up
case 34: // Page Down
event.preventDefault();
let dy = this.getPageHeight(),
dx = this.getPageWidth();
if (keyCode == 33) { // Page Up
dy *= -1;
dx *= -1;
}
if (shiftKey) {
_causeX = ScrollCause.KEYBOARD;
this.scrollByX(dx, options);
} else {
_causeY = ScrollCause.KEYBOARD;
this.scrollByY(dy, options);
}
break;
case 38: // Up
event.preventDefault();
_causeY = ScrollCause.KEYBOARD;
this.scrollByY(-keyboardStepY, options);
break;
case 40: // Down
event.preventDefault();
_causeY = ScrollCause.KEYBOARD;
this.scrollByY(keyboardStepY, options);
break;
case 37: // Left
event.preventDefault();
_causeX = ScrollCause.KEYBOARD;
this.scrollByX(-keyboardStepX, options);
break;
case 39: // Right
event.preventDefault();
_causeX = ScrollCause.KEYBOARD;
this.scrollByX(keyboardStepX, options);
break;
}
};
const handleFastTrack = (event, isHorizontal) => {
const {
disabled,
captureFastTrackX,
captureFastTrackY,
fastTrackModeX,
fastTrackModeY,
fastTrackScrollDurationX,
fastTrackScrollDurationY
} = this.props;
if (disabled | !captureFastTrackX && !captureFastTrackY | event.button) {
// Component is disabled or secondary mouse button is being pressed.
return;
}
const {
clientWidth,
clientHeight,
scrollWidth,
scrollHeight
} = _viewport;
if (isHorizontal) {
if (!captureFastTrackX) {
return;
}
_causeX = ScrollCause.FAST_TRACK;
const pointerX = event.clientX - _trackX.getBoundingClientRect().left,
optionsX = {duration: fastTrackScrollDurationX};
switch (fastTrackModeX) {
case FastTrackMode.PAGING:
this.scrollToX(_targetX + (1 - 2 * (pointerX < _handleX.offsetLeft)) * this.getPageWidth(), optionsX);
break;
case FastTrackMode.GOTO:
this.scrollToX(pointerX / _trackX.clientWidth * scrollWidth - clientWidth / 2, optionsX);
break;
}
} else {
if (!captureFastTrackY) {
return;
}
_causeY = ScrollCause.FAST_TRACK;
const pointerY = event.clientY - _trackY.getBoundingClientRect().top,
optionsY = {duration: fastTrackScrollDurationY};
switch (fastTrackModeY) {
case FastTrackMode.PAGING:
this.scrollToY(_targetY + (1 - 2 * (pointerY < _handleY.offsetTop)) * this.getPageHeight(), optionsY);
break;
case FastTrackMode.GOTO:
this.scrollToY(pointerY / _trackY.clientHeight * scrollHeight - clientHeight / 2, optionsY);
break;
}
}
};
const handleFastTrackX = event => handleFastTrack(event, true);
const handleFastTrackY = event => handleFastTrack(event, false);
const handleDragStart = (event, isHorizontal) => {
const {
disabled,
captureHandleDragX,
captureHandleDragY,
permitHandleDragInterruption
} = this.props;
// Handle can be dragged with left mouse button only.
if (disabled | !captureHandleDragX && !captureHandleDragY | event.button) {
return;
}
event.preventDefault();
event.stopPropagation();
let track;
if (isHorizontal) {
_causeX = ScrollCause.HANDLE_DRAG;
track = _trackX;
} else {
_causeY = ScrollCause.HANDLE_DRAG;
track = _trackY;
}
const offsetX = event.clientX - _handleX.offsetLeft,
offsetY = event.clientY - _handleY.offsetTop;
const handleDrag = event => {
if (!_root | event.button | permitHandleDragInterruption && (isHorizontal ? _causeX : _causeY) != ScrollCause.HANDLE_DRAG) {
stopDrag();
return;
}
if (isHorizontal) {
_causeX = ScrollCause.HANDLE_DRAG;
this.scrollToX(_scrollMaxX * (event.clientX - offsetX) / _trackMaxX);
} else {
_causeY = ScrollCause.HANDLE_DRAG;
this.scrollToY(_scrollMaxY * (event.clientY - offsetY) / _trackMaxY);
}
};
const handleDragEnd = () => {
if (isHorizontal) {
_causeX = null;
} else {
_causeY = null;
}
stopDrag();
};
const stopDrag = () => {
removeEventListener('mousemove', handleDrag);
removeEventListener('mouseup', handleDragEnd);
track.classList.remove('scroll-box__track--dragged');
};
addEventListener('mousemove', handleDrag);
addEventListener('mouseup', handleDragEnd);
track.classList.add('scroll-box__track--dragged');
};
const handleDragStartX = event => handleDragStart(event, true);
const handleDragStartY = event => handleDragStart(event, false);
const handleWheel = event => {
let {target, deltaMode, deltaX, deltaY, shiftKey} = event;
const {
wheelStepX,
wheelStepY,
disabled,
clientScrollBars,
captureWheel,
lineHeight,
propagateWheelScrollX,
propagateWheelScrollY,
swapWheelAxes,
wheelScrollDurationX,
wheelScrollDurationY
} = this.props;
if (clientScrollBars && !captureWheel) {
event.preventDefault();
}
if (disabled | event.isDefaultPrevented()) {
return;
}
if (target != _viewport && target.tagName == 'TEXTAREA') {
// Nested textarea is focused and its is not a viewport.
return;
}
// By default, Google Chrome changes scrolling orientation if shift key is pressed,
// so propagate this behavior to other browsers as well.
if (shiftKey && deltaX == 0) {
deltaX = deltaY;
deltaY = 0;
}
if (swapWheelAxes) {
const buffer = deltaX;
deltaX = deltaY;
deltaY = buffer;
}
let dx = deltaX * _requiresScrollBarX,
dy = deltaY * _requiresScrollBarY;
if (deltaX && !_requiresScrollBarX | dx < 0 && !_targetX | dx > 0 && _targetX == _scrollMaxX) {
// Content is scrolled to its possible limit.
if (!propagateWheelScrollX) {
event.preventDefault();
}
return;
}
if (deltaY && !_requiresScrollBarY | dy < 0 && !_targetY | dy > 0 && _targetY == _scrollMaxY) {
if (!propagateWheelScrollY) {
event.preventDefault();
}
return;
}
event.preventDefault();
// Converts received delta values into pixels.
switch (deltaMode) {
case 0x01: // Delta values are specified in lines.
dx *= lineHeight;
dy *= lineHeight;
break;
case 0x02:
dx *= this.getPageWidth();
dy *= this.getPageHeight();
break;
default:
// Delta values are specified in pixels.
break;
}
dx *= wheelStepX / 100;
dy *= wheelStepY / 100;
let nextTargetX = _targetX + dx,
nextTargetY = _targetY + dy;
// Prevent jumping to target position when animated scrolling is in progress,
// but preserve scroll speed when mouse wheel events arrive frequently.
if (Date.now() - _timestampX > wheelScrollDurationX) {
nextTargetX = _scrollX + dx;
}
if (Date.now() - _timestampX > wheelScrollDurationY) {
nextTargetY = _scrollY + dy;
}
if (dx) {
_causeX = ScrollCause.MOUSE_WHEEL;
this.scrollToX(nextTargetX, {duration: wheelScrollDurationX});
}
if (dy) {
_causeY = ScrollCause.MOUSE_WHEEL;
this.scrollToY(nextTargetY, {duration: wheelScrollDurationY});
}
};
const updateTrackHoverStatus = (event, track, proximity, status) => {
if (status == null) {
const {clientX, clientY} = event,
{width, left, top, height} = track.getBoundingClientRect();
status =
proximity > clientY - height - top &&
proximity > clientX - width - left &&
proximity > left - clientX &&
proximity > top - clientY;
}
track.classList.toggle('scroll-box__track--hover', status);
};
const handleBarHover = event => {
const {
disabled,
clientScrollBars,
captureHandleDragX,
captureHandleDragY,
captureFastTrackX,
captureFastTrackY,
trackHoverProximityX,
trackHoverProximityY
} = this.props;
if ('orientation' in window | clientScrollBars | disabled) {
return;
}
if (event.buttons) {
if (_causeX != ScrollCause.HANDLE_DRAG) {
var statusX = false;
}
if (_causeY != ScrollCause.HANDLE_DRAG) {
var statusY = false;
}
}
if (_requiresScrollBarX && (captureHandleDragX | captureFastTrackX)) {
updateTrackHoverStatus(event, _trackX, trackHoverProximityX, statusX);
}
if (_requiresScrollBarY && (captureHandleDragY | captureFastTrackY)) {
updateTrackHoverStatus(event, _trackY, trackHoverProximityY, statusY);
}
};
const handleTouchStart = event => {
const {target, touches} = event;
const {
disabled,
clientScrollBars,
captureTouch,
propagateTouchScrollX,
propagateTouchScrollY,
} = this.props;
if (clientScrollBars && !captureTouch) {
event.preventDefault();
}
if (clientScrollBars | disabled | touches.length > 1 | event.isDefaultPrevented()) {
return;
}
if (target != _viewport && target.tagName == 'TEXTAREA') {
// Nested textarea is focused and its is not a viewport.
return;
}
const {
clientX: initialClientX,
clientY: initialClientY
} = touches[0];
let scrolled = false;
const handleTouchMove = event => {
const {targetX, targetY, scrollMaxX, scrollMaxY} = this;
const {clientX, clientY} = event.touches[0];
const dx = initialClientX - clientX,
dy = initialClientY - clientY;
if (
(dx < 0 && !targetX) || (dx > 0 && targetX == scrollMaxX) ||
(dy < 0 && !targetY) || (dy > 0 && targetY == scrollMaxY)
) {
if (!scrolled) {
disposeTouch();
}
return;
}
scrolled = true;
event.preventDefault();
this.scrollTo({x: _scrollX + dx, y: _scrollY + dy});
};
const handleTouchEnd = event => {
disposeTouch();
};
const disposeTouch = () => {
removeEventListener('touchmove', handleTouchMove);
removeEventListener('touchend', handleTouchEnd);
removeEventListener('touchcancel', handleTouchEnd);
};
addEventListener('touchmove', handleTouchMove);
addEventListener('touchend', handleTouchEnd);
addEventListener('touchcancel', handleTouchEnd);
};
}
}