Hello world...
This is my first post hoping I can write more. quick intro about me, I have been a developer for 2 years and one year as a Front-End developer, working on react.
I try to create fun react component from scratch rather than relying on a package or some library like material UI or Ant Design. By this post I aim to explain How I created a custom carousel using react hooks some css and typescript.
How It Works:
Before we start I want to show you how it looks in code and how it looks in action
<Carousel
heading="demo"
n={2}
g="12px"
>
{[...Array(10)].map((_val,index) => {
return(
<FlexBoxTitleCard
title={`item ${index+1}`}
key={`item ${index+1}`}
/>
)
})}
</Carousel>
This is How It is in action:
Carousel Demo
CODE:
this is the code for my carousel component we will break it down step by step:
this is how the carousel.tsx looks like
import { useRef, useState } from 'react'
interface CarouselProps {
heading:string,
children:JSX.Element[]
n?:number,
g?:string,
}
export const Carousel = (props:CarouselProps) => {
const [active,setActive] = useState(0)
const carouselRef = useRef<HTMLDivElement>(null)
const scrollToNextElement= () => {
if(carouselRef.current){
if(active < carouselRef.current.childNodes.length - (props.n?props.n:3)){
carouselRef.current.scrollLeft = (carouselRef.current.childNodes[active + 1] as HTMLElement).offsetLeft - (carouselRef.current.parentNode as HTMLElement).offsetLeft;
setActive(active +1)
}
}
}
const scrollToPreviousElement = () => {
console.log(active);
if(carouselRef.current){
if(active > 0) {
carouselRef.current.scrollLeft = (carouselRef.current.childNodes[active - 1] as HTMLElement).offsetLeft - (carouselRef.current.parentNode as HTMLElement).offsetLeft ;
setActive(active - 1)
}
}
}
return(
<div>
<div>
<p>{props.heading}</p>
<div>
<span className="nav-button cursor_pointer" onClick={scrollToPreviousElement} style={{marginRight:"32px"}}>{"<"}</span>
<span className="nav-button cursor_pointer" onClick={scrollToNextElement}>{">"}</span>
</div>
</div>
<div
className="carousel-slides"
ref={carouselRef}
style={{
gridAutoColumns:`calc((100% - (${props.n?props.n:3} - 1)*${props.g?props.g:"32px"})/${props.n?props.n:3})`,
gridGap:props.g
}}
>
{props.children}
</div>
</div>
)
}
carousel.css
.carousel-slides {
display: grid;
grid-auto-flow: column;
/* grid-auto-columns: calc((100% - (var(--n) - 1) * var(--g)) / var(--n));
grid-gap: var(--g); */
overflow: hidden;
scroll-behavior: smooth;
padding: 40px 0;
}
.carousel-slides::-webkit-scrollbar {
display: none;
}
.nav-button {
padding: 8px;
border: 1px solid black;
border-radius: 100%;
}
Carousel will have 4 props:
interface CarouselProps {
heading:string,
children:JSX.Element[]
n?:number,
g?:string,
}
let's break it down,
- heading:(required) - Heading For the Carousel
-
children:(required) - Elements inside a carousel, It doesn't matter if it's
JSX.Element
orJSX.Element[]
- n_:(optional) - number of elements to be shown , default 3
- g:(optional) - gap between each carousel element , default 32px
const [active,setActive] = useState(0)
const carouselRef = useRef<HTMLDivElement>(null)
I have a state active
which denotes the left most element in the JSX.Element array
And I have a reference to the carousel element wrapper carouselRef
const scrollToNextElement= () => {
if(carouselRef.current){
if(active < carouselRef.current.childNodes.length - (props.n?props.n:3)){
carouselRef.current.scrollLeft = (carouselRef.current.childNodes[active + 1] as HTMLElement).offsetLeft - (carouselRef.current.parentNode as HTMLElement).offsetLeft;
setActive(active +1)
}
}
}
scrollToNextElement
function lets you scroll to next element in the carousel. Using carouselRef
I am able to access the child nodes and I just change scroll left of the wrapper element to the next elements offset left, it's important substract the parent offsetleft to avoid scrolling the extra padding outside the parent element
the wrapper element itself is a grid component, the inline styling of the element is done so that it can take in prop n to get how many elements to show inside the carousel
gridAutoColumns:`calc((100% - (${props.n?props.n:3} - 1)*${props.g?props.g:"32px"})/${props.n?props.n:3})`
this line of could sets the grid in one single row with as many as n number of items available to see, since the overflow is hidden we cannot see the rest of the elements.
scrollToPreviousElement
uses the same logic
What Can I do Next:
add another prop
step:(optional) - prop which accepts number and will be the numbers of elements it will skip when we click next or previous
Scrape both scrollToNextElement
and scrollToPreviousElement
and change to scrollToElement
which accepts a number and skips to that element
Top comments (1)
@lukeshiru Thanks for the suggestion,
Thanks for the feedback on nullish coalescing and yes I am trying to improve my naming conventions
The reason Why I used
carouselRef
is so that if the elements are of different type I can change my prop fromJSX.Element[ ]
to JSX.Element but still do the functionality.Once again I really appreciate the feedback and suggestion