Skip to content

Instantly share code, notes, and snippets.

@breakerh
Last active November 12, 2022 23:29
Show Gist options
  • Save breakerh/e4abf883d67a8a2d7de027adb9bb6919 to your computer and use it in GitHub Desktop.
Save breakerh/e4abf883d67a8a2d7de027adb9bb6919 to your computer and use it in GitHub Desktop.
Horizontal scroll in react
import React, {FC, MouseEventHandler, useState} from 'react'
import clsx from 'clsx'
type ScrollProps = {
className?: string,
children: React.ReactNode
}
const HorizontalScroll: FC<ScrollProps> = ({ className, children }) => {
const scrollRef = React.useRef<HTMLDivElement>(null)
let initialPosition = {scrollLeft: 0, x: 0}
const [grab, setGrab] = useState<boolean>(false)
const mouseDown = (event: { clientX: number; clientY: number }) => {
if(!grab && scrollRef.current!==null) {
console.log('drag',scrollRef.current?.scrollLeft, event.clientX)
setGrab(true)
initialPosition.scrollLeft = scrollRef.current?.scrollLeft??0
initialPosition.x = event.clientX
window.addEventListener('mousemove', mouseMove)
window.addEventListener('mouseup', mouseUp)
window.addEventListener('touchmove', touchMove)
window.addEventListener('touchend', mouseUp)
}
}
const touchMove = (event:any) => mouseMove(event.touches[0])
const mouseMove = (event: { clientX: number; clientY: number }) => {
if (scrollRef.current !== null && scrollRef.current.classList.contains('horizontal-scroll--dragging')) {
console.log('dragmove',initialPosition.scrollLeft)
const isStart = scrollRef.current.scrollLeft === 0
const isEnd = scrollRef.current.scrollLeft + scrollRef.current.clientWidth === scrollRef.current.scrollWidth
const direction = (event.clientX - initialPosition.x) < 0 ? 'right' : 'left'
if(isStart && direction === 'left') {
scrollRef.current.style.setProperty('--scroll-band', 10+Math.round((event.clientX - initialPosition.x)/50) + 'px')
}else if(isEnd && direction === 'right') {
scrollRef.current.style.setProperty('--scroll-band', 10-Math.round((event.clientX - initialPosition.x)/50) + 'px')
}else{
scrollRef.current.style.setProperty('--scroll-band', '10px')
}
scrollRef.current.scrollLeft = initialPosition.scrollLeft - ((event.clientX - initialPosition.x))
}
}
const mouseUp = () => {
if(scrollRef.current==null)
return
console.log('dragrelease')
setGrab(false)
window.removeEventListener('mousemove', mouseMove)
window.removeEventListener('mouseup', mouseUp)
window.removeEventListener('touchmove', touchMove)
window.removeEventListener('touchend', mouseUp)
scrollRef.current.style.setProperty('--scroll-band', '10px')
}
const touchStart = (event:any) => mouseDown(event.touches[0])
return (
<div ref={scrollRef} onMouseDown={mouseDown} onTouchStart={touchStart} className={clsx(className,'horizontal-scroll--hide-scrollbars',grab?'horizontal-scroll--dragging':'horizontal-scroll--release')}>
{children}
</div>
)
}
export default HorizontalScroll
.horizontal-scroll {
overflow: auto;
cursor: grab;
--scroll-band: 10px;
gap: var(--scroll-band);
display: flex;
flex-direction: row;
flex-wrap: nowrap;
&--dragging {
scroll-behavior: auto !important;
user-select: none !important;
cursor: grabbing;
> * {
cursor: grabbing;
}
}
&--release {
transition: gap .3s ease-in-out;
}
&--hide-scrollbars {
overflow: hidden;
overflow: -moz-scrollbars-none;
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none !important;
height: 0 !important;
width: 0 !important;
background: transparent !important;
-webkit-appearance: none !important;
}
}
&--native-scroll {
overflow: auto;
}
> * {
display: inline-block;
white-space: nowrap;
background: white;
border-radius: 50px;
padding: 5px 20px;
}
}
@breakerh
Copy link
Author

Will expand with a few options and momentum effect on scroll.

@breakerh
Copy link
Author

Forgot to remove the touch listeners

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment