๐ ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋(Image Slider) ์ปดํฌ๋ํธ๋?
์ด๋ฏธ์ง ์ฌ๋ผ์ด๋๋ ์ฌ๋ฌ ์ฅ์ ์ด๋ฏธ์ง๋ฅผ ์ข์ฐ๋ก ๋๊ธฐ๊ฑฐ๋ ์๋์ผ๋ก ์์๋๋ก ๋ณด์ฌ์ฃผ๋ UI ์ปดํฌ๋ํธ์ ๋๋ค. ๊ฐค๋ฌ๋ฆฌ, ์ํ ํ๋ฆฌ๋ทฐ, ๋ฐฐ๋ ๋ฑ ๋ค์ํ ๊ณณ์์ ์๊ฐ์ ์ฝํ ์ธ ๋ฅผ ํจ์จ์ ์ผ๋ก ๋ณด์ฌ์ค ๋ ์ฌ์ฉ๋ฉ๋๋ค.
๐ฏ ์ฃผ์ ๊ธฐ๋ฅ
- ๋ค์ค ์ด๋ฏธ์ง ์ง์: ์ฌ๋ฌ ์ฅ์ ์ด๋ฏธ์ง๋ฅผ ์์๋๋ก ๋ณด์ฌ์ค.
- ๋ด๋น๊ฒ์ด์ : ํ์ดํ(Prev/Next) ๋ฒํผ, ๋๋๊ทธ, ์ค์์ดํ ๋ฑ์ผ๋ก ์ด๋ ๊ฐ๋ฅ.
- ํ์ด์ง๋ค์ด์ : ํ์ฌ ์ฌ๋ผ์ด๋ ์์น๋ฅผ ํ์ํ๋ ์ธ๋์ผ์ดํฐ(์ , ์ซ์ ๋ฑ) ์ ๊ณต.
- ์๋ ์ฌ์(์ต์ ): ์ฃผ๊ธฐ์ ์ผ๋ก ์ฌ๋ผ์ด๋๋ฅผ ์๋ ์ ํํ๋ ๊ธฐ๋ฅ(AutoPlay).
- ๋ฐ์ํ ์ง์: ํ๋ฉด ํฌ๊ธฐ์ ๋ง๊ฒ ์ ์ฐํ๊ฒ ๋ ์ด์์ ์กฐ์ .
- ์ปค์คํฐ๋ง์ด์ง ๊ฐ๋ฅ: ์ ํ ์ ๋๋ฉ์ด์ , ์๋, ๋ฃจํ ์ฌ๋ถ ๋ฑ์ ์ค์ ๊ฐ๋ฅ.
โ๏ธ๊ตฌํ ๋ด์ฉ
transition
๊ณผ position
์ ํ์ฉํ ์ฌ๋ผ์ด๋
const ImageSlide1_React = () => {
const [currentIndex, setCurrentIndex] = useState(0)
const wrapperRef = useRef<HTMLDivElement>(null)
const animatingRef = useRef(false)
const move = useCallback(
(direction: Direction) => () => {
if (animatingRef.current) return
setCurrentIndex(prev => {
const next = ((direction === 'right' ? prev + 1 : prev - 1) + dataLength) % dataLength
animatingRef.current = true
return next
})
},
[],
)
const handleTransitionEnd = () => {
animatingRef.current = false
}
return (
<>
<h3>#2. React</h3>
<div className={cx('imageSlide', 'imageSlide2')} ref={wrapperRef}>
<ul
className={cx('container')}
style={{ left: currentIndex * 600 * -1 + 'px' }}
onTransitionEnd={handleTransitionEnd}
>
{data.map((url, index) => (
<li key={index} className={cx('item')}>
<LazyImage src={url} width={600} height={320} rootElemRef={wrapperRef} />
<span>#{index + 1}</span>
</li>
))}
</ul>
<button className={cx('navButton', 'navLeft')} onClick={move('left')} />
<button className={cx('navButton', 'navRight')} onClick={move('right')} />
</div>
</>
)
}
- ํ์ฌ ์ธ๋ฑ์ค๊ฐ ๋ณด์ด๋ ๊ณณ์ผ๋ก ์ค๋๋ก
left
๊ฐ์ ์กฐ์
์คํฌ๋กค์ ํ์ฉํ ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋
๊ฐ๋ก์คํฌ๋กค์ ํ์ฉํ๊ธฐ ๋๋ฌธ์ ๋ชจ๋ฐ์ผ์์ ์์ฐ์ค๋ฌ์ด ๊ธฐ๋ฅ ์ฒ๋ฆฌ ๊ฐ๋ฅ
const moveTo = useCallback((index: number) => {
if (scrollingRef.current) return
containerRef.current!.scrollTo({
left: index * 600,
behavior: 'smooth',
})
scrollingRef.current = true
setCurrentIndex(index)
}, [])
const move = useCallback(
(direction: Direction) => () => {
if (scrollingRef.current) return
setCurrentIndex(prev => {
const next = ((direction === 'right' ? prev + 1 : prev - 1) + dataLength) % dataLength
containerRef.current!.scrollTo({
left: next * 600,
behavior: 'smooth',
})
scrollingRef.current = true
return next
})
},
[],
)
- ์คํฌ๋กค ๊ธฐ๋ฅ์ธ scrollTo ํจ์๋ฅผ ์ฌ์ฉํด์ ์ฒ๋ฆฌ
useEffect(() => {
const handleScrollEnd = () => {
scrollingRef.current = false
}
const $container = containerRef.current
if ($container) {
$container.scrollLeft = 0
$container.addEventListener('scrollend', handleScrollEnd)
}
return () => {
$container?.removeEventListener('scrollend', handleScrollEnd)
}
}, [])
scrollingRef์
scrollend
์ด๋ฒคํธ๋ฅผ ํ์ฉํ์ฌ, ์คํฌ๋กค์ด ๋์ํ๊ณ ์์ ๋์๋ ํจ์๋ฅผ ๋ง์
'๊ฐ๋ฐ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[UI] Popover[UI์์ ๋ง๋ค๊ธฐ] (0) | 2025.04.20 |
---|---|
[UI] Snackbar [UI์์ ๋ง๋ค๊ธฐ] (0) | 2025.04.07 |
[UI] Scroll Spy [UI ์์ ๋ง๋ค๊ธฐ] (0) | 2025.03.30 |
[UI] Scroll Box (0) | 2025.03.28 |
[UI] ๋ฌดํ ์คํฌ๋กค(feat: UI ์์ ์ง์ ๋ง๋ค๊ธฐ) (0) | 2025.03.03 |