Skip to content

Instantly share code, notes, and snippets.

@Parameshvadivel
Created July 8, 2022 05:40
Show Gist options
  • Save Parameshvadivel/a3b396786454ed5fa6a30bb1077099ba to your computer and use it in GitHub Desktop.
Save Parameshvadivel/a3b396786454ed5fa6a30bb1077099ba to your computer and use it in GitHub Desktop.
React Easy Carousel
<div id='root'></div>
import styled from "https://cdn.skypack.dev/styled-components@5.2.1";
import * as React from "https://cdn.skypack.dev/react@17.0.1";
import * as ReactDOM from "https://cdn.skypack.dev/react-dom@17.0.1";
const H1 = styled.h1`
text-align: center;
margin: 0;
padding-bottom: 10rem;
`
const Relative = styled.div`
position: relative;
`
const Flex = styled.div`
display: flex;
`
const HorizontalCenter = styled(Flex)`
justify-content: center;
margin-left: auto;
margin-right: auto;
max-width: 25rem;
`
const Container = styled.div`
height: stretch;
width: 100%;
background: #ecf0f1;
`
const Item = styled.div`
color: white;
font-size: 2rem;
text-transform: capitalize;
width: ${({size}) => `${size}rem`};
height: ${({size}) => `${size}rem`};
display: flex;
align-items: center;
justify-content: center;
`
function getPrevElement(list) {
const sibling = list[0].previousElementSibling
if (sibling instanceof HTMLElement) {
return sibling
}
return sibling
}
function getNextElement(list) {
const sibling = list[list.length - 1].nextElementSibling
if (sibling instanceof HTMLElement) {
return sibling
}
return null
}
function usePosition(ref) {
const [prevElement, setPrevElement] = React.useState(null)
const [nextElement, setNextElement] = React.useState(null)
React.useEffect(() => {
const element = ref.current
const update = () => {
const rect = element.getBoundingClientRect()
const visibleElements = Array.from(element.children).filter((child) => {
const childRect = child.getBoundingClientRect()
return childRect.left >= rect.left && childRect.right <= rect.right
})
if (visibleElements.length > 0) {
setPrevElement(getPrevElement(visibleElements))
setNextElement(getNextElement(visibleElements))
}
}
update()
element.addEventListener('scroll', update, {passive: true})
return () => {
element.removeEventListener('scroll', update, {passive: true})
}
}, [ref])
const scrollToElement = React.useCallback(
(element) => {
const currentNode = ref.current
if (!currentNode || !element) return
let newScrollPosition
newScrollPosition =
element.offsetLeft +
element.getBoundingClientRect().width / 2 -
currentNode.getBoundingClientRect().width / 2
currentNode.scroll({
left: newScrollPosition,
behavior: 'smooth',
})
},
[ref],
)
const scrollRight = React.useCallback(() => scrollToElement(nextElement), [
scrollToElement,
nextElement,
])
const scrollLeft = React.useCallback(() => scrollToElement(prevElement), [
scrollToElement,
prevElement,
])
return {
hasItemsOnLeft: prevElement !== null,
hasItemsOnRight: nextElement !== null,
scrollRight,
scrollLeft,
}
}
const CarouserContainer = styled(Relative)`
overflow: hidden;
`
const CarouselItem = styled.div`
flex: 0 0 auto;
margin-left: 1rem;
`
const CarouselButton = styled.button`
position: absolute;
cursor: pointer;
top: 50%;
z-index: 1;
transition: transform 0.1s ease-in-out;
background: white;
border-radius: 15px;
border: none;
padding: 0.5rem;
`
const LeftCarouselButton = styled(CarouselButton)`
left: 0;
transform: translate(-100%, -50%);
${CarouserContainer}:hover & {
transform: translate(0%, -50%);
}
visibility: ${({hasItemsOnLeft}) => (hasItemsOnLeft ? `all` : `hidden`)};
`
const RightCarouselButton = styled(CarouselButton)`
right: 0;
transform: translate(100%, -50%);
${CarouserContainer}:hover & {
transform: translate(0%, -50%);
}
visibility: ${({hasItemsOnRight}) => (hasItemsOnRight ? `all` : `hidden`)};
`
const CarouserContainerInner = styled(Flex)`
overflow-x: scroll;
scroll-snap-type: x mandatory;
-ms-overflow-style: none;
scrollbar-width: none;
// offset for children spacing
margin-left: -1rem;
&::-webkit-scrollbar {
display: none;
}
${CarouselItem} & {
scroll-snap-align: center;
}
`
const ArrowLeft = ({size = 30, color = '#000000'}) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M19 12H6M12 5l-7 7 7 7" />
</svg>
)
const ArrowRight = ({size = 30, color = '#000000'}) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M5 12h13M12 5l7 7-7 7" />
</svg>
)
function Carousel({children}) {
const ref = React.useRef()
const {
hasItemsOnLeft,
hasItemsOnRight,
scrollRight,
scrollLeft,
} = usePosition(ref)
return (
<CarouserContainer role="region" aria-label="Colors carousel">
<CarouserContainerInner ref={ref}>
{React.Children.map(children, (child, index) => (
<CarouselItem key={index}>{child}</CarouselItem>
))}
</CarouserContainerInner>
<LeftCarouselButton hasItemsOnLeft={hasItemsOnLeft} onClick={scrollLeft} aria-label='Previous slide'>
<ArrowLeft />
</LeftCarouselButton>
<RightCarouselButton
hasItemsOnRight={hasItemsOnRight}
onClick={scrollRight}
aria-label='Next slide'
>
<ArrowRight />
</RightCarouselButton>
</CarouserContainer>
)
}
const colors = [
'#f1c40f',
'#f39c12',
'#e74c3c',
'#16a085',
'#2980b9',
'#8e44ad',
'#2c3e50',
'#95a5a6',
]
const numbersArray = Array.from(Array(10).keys()).map((number) => (
<Item size={5} style={{color: 'black'}} key={number}>
{number}
</Item>
))
const colorsArray = colors.map((color) => (
<Item
size={20}
style={{background: color, borderRadius: '20px', opacity: 0.9}}
key={color}
>
{color}
</Item>
))
function App() {
return (
<Container>
<H1>Easy Carousel</H1>
<HorizontalCenter>
<Carousel>{colorsArray}</Carousel>
</HorizontalCenter>
<HorizontalCenter>
<Carousel>{numbersArray}</Carousel>
</HorizontalCenter>
</Container>
)
}
ReactDOM.render(
<App/>,
document.getElementById('root')
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment