Skip to content

Instantly share code, notes, and snippets.

@prevostc
Created August 22, 2018 15:49
Show Gist options
  • Save prevostc/585214e59da287b3ce8e7063ab2d6038 to your computer and use it in GitHub Desktop.
Save prevostc/585214e59da287b3ce8e7063ab2d6038 to your computer and use it in GitHub Desktop.
Component with event listeners on window
import React, { Component, MouseEvent } from "react"
import { AnyChildren } from "react"
import { isServer } from "../../platform"
import { SliderArrows } from "./SliderArrows"
interface IProps<ItemType> {
children: (props: { item: ItemType }) => AnyChildren
items: ItemType[]
}
interface IState {
dragging: boolean
lastClientX: number
}
export function createSliderComponentClass<ItemType>() {
return class Slider extends Component<IProps<ItemType>, IState> {
protected slidable: React.RefObject<HTMLDivElement>
constructor(props: IProps<ItemType>) {
super(props)
this.slidable = React.createRef()
this.state = {
dragging: false,
lastClientX: 0,
}
}
public componentDidMount() {
if (isServer()) {
return
}
// @ts-ignore
window.addEventListener("mousemove", this.handleMouseMove, false)
// @ts-ignore
window.addEventListener("mouseup", this.handleMouseUp, false)
}
public componentWillMount() {
if (isServer()) {
return
}
// @ts-ignore
window.removeEventListener("mousemove", this.handleMouseMove, false)
// @ts-ignore
window.addEventListener("mouseup", this.handleMouseUp, false)
}
public render() {
const { items, children } = this.props
return (
<SliderArrows
onLeft={() => this.animateScroll(150, true)}
onRight={() => this.animateScroll(150, false)}
scrollLeft={
this.slidable.current ? this.slidable.current.scrollLeft : 0
}
contentWidth={
this.slidable.current ? this.slidable.current.scrollWidth : Infinity
}
containerWidth={
this.slidable.current ? this.slidable.current.clientWidth : 0
}
>
<div
className="slider"
onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp}
onMouseMove={this.handleMouseMove}
ref={this.slidable}
>
{items.map((item, key) => (
<div className="slider-item" key={key}>
{children({ item })}
</div>
))}
<style jsx>{`
.slider {
display: flex;
flex-direction: row;
overflow-x: scroll;
padding: 20px;
/* https://www.denisbouquet.com/css-forbid-selection-user-select-dragging-ghost-image/ */
/* prevent ghost drag */
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-o-user-select: none;
user-select: none;
}
.slider-item {
margin-right: 10px;
}
/* prevent item selection */
.slider-item :global(::selection) {
background: rgba(0, 0, 0, 0);
}
.slider-item :global(::-moz-selection) {
background: rgba(0, 0, 0, 0);
}
.slider-item :global(img) {
pointer-event: none;
}
`}</style>
</div>
</SliderArrows>
)
}
protected handleMouseDown = (e: MouseEvent<HTMLDivElement>) => {
const ClientX = e.clientX
this.setState({
dragging: true,
lastClientX: ClientX,
})
}
protected handleMouseUp = (_: MouseEvent<HTMLDivElement>) => {
this.setState({ dragging: false })
}
protected handleMouseMove = (e: MouseEvent<HTMLDivElement>) => {
if (this.state.dragging) {
const distance = -this.state.lastClientX + e.clientX
this.setState({
dragging: true,
lastClientX: e.clientX,
})
this.updateScrollPosition(distance)
}
return false
}
protected updateScrollPosition = (distance: number) => {
if (this.slidable.current) {
this.slidable.current.scrollLeft -= distance
}
}
protected animateScroll = (distance: number, towardLeft: boolean) => {
if (distance <= 0) {
return
} else {
const frameDistance = distance / 5
const remainingDistance = distance - frameDistance
this.updateScrollPosition(distance * (towardLeft ? 1 : -1))
setTimeout(() => this.animateScroll(remainingDistance, towardLeft), 8)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment