Skip to content

feat: add loop, autoPlay, autoPlayDelay #60

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ If You want to see more detail source,<br>
| **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,<br>`onClick = (idx, event) => { }`<br>idx : number : clicked bullet index (begin from 0) | |
Expand Down
40 changes: 23 additions & 17 deletions example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -168,11 +163,22 @@ const App: React.FC = () => {
<Select value={sliderOptions.duration} onChange={onChangeSelect} inputProps={{ name: 'duration' }}>
<MenuItem value={0.3}>0.3</MenuItem>
<MenuItem value={0.5}>0.5</MenuItem>
<MenuItem value={0.7}>0.9</MenuItem>
<MenuItem value={0.7}>0.7</MenuItem>
<MenuItem value={1.2}>1.2</MenuItem>
</Select>
</FormControl>
</ListItem>
<ListItem>
<FormControl>
<InputLabel>autoPlayDelay</InputLabel>
<Select value={sliderOptions.autoPlayDelay} onChange={onChangeSelect} inputProps={{ name: 'autoPlayDelay' }}>
<MenuItem value={1.5}>1.5</MenuItem>
<MenuItem value={2.0}>2</MenuItem>
<MenuItem value={2.5}>2.5</MenuItem>
<MenuItem value={3}>3</MenuItem>
</Select>
</FormControl>
</ListItem>
<ListItem>
<FormControl>
<InputLabel>Navigation Size</InputLabel>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
100 changes: 51 additions & 49 deletions src/ImageSlider.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -31,6 +35,9 @@ const SimpleImageSlider: React.FC<SimpleImageSliderProps> = ({
images,
showNavs,
showBullets,
loop = true,
autoPlay = false,
autoPlayDelay = 2.0,
startIndex = 0,
style = undefined,
slideDuration = 0.5,
Expand All @@ -46,11 +53,15 @@ const SimpleImageSlider: React.FC<SimpleImageSliderProps> = ({
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) => {
Expand All @@ -61,64 +72,54 @@ const SimpleImageSlider: React.FC<SimpleImageSliderProps> = ({

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 (
<div style={{ ...rootStyle, ...style }}>
Expand All @@ -130,27 +131,28 @@ const SimpleImageSlider: React.FC<SimpleImageSliderProps> = ({
</div>

{/* Render Navigation */}
{showNavs && images.length > 0 && slideIdx > 0 && (
{(loop || slideIdx > 0) && (
<ImageSliderNavigation
direction={ImageSliderNavDirection.LEFT}
navStyle={navStyle}
navSize={navSize}
navMargin={navMargin}
visible={showNavs && images.length > 0}
type={navStyle}
size={navSize}
margin={navMargin}
onClickNav={handleClickNav}
/>
)}
{showNavs && images.length > 0 && slideIdx < images.length - 1 && (
{(loop || slideIdx < images.length - 1) && (
<ImageSliderNavigation
direction={ImageSliderNavDirection.RIGHT}
navStyle={navStyle}
navSize={navSize}
navMargin={navMargin}
visible={showNavs && images.length > 0}
type={navStyle}
size={navSize}
margin={navMargin}
onClickNav={handleClickNav}
/>
)}

{/* Render Bullets */}
{showBullets && images.length > 0 && <ImageSliderBullets length={images.length} currentIdx={slideIdx} onClickBullets={handleClickBullets} />}
<ImageSliderBullets visible={showBullets} length={images.length} currentIdx={slideIdx} onClickBullets={handleClickBullets} />
</div>
</div>
);
Expand Down
29 changes: 17 additions & 12 deletions src/ImageSliderBullets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Props> = ({ length, currentIdx, onClickBullets }: Props) => {
const ImageSliderBullets: React.FC<Props> = ({ visible, length, currentIdx, onClickBullets }: Props) => {
return (
<div style={styles.getBulletContainer(length)}>
{Array.from(Array(length).keys()).map((idx: number) => (
<button
key={`bullet-${idx}`}
type="button"
data-id={`bullet-${idx}`}
style={idx === currentIdx ? styles.BulletActive : styles.BulletNormal}
onClick={() => onClickBullets(idx)}
/>
))}
</div>
<>
{visible && length > 0 && (
<div style={styles.getBulletContainer(length)}>
{Array.from(Array(length).keys()).map((idx: number) => (
<button
key={`bullet-${idx}`}
type="button"
data-id={`bullet-${idx}`}
style={idx === currentIdx ? styles.BulletActive : styles.BulletNormal}
onClick={() => onClickBullets(idx)}
/>
))}
</div>
)}
</>
);
};

Expand Down
25 changes: 15 additions & 10 deletions src/ImageSliderNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLButtonElement>) => void;
};

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

const ImageSliderNavigation: React.FC<ImageSliderNavigationProps> = (props: ImageSliderNavigationProps) => {
return (
<button type="button" style={styles.getNavStyle(props.direction, props.navSize, props.navMargin)} onClick={props.onClickNav(props.direction)}>
<img
src={props.navStyle === ImageSliderNavStyle.NORMAL ? ImageNavArrowNormal : ImageNavArrowBold}
style={{ width: '100%', ...(props.direction === ImageSliderNavDirection.RIGHT && { transform: 'rotate(180deg)' }) }}
alt={props.direction === ImageSliderNavDirection.LEFT ? altNavArrowLeft : altNavArrowRight}
/>
</button>
<>
{props.visible && (
<button type="button" style={styles.getNavStyle(props.direction, props.size, props.margin)} onClick={props.onClickNav(props.direction)}>
<img
src={props.type === ImageSliderNavStyle.NORMAL ? ImageNavArrowNormal : ImageNavArrowBold}
style={{ width: '100%', ...(props.direction === ImageSliderNavDirection.RIGHT && { transform: 'rotate(180deg)' }) }}
alt={props.direction === ImageSliderNavDirection.LEFT ? altNavArrowLeft : altNavArrowRight}
/>
</button>
)}
</>
);
};

Expand Down
Empty file added src/hooks/useAutoPlay.ts
Empty file.
Loading