From 8bfc3076b2a67966cda9421d97296fe8cbf76ebf Mon Sep 17 00:00:00 2001 From: kimcoder Date: Sun, 24 Oct 2021 18:38:07 +0900 Subject: [PATCH 1/4] feat: add loop, autoPlay --- example/App.tsx | 40 ++++++++------ src/ImageSlider.tsx | 100 +++++++++++++++++----------------- src/ImageSliderBullets.tsx | 29 ++++++---- src/ImageSliderNavigation.tsx | 25 +++++---- src/hooks/useAutoPlay.ts | 0 src/hooks/useSlideIndex.ts | 71 ++++++++++++++++++++++++ 6 files changed, 177 insertions(+), 88 deletions(-) create mode 100644 src/hooks/useAutoPlay.ts create mode 100644 src/hooks/useSlideIndex.ts diff --git a/example/App.tsx b/example/App.tsx index 937fd3f..1173b95 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -15,20 +15,15 @@ import SimpleImageSlider from '../dist'; import Input from '@material-ui/core/Input'; import './app.scss'; -const IMAGES = [ - { url: 'images/1.jpg' }, - { url: 'images/2.jpg' }, - { url: 'images/3.jpg' }, - { url: 'images/4.jpg' }, - { url: 'images/5.jpg' }, - { url: 'images/6.jpg' }, - { url: 'images/7.jpg' } -]; +const IMAGES = [{ url: 'images/1.jpg' }, { url: 'images/2.jpg' }, { url: 'images/3.jpg' }, { url: 'images/7.jpg' }]; type SliderOptions = { useGPURender: boolean; showNavs: boolean; showBullets: boolean; + loop: boolean; + autoPlay: boolean; + autoPlayDelay: number; navStyle: 1 | 2; navSize: number; navMargin: number; @@ -41,6 +36,9 @@ const App: React.FC = () => { useGPURender: true, showNavs: true, showBullets: true, + loop: true, + autoPlay: true, + autoPlayDelay: 2, navStyle: 1, navSize: 50, navMargin: 30, @@ -79,13 +77,7 @@ const App: React.FC = () => { case 'navStyle': setSliderOptions({ ...sliderOptions, navStyle: value as 1 | 2 }); break; - case 'useGPURender': - case 'showNavs': - case 'showBullets': - case 'duration': - case 'bgColor': - case 'navSize': - case 'navMargin': + default: setSliderOptions({ ...sliderOptions, [key]: value }); break; } @@ -123,6 +115,9 @@ const App: React.FC = () => { images={IMAGES} showBullets={sliderOptions.showBullets} showNavs={sliderOptions.showNavs} + loop={sliderOptions.loop} + autoPlay={sliderOptions.autoPlay} + autoPlayDelay={sliderOptions.autoPlayDelay} startIndex={0} useGPURender={sliderOptions.useGPURender} navStyle={sliderOptions.navStyle} @@ -168,11 +163,22 @@ const App: React.FC = () => { + + + autoPlayDelay + + + Navigation Size diff --git a/src/ImageSlider.tsx b/src/ImageSlider.tsx index 87efc5a..3e4f546 100644 --- a/src/ImageSlider.tsx +++ b/src/ImageSlider.tsx @@ -1,8 +1,9 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import ImagePreLoader from './ImageSliderPreLoader'; import styles from './ImageSliderStyle'; import ImageSliderNavigation, { ImageSliderNavDirection, ImageSliderNavStyle } from './ImageSliderNavigation'; import ImageSliderBullets from './ImageSliderBullets'; +import useSlideIndex from './hooks/useSlideIndex'; export type SimpleImageSliderProps = { width: number | string; @@ -11,6 +12,9 @@ export type SimpleImageSliderProps = { style?: React.CSSProperties; showNavs: boolean; showBullets: boolean; + loop?: boolean; + autoPlay?: boolean; + autoPlayDelay?: number; startIndex?: number; slideDuration?: number; bgColor?: string; @@ -31,6 +35,9 @@ const SimpleImageSlider: React.FC = ({ images, showNavs, showBullets, + loop = true, + autoPlay = false, + autoPlayDelay = 0.7, startIndex = 0, style = undefined, slideDuration = 0.5, @@ -46,11 +53,15 @@ const SimpleImageSlider: React.FC = ({ onCompleteSlide = undefined }: SimpleImageSliderProps) => { const rootStyle: React.CSSProperties = useMemo(() => styles.getRootContainer(width, height, bgColor), [width, height, bgColor]); - const [slideIdx, setSlideIdx] = useState(startIndex < images.length ? startIndex : 0); - const [slideDirection, setSlideDirection] = useState(ImageSliderNavDirection.RIGHT); - const [isSliding, setIsSliding] = useState(false); + const { slideIdx, updateSlideIdx, isRightDirection, getNextLoopingIdx, previousSlideIdx } = useSlideIndex({ + imageCount: images.length, + startIndex, + autoPlay, + autoPlayDelay: autoPlayDelay + slideDuration + }); const [currentSliderStyle, setCurrentSlideStyle] = useState(styles.getImageSlide(images[0].url, slideDuration, 0, useGPURender)); const [nextSliderStyle, setNextSliderStyle] = useState(styles.getImageSlide(images[1]?.url, slideDuration, 1, useGPURender)); + const isSlidingRef = useRef(false); const handleClick = useCallback( (event: React.SyntheticEvent) => { @@ -61,64 +72,54 @@ const SimpleImageSlider: React.FC = ({ const handleClickNav = useCallback( (direction: ImageSliderNavDirection) => () => { - if (isSliding) { + if (isSlidingRef.current) { return; } const isRight: boolean = direction === ImageSliderNavDirection.RIGHT; onClickNav?.(isRight); - slide(isRight ? slideIdx + 1 : slideIdx - 1); + updateSlideIdx(isRight ? slideIdx + 1 : slideIdx - 1); }, - [slideIdx, isSliding] + [onClickNav, slideIdx, updateSlideIdx] ); const handleClickBullets = useCallback( (idx: number) => { - if (idx === slideIdx || isSliding) { + if (idx === slideIdx || isSlidingRef.current) { return; } onClickBullets?.(idx); - slide(idx); + updateSlideIdx(idx); }, - [slideIdx, isSliding] + [onClickBullets, slideIdx, updateSlideIdx] ); - const slide = (idx: number) => { - const toNext: boolean = idx > slideIdx; - const currentUrl: string = images[slideIdx].url; - const nextUrl: string = images[idx].url; - const nextReadyX: 1 | -1 = toNext ? 1 : -1; - - setSlideIdx(idx); - setSlideDirection(idx > slideIdx ? ImageSliderNavDirection.RIGHT : ImageSliderNavDirection.LEFT); - setCurrentSlideStyle(styles.getImageSlide(currentUrl, 0, 0, useGPURender)); - setNextSliderStyle(styles.getImageSlide(nextUrl, 0, nextReadyX, useGPURender)); - setIsSliding(true); - - onStartSlide?.(idx + 1, images.length); - idx + 2 < images.length && ImagePreLoader.load(images[idx + 2].url); - }; - useEffect(() => { - if (isSliding) { - setTimeout(() => { - const toRight: boolean = slideDirection === ImageSliderNavDirection.RIGHT; - const currentUrl: string = images[toRight ? slideIdx - 1 : slideIdx + 1].url; - const nextUrl: string = images[slideIdx].url; - const currentOffsetX: 1 | -1 = toRight ? -1 : 1; - - setCurrentSlideStyle(styles.getImageSlide(currentUrl, slideDuration, currentOffsetX, useGPURender)); - setNextSliderStyle(styles.getImageSlide(nextUrl, slideDuration, 0, useGPURender)); - }, 50); + if (slideIdx === previousSlideIdx) { + return; } - }, [slideIdx, isSliding]); + + const currentUrl: string = images[getNextLoopingIdx(isRightDirection ? slideIdx - 1 : slideIdx + 1)].url; + const nextUrl: string = images[slideIdx].url; + const currentOffsetX: 1 | -1 = isRightDirection ? -1 : 1; + const nextReadyOffsetX: 1 | -1 = isRightDirection ? 1 : -1; + + onStartSlide?.(slideIdx + 1, images.length); + setNextSliderStyle(styles.getImageSlide(nextUrl, 0, nextReadyOffsetX, useGPURender)); + setTimeout(() => { + isSlidingRef.current = true; + setCurrentSlideStyle(styles.getImageSlide(currentUrl, slideDuration, currentOffsetX, useGPURender)); + setNextSliderStyle(styles.getImageSlide(nextUrl, slideDuration, 0, useGPURender)); + }, 50); + }, [onStartSlide, slideIdx, isRightDirection]); const handleSlideEnd = useCallback(() => { + isSlidingRef.current = false; + ImagePreLoader.load(images[slideIdx + 2]?.url); setCurrentSlideStyle(styles.getImageSlide(images[slideIdx].url, 0, 0, useGPURender)); - setIsSliding(false); onCompleteSlide?.(slideIdx + 1, images.length); - }, [slideIdx]); + }, [onCompleteSlide, slideIdx]); return (
@@ -130,27 +131,28 @@ const SimpleImageSlider: React.FC = ({
{/* Render Navigation */} - {showNavs && images.length > 0 && slideIdx > 0 && ( + {(loop || slideIdx > 0) && ( 0} + type={navStyle} + size={navSize} + margin={navMargin} onClickNav={handleClickNav} /> )} - {showNavs && images.length > 0 && slideIdx < images.length - 1 && ( + {(loop || slideIdx < images.length - 1) && ( 0} + type={navStyle} + size={navSize} + margin={navMargin} onClickNav={handleClickNav} /> )} - {/* Render Bullets */} - {showBullets && images.length > 0 && } + ); diff --git a/src/ImageSliderBullets.tsx b/src/ImageSliderBullets.tsx index cc97e98..e852a38 100644 --- a/src/ImageSliderBullets.tsx +++ b/src/ImageSliderBullets.tsx @@ -2,24 +2,29 @@ import React from 'react'; import styles from './ImageSliderStyle'; type Props = { + visible: boolean; length: number; currentIdx: number; onClickBullets: (idx: number) => void; }; -const ImageSliderBullets: React.FC = ({ length, currentIdx, onClickBullets }: Props) => { +const ImageSliderBullets: React.FC = ({ visible, length, currentIdx, onClickBullets }: Props) => { return ( -
- {Array.from(Array(length).keys()).map((idx: number) => ( -
+ <> + {visible && length > 0 && ( +
+ {Array.from(Array(length).keys()).map((idx: number) => ( +
+ )} + ); }; diff --git a/src/ImageSliderNavigation.tsx b/src/ImageSliderNavigation.tsx index 56de2d6..21099a5 100644 --- a/src/ImageSliderNavigation.tsx +++ b/src/ImageSliderNavigation.tsx @@ -14,10 +14,11 @@ export enum ImageSliderNavDirection { } type ImageSliderNavigationProps = { - navStyle: ImageSliderNavStyle; - navSize: number; - navMargin: number; + type: ImageSliderNavStyle; + size: number; + margin: number; direction: ImageSliderNavDirection; + visible: boolean; onClickNav: (direction: ImageSliderNavDirection) => (event: React.SyntheticEvent) => void; }; @@ -26,13 +27,17 @@ const altNavArrowRight = 'slide to right'; const ImageSliderNavigation: React.FC = (props: ImageSliderNavigationProps) => { return ( - + <> + {props.visible && ( + + )} + ); }; diff --git a/src/hooks/useAutoPlay.ts b/src/hooks/useAutoPlay.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/hooks/useSlideIndex.ts b/src/hooks/useSlideIndex.ts new file mode 100644 index 0000000..dca9fab --- /dev/null +++ b/src/hooks/useSlideIndex.ts @@ -0,0 +1,71 @@ +import { useEffect, useRef, useState } from 'react'; + +type UseSlideIndexParam = { + imageCount: number; + startIndex: number; + autoPlay: boolean; + autoPlayDelay: number; +}; + +type UseSlideIndexValue = { + slideIdx: number; + updateSlideIdx: (idx: number) => void; + getNextLoopingIdx: (idx: number) => number; + isRightDirection: boolean; + previousSlideIdx: number; +}; + +const useSlideIndex = ({ startIndex, imageCount, autoPlay, autoPlayDelay }: UseSlideIndexParam): UseSlideIndexValue => { + const [slideIdx, setSlideIdx] = useState(startIndex < imageCount ? startIndex : 0); + const isRightDirectionRef = useRef(true); + const previousSlideIdxRef = useRef(slideIdx); + const autoPlayTimerRef = useRef(null); + + const setAutoPlayTimeout = (idx: number) => { + if (!autoPlay || autoPlayTimerRef.current) { + return; + } + autoPlayTimerRef.current = setTimeout(() => { + updateSlideIdx(idx); + }, autoPlayDelay * 1000); + }; + + const removeAutoPlayTimeout = () => { + if (autoPlayTimerRef.current !== null) { + clearTimeout(autoPlayTimerRef.current); + autoPlayTimerRef.current = null; + } + }; + + const getNextLoopingIdx = (idx: number) => { + if (idx >= imageCount) { + return 0; + } else if (idx < 0) { + return imageCount - 1; + } + + return idx; + }; + + const updateSlideIdx = (idx: number) => { + isRightDirectionRef.current = idx > slideIdx; + previousSlideIdxRef.current = slideIdx; + setSlideIdx(getNextLoopingIdx(idx)); + }; + + useEffect(() => { + removeAutoPlayTimeout(); + setAutoPlayTimeout(slideIdx + 1); + return removeAutoPlayTimeout; + }, [slideIdx]); + + return { + slideIdx, + updateSlideIdx, + getNextLoopingIdx, + isRightDirection: isRightDirectionRef.current, + previousSlideIdx: previousSlideIdxRef.current + }; +}; + +export default useSlideIndex; From e8164d2cd21c5a449c4ee91f459d310fbe686ab3 Mon Sep 17 00:00:00 2001 From: kimcoder Date: Sun, 24 Oct 2021 18:40:16 +0900 Subject: [PATCH 2/4] fix: autoPlayDelay fix default to 2.0 --- src/ImageSlider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSlider.tsx b/src/ImageSlider.tsx index 3e4f546..0776bb9 100644 --- a/src/ImageSlider.tsx +++ b/src/ImageSlider.tsx @@ -37,7 +37,7 @@ const SimpleImageSlider: React.FC = ({ showBullets, loop = true, autoPlay = false, - autoPlayDelay = 0.7, + autoPlayDelay = 2.0, startIndex = 0, style = undefined, slideDuration = 0.5, From 877b2b80c7640565a6fd4eaef9a4b2dc08f788d6 Mon Sep 17 00:00:00 2001 From: kimcoder Date: Sun, 24 Oct 2021 18:42:00 +0900 Subject: [PATCH 3/4] docs: update readme for loop, autoPlay, autoPlayDelay --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 338ffbc..b585fd6 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,9 @@ If You want to see more detail source,
| **showNavs** | `Boolean` | `Required` | Toggle Arrow | | **startIndex** | `Number` | `Optional` | start Index of Slide | 0 | | **showBullets** | `Boolean` | `Required` | Toggle Bullets | `true` | +| **loop** | `Boolean` | `Optional` | looping slider | `true` | +| **autoPlay** | `Boolean` | `Optional` | auto play | `false` | +| **autoPlayDelay** | `Boolean` | `Optional` | auto play delay | 2.0 | | **useGPURender** | `Boolean` | `Optional` | Toggle GPU Render | `true` | | **bgColor** | `String` | `Optional` | slider container's css background-color property | `#000000` | | **onClick** | `Function` | `Optional` | Image Click Callback function,
`onClick = (idx, event) => { }`
idx : number : clicked bullet index (begin from 0) | | From dcdf302396d43946ee069307ca32a4b1dec8411e Mon Sep 17 00:00:00 2001 From: kimcoder Date: Sun, 24 Oct 2021 18:42:50 +0900 Subject: [PATCH 4/4] chore: version 2.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 823d1c3..74d19d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-simple-image-slider", - "version": "2.2.0", + "version": "2.3.0", "description": "simple image slider component for react", "main": "dist/index.js", "scripts": {