Skip to content

Instantly share code, notes, and snippets.

@cptSwing
Created August 1, 2022 13:27
Show Gist options
  • Save cptSwing/e9601b14ee27df3cf9c93b802f00339b to your computer and use it in GitHub Desktop.
Save cptSwing/e9601b14ee27df3cf9c93b802f00339b to your computer and use it in GitHub Desktop.
3D Carousel using Typescript-React; and mostly Tailwind
/**
* From: https://github.com/suhailsulu/react-carousel-3d ; edited for TS & TW
*/
import { useState, useEffect, useRef } from 'react';
import { useSwipeable } from 'react-swipeable';
import './3dCarouselStyles.css';
interface CarouselProps {
slides:
| JSX.Element[]
| string[] /* can now also use array of image hrefs instead. Mix would be best? */;
autoplay: boolean;
interval?: number;
onSlideChange?: (currentSlide: number) => void;
}
interface SlideObject {
class: string;
element: JSX.Element | string;
}
export function Carousel(props: CarouselProps) {
const [slideTotal, setSlideTotal] = useState(0);
const [slideCurrent, setSlideCurrent] = useState(-1);
const [slides, setSlides] = useState<SlideObject[]>([]);
const intervalRef = useRef<NodeJS.Timeout | undefined>(undefined);
const nextRef = useRef<HTMLDivElement | null>(null);
const handlers = useSwipeable({
onSwipedLeft: () => slideRight(),
onSwipedRight: () => slideLeft(),
preventScrollOnSwipe: true,
trackMouse: true,
});
useEffect(
() => {
const locSlides: SlideObject[] = [];
props.slides.forEach((slide) => {
const slideobject = {
class: 'slider-single proactivede',
element: slide,
};
locSlides.push(slideobject);
});
/* This supposedly copies a slide for necessary 3 slides in total to appear.. */
if (props.slides.length === 2) {
props.slides.forEach((slide) => {
const slideobject = {
class: 'slider-single proactivede',
element: slide,
};
locSlides.push(slideobject);
});
}
setSlides(locSlides);
setSlideTotal(locSlides.length - 1);
setSlideCurrent(-1);
if (slideCurrent === -1) {
setTimeout(() => {
nextRef.current?.click();
if (props.autoplay) {
intervalRef.current = setTimeout(() => {
nextRef.current?.click();
}, props.interval);
}
}, 500);
}
},
[
/*props.slides*/
]
);
const slideRight = () => {
let preactiveSlide;
let proactiveSlide;
let slideCurrentLoc = slideCurrent;
const activeClass = 'slider-single active';
const slide = [...slides];
if (slideTotal > 1) {
if (slideCurrentLoc < slideTotal) {
slideCurrentLoc++;
} else {
slideCurrentLoc = 0;
}
if (slideCurrentLoc > 0) {
preactiveSlide = slide[slideCurrentLoc - 1];
} else {
preactiveSlide = slide[slideTotal];
}
const activeSlide = slide[slideCurrentLoc];
if (slideCurrentLoc < slideTotal) {
proactiveSlide = slide[slideCurrentLoc + 1];
} else {
proactiveSlide = slide[0];
}
slide.forEach((slid, _index) => {
if (slid.class.includes('preactivede')) {
slid.class = 'slider-single proactivede';
}
if (slid.class.includes('preactive')) {
slid.class = 'slider-single preactivede';
}
});
preactiveSlide.class = 'slider-single preactive';
activeSlide.class = activeClass;
proactiveSlide.class = 'slider-single proactive';
setSlides(slide);
setSlideCurrent(slideCurrentLoc);
props.onSlideChange && props.onSlideChange(slideCurrentLoc);
if (props.autoplay) {
clearTimeout(intervalRef.current);
intervalRef.current = setTimeout(() => {
nextRef.current?.click();
}, props.interval);
}
} else if (slide[0] && slide[0].class !== activeClass) {
slide[0].class = activeClass;
setSlides(slide);
setSlideCurrent(0);
}
};
const slideLeft = () => {
if (slideTotal > 1) {
let preactiveSlide;
let proactiveSlide;
let slideCurrentLoc = slideCurrent;
const slide = [...slides];
if (slideCurrentLoc > 0) {
slideCurrentLoc--;
} else {
slideCurrentLoc = slideTotal;
}
if (slideCurrentLoc < slideTotal) {
proactiveSlide = slide[slideCurrentLoc + 1];
} else {
proactiveSlide = slide[0];
}
const activeSlide = slide[slideCurrentLoc];
if (slideCurrentLoc > 0) {
preactiveSlide = slide[slideCurrentLoc - 1];
} else {
preactiveSlide = slide[slideTotal];
}
slide.forEach((slid, _index) => {
if (slid.class.includes('proactivede')) {
slid.class = 'slider-single preactivede';
}
if (slid.class.includes('proactive')) {
slid.class = 'slider-single proactivede';
}
});
preactiveSlide.class = 'slider-single preactive';
activeSlide.class = 'slider-single active';
proactiveSlide.class = 'slider-single proactive';
setSlides(slide);
setSlideCurrent(slideCurrentLoc);
props.onSlideChange && props.onSlideChange(slideCurrentLoc);
}
};
const centralElementCss =
'w-full h-full rounded-md object-center object-cover select-none touch-none';
return (
<div className='bg-white w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8' {...handlers}>
{slides?.length > 0 && (
<div className='flex flex-col justify-center items-center overflow-hidden lg:overflow-visible'>
<div>
<div className='grid'>
{slides.map((slider, index) => (
<div className={`flex justify-between ${slider.class}`} key={index}>
<div className='slider-single-content p-2 bg-gray-100 rounded-lg shadow-xl lg:h-[21rem] lg:w-128 xl:h-[26rem] xl:w-160 2xl:h-128 2xl:w-192 h-80 w-128'>
{typeof slider.element === 'string' ? (
<img
className={centralElementCss}
src={slider.element}
/>
) : (
<div className={centralElementCss}>
{slider.element}
</div>
)}
</div>
</div>
))}
</div>
</div>
<div className='flex justify-between mt-4 w-32'>
<div className='pr-4 z-30 self-end cursor-pointer' onClick={slideLeft}>
<div>
<svg
className='w-8 h-8 border-2 border-gray-200 rounded-full text-white dark:text-gray-800 bg-gray-200/75'
fill='none'
stroke='currentColor'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
<path
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth='3'
d='M15 19l-7-7 7-7'
></path>
</svg>
</div>
</div>
<div
className='pl-4 z-30 self-end cursor-pointer'
onClick={slideRight}
ref={nextRef}
>
<div>
<svg
className='w-8 h-8 border-2 border-gray-200 rounded-full text-white dark:text-gray-800 bg-gray-200/75'
fill='none'
stroke='currentColor'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
<path
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth='3'
d='M9 5l7 7-7 7'
></path>
</svg>
</div>
</div>
</div>
</div>
)}
</div>
);
}
Carousel.defaultProps = {
autoplay: false,
interval: 3000,
onSlideChange() {
return null;
},
};
.slider-single {
@apply z-0 row-start-1 col-start-1 row-end-2 col-end-2 transition-[z-index] delay-[0ms];
}
.slider-single .slider-single-content {
@apply opacity-0 lg:opacity-50 scale-50 translate-x-0;
transition: opacity 333ms 0ms, transform 1000ms 0ms;
}
.slider-single.proactive {
@apply z-20;
}
/* 2nd position before moving to center stage */
.slider-single.proactivede .slider-single-content {
@apply opacity-0 lg:opacity-75 scale-50 lg:scale-50 translate-x-0;
}
/* first position before moving to center stage */
.slider-single.proactive .slider-single-content {
@apply opacity-0 lg:opacity-75 scale-90 lg:scale-75 translate-x-1/2;
transition: opacity 333ms 0ms, transform 1000ms 0ms;
}
.slider-single.active {
@apply z-50;
}
.slider-single.active .slider-left,
.slider-single.active .slider-right {
@apply block;
}
.slider-single.active .slider-single-content {
@apply opacity-100 scale-100;
transition: opacity 333ms 0ms, transform 1000ms 0ms;
}
/* first position moving from center stage */
.slider-single.preactive .slider-single-content {
@apply opacity-0 lg:opacity-90 scale-90 lg:scale-75 -translate-x-1/2;
transition: opacity 333ms 0ms, transform 1000ms 0ms;
}
/* opacity when moving to second position from center stage */
.slider-single.preactive {
@apply z-20;
}
/* 2nd position moving from center stage */
.slider-single.preactivede .slider-single-content {
@apply opacity-0 lg:opacity-60 scale-50 lg:scale-50 -translate-x-1;
transition: opacity 333ms 0ms, transform 1000ms 0ms;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment