I am trying to build a carousel just like how it is on the amazon home page. I used React Bootstrap for the carousel but it does not seems to work. It just stacks every item just like in a column.
<Carousel>
<Carousel.Item>
<img src={'https://images-na.ssl-images-amazon.com/images/G/01/AmazonExports/Fuji/2020/October/Fuji_Tallhero_Dash_en_US_1x._CB418727898_.jpg'} alt="" />
</Carousel.Item>
<Carousel.Item>
<img src={'https://images-na.ssl-images-amazon.com/images/G/01/AmazonExports/Events/2020/PrimeDay/Fuji_TallHero_NonPrime_v2_en_US_1x._CB403670067_.jpg'} alt="" />
</Carousel.Item>
</Carousel>
Code with any other framework other than React Bootstrap is accepted in the answers.
I just implemented keen slider on a project I'm doing for a client (dentist) in about an hour or so. Using it for patient testimonials. Not too bad, vercel does a nice job of isolating the heavy lifting to a single components subdirectory.
import { useKeenSlider } from 'keen-slider/react';
import React, {
Children,
FC,
isValidElement,
useState,
useEffect,
useRef
} from 'react';
import cn from 'classnames';
import css from './keen-slider.module.css';
const KeenSlider: FC = ({ children }) => {
const [currentSlide, setCurrentSlide] = useState(0);
const [isMounted, setIsMounted] = useState(false);
const sliderContainerRef = useRef<HTMLDivElement>(null);
const [ref, slider] = useKeenSlider<HTMLDivElement>({
loop: true,
slidesPerView: 1,
mounted: () => setIsMounted(true),
slideChanged(s) {
setCurrentSlide(s.details().relativeSlide);
}
});
// Stop the history navigation gesture on touch devices
useEffect(() => {
const preventNavigation = (event: TouchEvent) => {
// Center point of the touch area
const touchXPosition = event.touches[0].pageX;
// Size of the touch area
const touchXRadius = event.touches[0].radiusX || 0;
// We set a threshold (10px) on both sizes of the screen,
// if the touch area overlaps with the screen edges
// it's likely to trigger the navigation. We prevent the
// touchstart event in that case.
if (
touchXPosition - touchXRadius < 10 ||
touchXPosition + touchXRadius > window.innerWidth - 10
)
event.preventDefault();
};
sliderContainerRef.current!.addEventListener('touchstart', preventNavigation);
return () => {
sliderContainerRef.current!.removeEventListener(
'touchstart',
preventNavigation
);
};
}, []);
return (
<div className={css.root} ref={sliderContainerRef}>
<button
className={cn(css.leftControl, css.control)}
onClick={slider?.prev}
aria-label='Previous Testimonial'
/>
<button
className={cn(css.rightControl, css.control)}
onClick={slider?.next}
aria-label='Next Testimonial'
/>
<div
ref={ref}
className='keen-slider h-full transition-opacity duration-150'
style={{ opacity: isMounted ? 1 : 0 }}
>
{Children.map(children, child => {
// Add the keen-slider__slide className to children
if (isValidElement(child)) {
return {
...child,
props: {
...child.props,
className: `${
child.props.className ? `${child.props.className} ` : ''
}keen-slider__slide`
}
};
}
return child;
})}
</div>
{slider && (
<div className={cn(css.positionIndicatorsContainer)} ref={ref}>
{[...Array(slider.details().size).keys()].map(idx => {
return (
<button
aria-label='Position indicator'
key={idx}
className={cn(css.positionIndicator + `keen-slider__slide`, {
[css.positionIndicatorActive]: currentSlide === idx
})}
onClick={() => {
slider.moveToSlideRelative(idx);
}}
>
<div className={css.dot} />
</button>
);
})}
</div>
)}
</div>
);
};
export default KeenSlider;
The corresponding css file
.root {
@apply relative w-full h-full;
overflow-y: hidden;
}
.leftControl,
.rightControl {
@apply absolute top-1/2 -translate-x-1/2 z-20 w-16 h-16 flex items-center justify-center bg-hover-1 rounded-full;
}
.leftControl {
@apply bg-cover left-10;
background-image: url('/cursor-left.png');
}
.rightControl {
@apply bg-cover right-10;
background-image: url('/cursor-right.png');
}
.leftControl:hover,
.rightControl:hover {
@apply bg-hover-2 outline-none shadow-outline-blue;
}
.control {
@apply opacity-0 transition duration-150;
}
.root:hover .control {
@apply opacity-100;
}
.positionIndicatorsContainer {
@apply hidden;
@screen md {
@apply block absolute left-1/2;
transform: translateX(-50%);
}
}
.positionIndicator {
@apply rounded-full p-2;
}
.dot {
@apply bg-hover-1 transition w-3 h-3 rounded-full;
}
.positionIndicatorActive .dot {
@apply bg-white;
}
.positionIndicator:hover .dot {
@apply bg-hover-2;
}
.positionIndicator:focus {
@apply outline-none;
}
.positionIndicator:focus .dot {
@apply shadow-outline-blue;
}
.positionIndicatorActive:hover .dot {
@apply bg-white;
}
.number-slide {
background: grey;
display: flex;
align-items: center;
justify-content: center;
font-size: 50px;
color: #fff;
font-weight: 500;
height: 300px;
max-height: 100vh;
}
My implementation
import cn from 'classnames';
import { Container } from '@components/UI';
import TestimonialsData from './TestimonialsData';
import TestimonialsWrapper from './TestimonialsWrapper';
import dynamic from 'next/dynamic';
import { ALL_TESTIMONIALS } from '@lib/graphql';
import { useQuery } from '@apollo/client';
import {
PostObjectsConnectionOrderbyEnum,
OrderEnum
} from '@_types/graphql-global-types';
import {
AllTestimonials,
AllTestimonialsVariables
} from '@lib/graphql/AllTestimonials/__generated__/AllTestimonials';
import css from './testimonials.module.css';
import KeenSlider from '../KeenSlider/keen-slider';
export const TestimonialsQueryVars: AllTestimonialsVariables = {
first: 10,
order: OrderEnum.ASC,
field: PostObjectsConnectionOrderbyEnum.TITLE
};
const LoadingDots = dynamic(() => import('@components/UI/LoadingDots'));
const Loading = () => (
<div className='w-80 h-80 flex items-center text-center justify-center p-3'>
<LoadingDots />
</div>
);
const dynamicProps = {
loading: () => <Loading />
};
const ApolloErrorMessage = dynamic(
() => import('@components/ErrorMessage'),
dynamicProps
);
const TestimonialsCoalesced = () => {
const { loading, error, data } = useQuery<
AllTestimonials,
AllTestimonialsVariables
>(ALL_TESTIMONIALS, {
variables: TestimonialsQueryVars,
notifyOnNetworkStatusChange: true
});
return error ? (
<>
<ApolloErrorMessage
message={`${error.message}`}
graphQLErrors={error.graphQLErrors}
networkError={error.networkError}
extraInfo={error.extraInfo}
stack={error.stack}
name={error.name}
/>
</>
) : loading && !error ? (
<Loading />
) : (
<Container className={cn('mx-auto max-w-none w-full')} clean>
{data &&
data.prosites !== null &&
data.prosites.edges !== null &&
data.prosites.edges.length > 0 ? (
<TestimonialsWrapper root={css.sliderContainer}>
<KeenSlider>
{data.prosites.edges.map(edge => {
return edge !== null && edge.cursor !== null && edge.node !== null ? (
<div className={css.childContainer}>
<TestimonialsData
root={''}
key={edge.node.id}
id={edge.node.id}
__typename={edge.node.__typename}
title={edge.node.title}
slug={edge.node.slug}
featuredImage={edge.node.featuredImage}
content={edge.node.content}
modified={edge.node.modified}
/>
</div>
) : (
<div>{error}</div>
);
})}
</KeenSlider>
</TestimonialsWrapper>
) : (
<div>{error}</div>
)}
</Container>
);
};
export default TestimonialsCoalesced;
The CSS from my testimonials implementation
.sliderContainer {
@apply absolute z-10 inset-0 flex items-center justify-center overflow-x-hidden;
}
.childContainer {
& > div {
@apply h-full;
& > div {
@apply h-full;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With