Преглед на файлове

stop autoscroll on touch start/on scroll

Ivan Asmer преди 3 години
родител
ревизия
6c181e5e9e
променени са 2 файла, в които са добавени 320 реда и са изтрити 0 реда
  1. 159 0
      src/SliderBackground.js
  2. 161 0
      src/SliderImage.js

+ 159 - 0
src/SliderBackground.js

@@ -0,0 +1,159 @@
+import React, {useRef, useEffect, useState} from 'react'
+
+export default ({images = [], className, onChange, onClick, ...props}) => {
+    const divRef                = useRef()
+    const containerRef          = useRef()
+    const autoscroll            = useRef(false)
+    const inTouch               = useRef(false)
+    const timeout               = useRef(false)
+
+    const [imgs, setImgs]       = useState([])
+    const [current, setCurrent] = useState(0)
+    const currentScrollPosition = useRef(0)
+
+
+    useEffect(() => {
+        if (divRef.current){
+            const div  = divRef.current
+
+            const totalWidth = imgs.reduce((total, img) => total + (img?.getBoundingClientRect().width || 0),0)
+            div.style.minWidth = totalWidth + 'px'
+        }
+    }, [divRef, imgs])
+
+    const scrollToCurrent = () => {
+        if (autoscroll.current || !containerRef.current) return;
+
+        const el = containerRef.current
+
+        const step = () => {
+            if (!el) return;
+
+            autoscroll.current = true
+            const {scrollLeft} =  el
+            const diff = (currentScrollPosition.current - scrollLeft) /5
+            if (Math.abs(diff) > 1){
+                el.scrollTo(scrollLeft + diff, 0)
+                setTimeout(step, 20)
+            }
+            else {
+                el.scrollTo(currentScrollPosition.current, 0)
+                autoscroll.current = false
+            }
+        }
+        step()
+        //if (milaCount !== current +1) set(current +1)
+    }
+
+    const onTouchEnd = () => {
+        if (inTouch.current){
+            if (timeout.current) clearInterval(timeout.current)
+
+            timeout.current = setTimeout(onTouchEnd, 200)
+            return;
+        }
+
+        const {scrollLeft, scrollWidth} = containerRef.current;
+        const viewPortWidth             = containerRef.current.getBoundingClientRect().width
+
+        let newCurrent;
+        if (scrollLeft === 0) {
+            newCurrent = 0
+            currentScrollPosition.current = 0
+        }
+        else if (scrollLeft >= scrollWidth - viewPortWidth) {
+            newCurrent = (images.length -1)
+            currentScrollPosition.current = scrollWidth - viewPortWidth
+        }
+        else {  
+            let imgsWidth = 0
+            let i         = 0
+            for (const img of imgs){
+                const imgWidth = img?.getBoundingClientRect().width || 0
+                imgsWidth += imgWidth
+                if (imgsWidth > scrollLeft){
+                    break;
+                }
+                i++
+            }
+            const offset = imgsWidth  - scrollLeft
+            newCurrent   = i
+            currentScrollPosition.current = imgsWidth - (imgs?.[i].getBoundingClientRect().width || 0)
+            if (offset < viewPortWidth/2){
+                currentScrollPosition.current = imgsWidth 
+                newCurrent++
+            }
+        }
+        setCurrent(newCurrent)
+        if (newCurrent === current) scrollToCurrent()
+        else if (typeof onChange === 'function') onChange(newCurrent)
+    }
+
+    const onScroll = e => {
+        if (autoscroll.current) return;
+
+        if (timeout.current) clearInterval(timeout.current)
+
+        timeout.current = setTimeout(onTouchEnd, 200)
+    }
+
+    useEffect(scrollToCurrent, [current])
+
+    const isTouchDevice = () => (navigator.maxTouchPoints || 'ontouchstart' in document.documentElement);
+
+    const onImgClick = (e, i) => {
+        const touchDevice = isTouchDevice()
+        if (touchDevice && typeof onClick === 'function'){
+            onClick(i)
+        }
+        else {
+            const viewPortWidth             = containerRef.current.getBoundingClientRect().width
+            const clickX                    = e.clientX -e.target.getBoundingClientRect().x 
+            const {scrollLeft}              = containerRef.current;
+            if (clickX > viewPortWidth * 0.75) {
+                if (i < images.length -1){
+                    currentScrollPosition.current = scrollLeft + (imgs?.[i].getBoundingClientRect().width || 0)
+                    setCurrent(i +1)
+                    scrollToCurrent()
+                    if (typeof onChange === 'function') onChange(i +1)
+                }
+            }
+            else if (clickX < viewPortWidth * 0.25) {
+                if (i > 0){
+                    currentScrollPosition.current = scrollLeft - (imgs?.[i-1].getBoundingClientRect().width || 0)
+                    setCurrent(i -1)
+                    scrollToCurrent()
+                    if (typeof onChange === 'function') onChange(i -1)
+                }
+            }
+            else if (typeof onClick === 'function'){
+                onClick(i)
+            }
+        }
+    }
+
+    return (
+        <div className={className} 
+             style={{cssText: `overflow: auto; -ms-overflow-style: none; scrollbar-width: none`}}
+             onScroll={onScroll} //TODO: do it only on desktop, on touch devices use onTouchEnd
+             ref={containerRef} 
+            {...isTouchDevice() ? {
+                onTouchStart(){
+                    inTouch.current = true
+                },
+                onTouchEnd(){
+                    inTouch.current = false
+                }
+            }: {}}
+            >
+            <div ref={divRef}>
+                {images.map((image, i) => <img  src={image} 
+                                                key={`image_${i}`} 
+                                                alt={`image ${i}`} 
+                                                onLoad={e => {imgs[i] = e.target; setImgs([...imgs]) }}
+                                                onClick={e => onImgClick(e, i)}
+                                                />)}
+            </div>
+        </div>
+    )
+}

+ 161 - 0
src/SliderImage.js

@@ -0,0 +1,161 @@
+import React, {useRef, useEffect, useState} from 'react'
+
+export default ({images = [], className, onChange, onClick, ...props}) => {
+    const divRef                = useRef()
+    const containerRef          = useRef()
+    const autoscroll            = useRef(false)
+    const inTouch               = useRef(false)
+    const timeout               = useRef(false)
+
+    const [imgs, setImgs]       = useState([])
+    const [current, setCurrent] = useState(0)
+    const currentScrollPosition = useRef(0)
+
+
+    useEffect(() => {
+        if (divRef.current){
+            const div  = divRef.current
+
+            const totalWidth = imgs.reduce((total, img) => total + (img?.getBoundingClientRect().width || 0),0)
+            div.style.minWidth = totalWidth + 'px'
+        }
+    }, [divRef, imgs])
+
+    const scrollToCurrent = () => {
+        if (autoscroll.current || !containerRef.current) return;
+
+        const el = containerRef.current
+
+        const step = () => {
+            if (!el) return;
+
+            autoscroll.current = true
+            const {scrollLeft} =  el
+            const diff = (currentScrollPosition.current - scrollLeft) /5
+            if (Math.abs(diff) > 1){
+                el.scrollTo(scrollLeft + diff, 0)
+                setTimeout(step, 20)
+            }
+            else {
+                el.scrollTo(currentScrollPosition.current, 0)
+                autoscroll.current = false
+            }
+        }
+        step()
+        //if (milaCount !== current +1) set(current +1)
+    }
+
+    const onTouchEnd = () => {
+        if (inTouch.current){
+            if (timeout.current) clearInterval(timeout.current)
+
+            timeout.current = setTimeout(onTouchEnd, 200)
+            return;
+        }
+
+        const {scrollLeft, scrollWidth} = containerRef.current;
+        const viewPortWidth             = containerRef.current.getBoundingClientRect().width
+
+        let newCurrent;
+        if (scrollLeft === 0) {
+            newCurrent = 0
+            currentScrollPosition.current = 0
+        }
+        else if (scrollLeft >= scrollWidth - viewPortWidth) {
+            newCurrent = (images.length -1)
+            currentScrollPosition.current = scrollWidth - viewPortWidth
+        }
+        else {  
+            let imgsWidth = 0
+            let i         = 0
+            for (const img of imgs){
+                const imgWidth = img?.getBoundingClientRect().width || 0
+                imgsWidth += imgWidth
+                if (imgsWidth > scrollLeft){
+                    break;
+                }
+                i++
+            }
+            const offset = imgsWidth  - scrollLeft
+            newCurrent   = i
+            currentScrollPosition.current = imgsWidth - (imgs?.[i].getBoundingClientRect().width || 0)
+            if (offset < viewPortWidth/2){
+                currentScrollPosition.current = imgsWidth 
+                newCurrent++
+            }
+        }
+        setCurrent(newCurrent)
+        if (newCurrent === current) scrollToCurrent()
+        else if (typeof onChange === 'function') onChange(newCurrent)
+    }
+
+    const onScroll = e => {
+        autoscroll.current = false
+        //if (autoscroll.current) return;
+
+        if (timeout.current) clearInterval(timeout.current)
+
+        timeout.current = setTimeout(onTouchEnd, 200)
+    }
+
+    useEffect(scrollToCurrent, [current])
+
+    const isTouchDevice = () => (navigator.maxTouchPoints || 'ontouchstart' in document.documentElement);
+
+    const onImgClick = (e, i) => {
+        const touchDevice = isTouchDevice()
+        if (touchDevice && typeof onClick === 'function'){
+            onClick(i)
+        }
+        else {
+            const viewPortWidth             = containerRef.current.getBoundingClientRect().width
+            const clickX                    = e.clientX -e.target.getBoundingClientRect().x 
+            const {scrollLeft}              = containerRef.current;
+            if (clickX > viewPortWidth * 0.75) {
+                if (i < images.length -1){
+                    currentScrollPosition.current = scrollLeft + (imgs?.[i].getBoundingClientRect().width || 0)
+                    setCurrent(i +1)
+                    scrollToCurrent()
+                    if (typeof onChange === 'function') onChange(i +1)
+                }
+            }
+            else if (clickX < viewPortWidth * 0.25) {
+                if (i > 0){
+                    currentScrollPosition.current = scrollLeft - (imgs?.[i-1].getBoundingClientRect().width || 0)
+                    setCurrent(i -1)
+                    scrollToCurrent()
+                    if (typeof onChange === 'function') onChange(i -1)
+                }
+            }
+            else if (typeof onClick === 'function'){
+                onClick(i)
+            }
+        }
+    }
+
+    return (
+        <div className={className} 
+             style={{cssText: `overflow: auto; -ms-overflow-style: none; scrollbar-width: none; overscroll-behavior-x: none;`}}
+             onScroll={onScroll} //TODO: do it only on desktop, on touch devices use onTouchEnd
+             ref={containerRef} 
+            {...isTouchDevice() ? {
+                onTouchStart(){
+                    inTouch.current = true
+                    autoscroll.current = false
+                },
+                onTouchEnd(){
+                    inTouch.current = false
+                }
+            }: {}}
+            >
+            <div ref={divRef}>
+                {images.map((image, i) => <img  src={image} 
+                                                key={`image_${i}`} 
+                                                alt={`image ${i}`} 
+                                                onLoad={e => {imgs[i] = e.target; setImgs([...imgs]) }}
+                                                onClick={e => onImgClick(e, i)}
+                                                />)}
+            </div>
+        </div>
+    )
+}