Last active
August 10, 2018 19:57
-
-
Save mreishus/9c368cb01d7dbb367202425384e19891 to your computer and use it in GitHub Desktop.
React component to provide click-and-drag scrolling
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
// @flow | |
import * as React from "react"; | |
// I got this from https://gist.github.com/flushentitypacket/7717cb30d1b172e633cea864eeb4d2e7 | |
// Changes made | |
// 1. Added Y position | |
// 2. Converted from TypeScript to FlowType | |
// 3. Changed removeEventListeners to run off document.documentElement - they weren't working for me | |
// 4. Added event.preventDefault to mouse down handler - might prevent getting stuck in dragging state | |
// 5. Changed privateState updates to use a mutational style. | |
type DragScrollProvisions = { | |
onMouseDown: (event: SyntheticMouseEvent<*>) => void, | |
ref: (element: ?HTMLElement) => ?HTMLElement, | |
}; | |
type Props = { | |
children: (provisions: DragScrollProvisions) => React.Node, | |
}; | |
type PrivateState = { | |
isMouseDown: boolean, | |
lastMousePositionX: number | null, | |
lastMousePositionY: number | null, | |
}; | |
export class DragScrollProvider extends React.Component<Props> { | |
// Not using React state since we don't want to rerender on these state changes | |
privateState: PrivateState = { | |
isMouseDown: false, | |
lastMousePositionX: null, | |
lastMousePositionY: null, | |
}; | |
clearListeners: Array<() => any> = []; | |
refElement: ?HTMLElement = null; | |
render() { | |
return this.props.children({ | |
onMouseDown: this.provisionOnMouseDown, | |
ref: this.provisionRef, | |
}); | |
} | |
componentDidMount() { | |
const onMouseUp = () => { | |
this.privateState = { | |
isMouseDown: false, | |
lastMousePositionX: null, | |
lastMousePositionY: null, | |
}; | |
}; | |
if (document.documentElement == null) { | |
return; | |
} | |
document.documentElement.addEventListener("mouseup", onMouseUp); | |
const clearMouseUpListener = () => | |
document.documentElement != null && document.documentElement.removeEventListener("mouseup", onMouseUp); | |
this.clearListeners.push(clearMouseUpListener); | |
const onMouseMove = (event: MouseEvent) => { | |
if (!this.privateState.isMouseDown || this.refElement == null) { | |
return; | |
} | |
const { lastMousePositionX, lastMousePositionY } = this.privateState; | |
// The mousedown handler should have set the lastMousePositionX, so this case should only happen if setPrivateState | |
// hasn't finished yet. In that case, let's just ignore this first movement and wait for that initial | |
// setPrivateState to complete. | |
if (lastMousePositionX === null || lastMousePositionY === null) return; | |
this.refElement.scrollLeft += lastMousePositionX - event.clientX; | |
this.refElement.scrollTop += lastMousePositionY - event.clientY; | |
this.privateState.lastMousePositionX = event.clientX; | |
this.privateState.lastMousePositionY = event.clientY; | |
}; | |
if (document.documentElement == null) { | |
return; | |
} | |
document.documentElement.addEventListener("mousemove", onMouseMove); | |
const clearMouseMoveListener = () => | |
document.documentElement != null && document.documentElement.removeEventListener("mousemove", onMouseMove); | |
this.clearListeners.push(clearMouseMoveListener); | |
} | |
componentWillUnmount() { | |
this.clearListeners.forEach(clear => clear()); | |
} | |
provisionOnMouseDown = (event: SyntheticMouseEvent<*>) => { | |
event.preventDefault(); // This may prevent it from getting stuck in the "down" position | |
this.privateState = { | |
isMouseDown: true, | |
lastMousePositionX: event.clientX, | |
lastMousePositionY: event.clientY, | |
}; | |
}; | |
provisionRef = (element: ?HTMLElement) => (this.refElement = element); | |
} |
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
import * as React from 'react' | |
import {DragScrollProvider} from './DragScrollProvider' | |
const MyComponent = () => ( | |
<DragScrollProvider> | |
{({onMouseDown, ref}) => ( | |
<div className='scrollableDiv' onMouseDown={onMouseDown} ref={ref}> | |
<div className='overflowsTheParent' /> | |
</div> | |
)} | |
</DragScrollProvider> | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment