Created
August 1, 2022 13:27
-
-
Save cptSwing/e9601b14ee27df3cf9c93b802f00339b to your computer and use it in GitHub Desktop.
3D Carousel using Typescript-React; and mostly Tailwind
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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; | |
}, | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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