SliderImage.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import React, {useRef, useEffect, useState} from 'react'
  2. export default ({images = [], className, onChange, onClick, ...props}) => {
  3. const divRef = useRef()
  4. const containerRef = useRef()
  5. const autoscroll = useRef(false)
  6. const inTouch = useRef(false)
  7. const timeout = useRef(false)
  8. const [imgs, setImgs] = useState([])
  9. const [current, setCurrent] = useState(0)
  10. const currentScrollPosition = useRef(0)
  11. useEffect(() => {
  12. if (divRef.current){
  13. const div = divRef.current
  14. const totalWidth = imgs.reduce((total, img) => total + (img?.getBoundingClientRect().width || 0),0)
  15. div.style.minWidth = totalWidth + 'px'
  16. }
  17. }, [divRef, imgs])
  18. const scrollToCurrent = () => {
  19. const el = containerRef.current
  20. autoscroll.current = true
  21. const step = () => {
  22. if (!autoscroll.current || !el) return;
  23. const {scrollLeft} = el
  24. const diff = (currentScrollPosition.current - scrollLeft) /5
  25. if (Math.abs(diff) > 1){
  26. el.scrollTo(scrollLeft + diff, 0)
  27. setTimeout(step, 20)
  28. }
  29. else {
  30. el.scrollTo(currentScrollPosition.current, 0)
  31. autoscroll.current = false
  32. }
  33. }
  34. step()
  35. //if (milaCount !== current +1) set(current +1)
  36. }
  37. const onTouchEnd = () => {
  38. if (inTouch.current){
  39. if (timeout.current) clearInterval(timeout.current)
  40. timeout.current = setTimeout(onTouchEnd, 200)
  41. return;
  42. }
  43. const {scrollLeft, scrollWidth} = containerRef.current;
  44. const viewPortWidth = containerRef.current.getBoundingClientRect().width
  45. let newCurrent;
  46. if (scrollLeft === 0) {
  47. newCurrent = 0
  48. currentScrollPosition.current = 0
  49. }
  50. else if (scrollLeft >= scrollWidth - viewPortWidth) {
  51. newCurrent = (images.length -1)
  52. currentScrollPosition.current = scrollWidth - viewPortWidth
  53. }
  54. else {
  55. let imgsWidth = 0
  56. let i = 0
  57. for (const img of imgs){
  58. const imgWidth = img?.getBoundingClientRect().width || 0
  59. imgsWidth += imgWidth
  60. if (imgsWidth > scrollLeft){
  61. break;
  62. }
  63. i++
  64. }
  65. const offset = imgsWidth - scrollLeft
  66. newCurrent = i
  67. currentScrollPosition.current = imgsWidth - (imgs?.[i].getBoundingClientRect().width || 0)
  68. if (offset < viewPortWidth/2){
  69. currentScrollPosition.current = imgsWidth
  70. newCurrent++
  71. }
  72. }
  73. setCurrent(newCurrent)
  74. if (newCurrent === current) scrollToCurrent()
  75. else if (typeof onChange === 'function') onChange(newCurrent)
  76. }
  77. const onScroll = e => {
  78. //autoscroll.current = false
  79. if (autoscroll.current) return;
  80. if (timeout.current) clearInterval(timeout.current)
  81. timeout.current = setTimeout(onTouchEnd, 200)
  82. }
  83. useEffect(scrollToCurrent, [current])
  84. const isTouchDevice = () => (navigator.maxTouchPoints || 'ontouchstart' in document.documentElement);
  85. const onImgClick = (e, i) => {
  86. const touchDevice = isTouchDevice()
  87. if (touchDevice && typeof onClick === 'function'){
  88. onClick(i)
  89. }
  90. else {
  91. const viewPortWidth = containerRef.current.getBoundingClientRect().width
  92. const clickX = e.clientX -e.target.getBoundingClientRect().x
  93. const {scrollLeft} = containerRef.current;
  94. if (clickX > viewPortWidth * 0.75) {
  95. if (i < images.length -1){
  96. currentScrollPosition.current = scrollLeft + (imgs?.[i].getBoundingClientRect().width || 0)
  97. setCurrent(i +1)
  98. scrollToCurrent()
  99. if (typeof onChange === 'function') onChange(i +1)
  100. }
  101. }
  102. else if (clickX < viewPortWidth * 0.25) {
  103. if (i > 0){
  104. currentScrollPosition.current = scrollLeft - (imgs?.[i-1].getBoundingClientRect().width || 0)
  105. setCurrent(i -1)
  106. scrollToCurrent()
  107. if (typeof onChange === 'function') onChange(i -1)
  108. }
  109. }
  110. else if (typeof onClick === 'function'){
  111. onClick(i)
  112. }
  113. }
  114. }
  115. return (
  116. <div className={className}
  117. style={{cssText: `overflow: auto; -ms-overflow-style: none; scrollbar-width: none; overscroll-behavior-x: none;`}}
  118. onScroll={onScroll} //TODO: do it only on desktop, on touch devices use onTouchEnd
  119. ref={containerRef}
  120. {...isTouchDevice() ? {
  121. onTouchStart(){
  122. inTouch.current = true
  123. autoscroll.current = false
  124. },
  125. onTouchEnd(){
  126. inTouch.current = false
  127. }
  128. }: {}}
  129. >
  130. <div ref={divRef}>
  131. {images.map((image, i) => <img src={image}
  132. key={`image_${i}`}
  133. alt={`image ${i}`}
  134. onLoad={e => {imgs[i] = e.target; setImgs([...imgs]) }}
  135. onClick={e => onImgClick(e, i)}
  136. />)}
  137. </div>
  138. </div>
  139. )
  140. }