Skip to content

Commit f380bdb

Browse files
authored
Merge pull request #60 from kimcoder/feature/issue-52
feat: add loop, autoPlay, autoPlayDelay
2 parents 4a2b2ec + dcdf302 commit f380bdb

8 files changed

+181
-89
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ If You want to see more detail source,<br>
8383
| **showNavs** | `Boolean` | `Required` | Toggle Arrow |
8484
| **startIndex** | `Number` | `Optional` | start Index of Slide | 0 |
8585
| **showBullets** | `Boolean` | `Required` | Toggle Bullets | `true` |
86+
| **loop** | `Boolean` | `Optional` | looping slider | `true` |
87+
| **autoPlay** | `Boolean` | `Optional` | auto play | `false` |
88+
| **autoPlayDelay** | `Boolean` | `Optional` | auto play delay | 2.0 |
8689
| **useGPURender** | `Boolean` | `Optional` | Toggle GPU Render | `true` |
8790
| **bgColor** | `String` | `Optional` | slider container's css background-color property | `#000000` |
8891
| **onClick** | `Function` | `Optional` | Image Click Callback function,<br>`onClick = (idx, event) => { }`<br>idx : number : clicked bullet index (begin from 0) | |

example/App.tsx

+23-17
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,15 @@ import SimpleImageSlider from '../dist';
1515
import Input from '@material-ui/core/Input';
1616
import './app.scss';
1717

18-
const IMAGES = [
19-
{ url: 'images/1.jpg' },
20-
{ url: 'images/2.jpg' },
21-
{ url: 'images/3.jpg' },
22-
{ url: 'images/4.jpg' },
23-
{ url: 'images/5.jpg' },
24-
{ url: 'images/6.jpg' },
25-
{ url: 'images/7.jpg' }
26-
];
18+
const IMAGES = [{ url: 'images/1.jpg' }, { url: 'images/2.jpg' }, { url: 'images/3.jpg' }, { url: 'images/7.jpg' }];
2719

2820
type SliderOptions = {
2921
useGPURender: boolean;
3022
showNavs: boolean;
3123
showBullets: boolean;
24+
loop: boolean;
25+
autoPlay: boolean;
26+
autoPlayDelay: number;
3227
navStyle: 1 | 2;
3328
navSize: number;
3429
navMargin: number;
@@ -41,6 +36,9 @@ const App: React.FC = () => {
4136
useGPURender: true,
4237
showNavs: true,
4338
showBullets: true,
39+
loop: true,
40+
autoPlay: true,
41+
autoPlayDelay: 2,
4442
navStyle: 1,
4543
navSize: 50,
4644
navMargin: 30,
@@ -79,13 +77,7 @@ const App: React.FC = () => {
7977
case 'navStyle':
8078
setSliderOptions({ ...sliderOptions, navStyle: value as 1 | 2 });
8179
break;
82-
case 'useGPURender':
83-
case 'showNavs':
84-
case 'showBullets':
85-
case 'duration':
86-
case 'bgColor':
87-
case 'navSize':
88-
case 'navMargin':
80+
default:
8981
setSliderOptions({ ...sliderOptions, [key]: value });
9082
break;
9183
}
@@ -123,6 +115,9 @@ const App: React.FC = () => {
123115
images={IMAGES}
124116
showBullets={sliderOptions.showBullets}
125117
showNavs={sliderOptions.showNavs}
118+
loop={sliderOptions.loop}
119+
autoPlay={sliderOptions.autoPlay}
120+
autoPlayDelay={sliderOptions.autoPlayDelay}
126121
startIndex={0}
127122
useGPURender={sliderOptions.useGPURender}
128123
navStyle={sliderOptions.navStyle}
@@ -168,11 +163,22 @@ const App: React.FC = () => {
168163
<Select value={sliderOptions.duration} onChange={onChangeSelect} inputProps={{ name: 'duration' }}>
169164
<MenuItem value={0.3}>0.3</MenuItem>
170165
<MenuItem value={0.5}>0.5</MenuItem>
171-
<MenuItem value={0.7}>0.9</MenuItem>
166+
<MenuItem value={0.7}>0.7</MenuItem>
172167
<MenuItem value={1.2}>1.2</MenuItem>
173168
</Select>
174169
</FormControl>
175170
</ListItem>
171+
<ListItem>
172+
<FormControl>
173+
<InputLabel>autoPlayDelay</InputLabel>
174+
<Select value={sliderOptions.autoPlayDelay} onChange={onChangeSelect} inputProps={{ name: 'autoPlayDelay' }}>
175+
<MenuItem value={1.5}>1.5</MenuItem>
176+
<MenuItem value={2.0}>2</MenuItem>
177+
<MenuItem value={2.5}>2.5</MenuItem>
178+
<MenuItem value={3}>3</MenuItem>
179+
</Select>
180+
</FormControl>
181+
</ListItem>
176182
<ListItem>
177183
<FormControl>
178184
<InputLabel>Navigation Size</InputLabel>

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-simple-image-slider",
3-
"version": "2.2.0",
3+
"version": "2.3.0",
44
"description": "simple image slider component for react",
55
"main": "dist/index.js",
66
"scripts": {

src/ImageSlider.tsx

+51-49
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import React, { useCallback, useEffect, useMemo, useState } from 'react';
1+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
22
import ImagePreLoader from './ImageSliderPreLoader';
33
import styles from './ImageSliderStyle';
44
import ImageSliderNavigation, { ImageSliderNavDirection, ImageSliderNavStyle } from './ImageSliderNavigation';
55
import ImageSliderBullets from './ImageSliderBullets';
6+
import useSlideIndex from './hooks/useSlideIndex';
67

78
export type SimpleImageSliderProps = {
89
width: number | string;
@@ -11,6 +12,9 @@ export type SimpleImageSliderProps = {
1112
style?: React.CSSProperties;
1213
showNavs: boolean;
1314
showBullets: boolean;
15+
loop?: boolean;
16+
autoPlay?: boolean;
17+
autoPlayDelay?: number;
1418
startIndex?: number;
1519
slideDuration?: number;
1620
bgColor?: string;
@@ -31,6 +35,9 @@ const SimpleImageSlider: React.FC<SimpleImageSliderProps> = ({
3135
images,
3236
showNavs,
3337
showBullets,
38+
loop = true,
39+
autoPlay = false,
40+
autoPlayDelay = 2.0,
3441
startIndex = 0,
3542
style = undefined,
3643
slideDuration = 0.5,
@@ -46,11 +53,15 @@ const SimpleImageSlider: React.FC<SimpleImageSliderProps> = ({
4653
onCompleteSlide = undefined
4754
}: SimpleImageSliderProps) => {
4855
const rootStyle: React.CSSProperties = useMemo(() => styles.getRootContainer(width, height, bgColor), [width, height, bgColor]);
49-
const [slideIdx, setSlideIdx] = useState(startIndex < images.length ? startIndex : 0);
50-
const [slideDirection, setSlideDirection] = useState(ImageSliderNavDirection.RIGHT);
51-
const [isSliding, setIsSliding] = useState(false);
56+
const { slideIdx, updateSlideIdx, isRightDirection, getNextLoopingIdx, previousSlideIdx } = useSlideIndex({
57+
imageCount: images.length,
58+
startIndex,
59+
autoPlay,
60+
autoPlayDelay: autoPlayDelay + slideDuration
61+
});
5262
const [currentSliderStyle, setCurrentSlideStyle] = useState(styles.getImageSlide(images[0].url, slideDuration, 0, useGPURender));
5363
const [nextSliderStyle, setNextSliderStyle] = useState(styles.getImageSlide(images[1]?.url, slideDuration, 1, useGPURender));
64+
const isSlidingRef = useRef(false);
5465

5566
const handleClick = useCallback(
5667
(event: React.SyntheticEvent) => {
@@ -61,64 +72,54 @@ const SimpleImageSlider: React.FC<SimpleImageSliderProps> = ({
6172

6273
const handleClickNav = useCallback(
6374
(direction: ImageSliderNavDirection) => () => {
64-
if (isSliding) {
75+
if (isSlidingRef.current) {
6576
return;
6677
}
6778
const isRight: boolean = direction === ImageSliderNavDirection.RIGHT;
6879

6980
onClickNav?.(isRight);
70-
slide(isRight ? slideIdx + 1 : slideIdx - 1);
81+
updateSlideIdx(isRight ? slideIdx + 1 : slideIdx - 1);
7182
},
72-
[slideIdx, isSliding]
83+
[onClickNav, slideIdx, updateSlideIdx]
7384
);
7485

7586
const handleClickBullets = useCallback(
7687
(idx: number) => {
77-
if (idx === slideIdx || isSliding) {
88+
if (idx === slideIdx || isSlidingRef.current) {
7889
return;
7990
}
8091

8192
onClickBullets?.(idx);
82-
slide(idx);
93+
updateSlideIdx(idx);
8394
},
84-
[slideIdx, isSliding]
95+
[onClickBullets, slideIdx, updateSlideIdx]
8596
);
8697

87-
const slide = (idx: number) => {
88-
const toNext: boolean = idx > slideIdx;
89-
const currentUrl: string = images[slideIdx].url;
90-
const nextUrl: string = images[idx].url;
91-
const nextReadyX: 1 | -1 = toNext ? 1 : -1;
92-
93-
setSlideIdx(idx);
94-
setSlideDirection(idx > slideIdx ? ImageSliderNavDirection.RIGHT : ImageSliderNavDirection.LEFT);
95-
setCurrentSlideStyle(styles.getImageSlide(currentUrl, 0, 0, useGPURender));
96-
setNextSliderStyle(styles.getImageSlide(nextUrl, 0, nextReadyX, useGPURender));
97-
setIsSliding(true);
98-
99-
onStartSlide?.(idx + 1, images.length);
100-
idx + 2 < images.length && ImagePreLoader.load(images[idx + 2].url);
101-
};
102-
10398
useEffect(() => {
104-
if (isSliding) {
105-
setTimeout(() => {
106-
const toRight: boolean = slideDirection === ImageSliderNavDirection.RIGHT;
107-
const currentUrl: string = images[toRight ? slideIdx - 1 : slideIdx + 1].url;
108-
const nextUrl: string = images[slideIdx].url;
109-
const currentOffsetX: 1 | -1 = toRight ? -1 : 1;
110-
111-
setCurrentSlideStyle(styles.getImageSlide(currentUrl, slideDuration, currentOffsetX, useGPURender));
112-
setNextSliderStyle(styles.getImageSlide(nextUrl, slideDuration, 0, useGPURender));
113-
}, 50);
99+
if (slideIdx === previousSlideIdx) {
100+
return;
114101
}
115-
}, [slideIdx, isSliding]);
102+
103+
const currentUrl: string = images[getNextLoopingIdx(isRightDirection ? slideIdx - 1 : slideIdx + 1)].url;
104+
const nextUrl: string = images[slideIdx].url;
105+
const currentOffsetX: 1 | -1 = isRightDirection ? -1 : 1;
106+
const nextReadyOffsetX: 1 | -1 = isRightDirection ? 1 : -1;
107+
108+
onStartSlide?.(slideIdx + 1, images.length);
109+
setNextSliderStyle(styles.getImageSlide(nextUrl, 0, nextReadyOffsetX, useGPURender));
110+
setTimeout(() => {
111+
isSlidingRef.current = true;
112+
setCurrentSlideStyle(styles.getImageSlide(currentUrl, slideDuration, currentOffsetX, useGPURender));
113+
setNextSliderStyle(styles.getImageSlide(nextUrl, slideDuration, 0, useGPURender));
114+
}, 50);
115+
}, [onStartSlide, slideIdx, isRightDirection]);
116116

117117
const handleSlideEnd = useCallback(() => {
118+
isSlidingRef.current = false;
119+
ImagePreLoader.load(images[slideIdx + 2]?.url);
118120
setCurrentSlideStyle(styles.getImageSlide(images[slideIdx].url, 0, 0, useGPURender));
119-
setIsSliding(false);
120121
onCompleteSlide?.(slideIdx + 1, images.length);
121-
}, [slideIdx]);
122+
}, [onCompleteSlide, slideIdx]);
122123

123124
return (
124125
<div style={{ ...rootStyle, ...style }}>
@@ -130,27 +131,28 @@ const SimpleImageSlider: React.FC<SimpleImageSliderProps> = ({
130131
</div>
131132

132133
{/* Render Navigation */}
133-
{showNavs && images.length > 0 && slideIdx > 0 && (
134+
{(loop || slideIdx > 0) && (
134135
<ImageSliderNavigation
135136
direction={ImageSliderNavDirection.LEFT}
136-
navStyle={navStyle}
137-
navSize={navSize}
138-
navMargin={navMargin}
137+
visible={showNavs && images.length > 0}
138+
type={navStyle}
139+
size={navSize}
140+
margin={navMargin}
139141
onClickNav={handleClickNav}
140142
/>
141143
)}
142-
{showNavs && images.length > 0 && slideIdx < images.length - 1 && (
144+
{(loop || slideIdx < images.length - 1) && (
143145
<ImageSliderNavigation
144146
direction={ImageSliderNavDirection.RIGHT}
145-
navStyle={navStyle}
146-
navSize={navSize}
147-
navMargin={navMargin}
147+
visible={showNavs && images.length > 0}
148+
type={navStyle}
149+
size={navSize}
150+
margin={navMargin}
148151
onClickNav={handleClickNav}
149152
/>
150153
)}
151154

152-
{/* Render Bullets */}
153-
{showBullets && images.length > 0 && <ImageSliderBullets length={images.length} currentIdx={slideIdx} onClickBullets={handleClickBullets} />}
155+
<ImageSliderBullets visible={showBullets} length={images.length} currentIdx={slideIdx} onClickBullets={handleClickBullets} />
154156
</div>
155157
</div>
156158
);

src/ImageSliderBullets.tsx

+17-12
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,29 @@ import React from 'react';
22
import styles from './ImageSliderStyle';
33

44
type Props = {
5+
visible: boolean;
56
length: number;
67
currentIdx: number;
78
onClickBullets: (idx: number) => void;
89
};
910

10-
const ImageSliderBullets: React.FC<Props> = ({ length, currentIdx, onClickBullets }: Props) => {
11+
const ImageSliderBullets: React.FC<Props> = ({ visible, length, currentIdx, onClickBullets }: Props) => {
1112
return (
12-
<div style={styles.getBulletContainer(length)}>
13-
{Array.from(Array(length).keys()).map((idx: number) => (
14-
<button
15-
key={`bullet-${idx}`}
16-
type="button"
17-
data-id={`bullet-${idx}`}
18-
style={idx === currentIdx ? styles.BulletActive : styles.BulletNormal}
19-
onClick={() => onClickBullets(idx)}
20-
/>
21-
))}
22-
</div>
13+
<>
14+
{visible && length > 0 && (
15+
<div style={styles.getBulletContainer(length)}>
16+
{Array.from(Array(length).keys()).map((idx: number) => (
17+
<button
18+
key={`bullet-${idx}`}
19+
type="button"
20+
data-id={`bullet-${idx}`}
21+
style={idx === currentIdx ? styles.BulletActive : styles.BulletNormal}
22+
onClick={() => onClickBullets(idx)}
23+
/>
24+
))}
25+
</div>
26+
)}
27+
</>
2328
);
2429
};
2530

src/ImageSliderNavigation.tsx

+15-10
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ export enum ImageSliderNavDirection {
1414
}
1515

1616
type ImageSliderNavigationProps = {
17-
navStyle: ImageSliderNavStyle;
18-
navSize: number;
19-
navMargin: number;
17+
type: ImageSliderNavStyle;
18+
size: number;
19+
margin: number;
2020
direction: ImageSliderNavDirection;
21+
visible: boolean;
2122
onClickNav: (direction: ImageSliderNavDirection) => (event: React.SyntheticEvent<HTMLButtonElement>) => void;
2223
};
2324

@@ -26,13 +27,17 @@ const altNavArrowRight = 'slide to right';
2627

2728
const ImageSliderNavigation: React.FC<ImageSliderNavigationProps> = (props: ImageSliderNavigationProps) => {
2829
return (
29-
<button type="button" style={styles.getNavStyle(props.direction, props.navSize, props.navMargin)} onClick={props.onClickNav(props.direction)}>
30-
<img
31-
src={props.navStyle === ImageSliderNavStyle.NORMAL ? ImageNavArrowNormal : ImageNavArrowBold}
32-
style={{ width: '100%', ...(props.direction === ImageSliderNavDirection.RIGHT && { transform: 'rotate(180deg)' }) }}
33-
alt={props.direction === ImageSliderNavDirection.LEFT ? altNavArrowLeft : altNavArrowRight}
34-
/>
35-
</button>
30+
<>
31+
{props.visible && (
32+
<button type="button" style={styles.getNavStyle(props.direction, props.size, props.margin)} onClick={props.onClickNav(props.direction)}>
33+
<img
34+
src={props.type === ImageSliderNavStyle.NORMAL ? ImageNavArrowNormal : ImageNavArrowBold}
35+
style={{ width: '100%', ...(props.direction === ImageSliderNavDirection.RIGHT && { transform: 'rotate(180deg)' }) }}
36+
alt={props.direction === ImageSliderNavDirection.LEFT ? altNavArrowLeft : altNavArrowRight}
37+
/>
38+
</button>
39+
)}
40+
</>
3641
);
3742
};
3843

src/hooks/useAutoPlay.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)