Carousel
是一個像旋轉木馬一樣會輪流轉的輪播元件。在一個內容空間有限的可視範圍中進行內容的輪播展示。通常適用於一組圖片或是卡片的輪播。
Carousel 的樣式也是五花八門,隨便在 google 下一個關鍵字就能夠找到各種不同形式的 Carousel。
所以在這邊我們也是挑一個簡單易做的 Carousel 來實現。
dataSource
由於是一組輪流播放的圖片,所以首先我們需要提供給這個元件一個 list 的資料,其中 list 的每一筆資料我們希望能夠包含一個 image url。
autoplay
autoplay 提供一個 boolean 來決定這個輪播是否自動切換。
dots
dots 是一個 boolean,用來決定是否出現「指示點」。
屬性 | 說明 | 類型 | 默認值 |
---|---|---|---|
className | 客製化樣式 | string | |
dataSource | 輪播資料 | list of image url | |
autoplay | 是否自動播放 | boolean | false |
hasDots | 是否顯示指示點 | boolean | true |
hasControlArrow | 是否顯示上一個、下一個切換鍵 | boolean | true |
我們的 Carousel 包含了輪播圖片本身、左右切換按鈕,以及指示點,所以 DOM 大致上會長得像下面這個形狀:
<CarouselWrapper>
<ImageWrapper>
{...images...}
</ImageWrapper>
<ControlButtons />
<Dots />
</CarouselWrapper>
由下圖知道,輪播圖片、左右切換按鈕以及指示點都是疊在一起的,有點是分不同圖層的概念,所以這邊的定位都是使用 position: absolute;
。
圖片輪播的計算方法
我們就直接來看 Carousel 的核心,到底要怎麼讓圖片可以輪播。
這邊我們是要做 slide 動畫的輪播,所以為了讓圖片可以在 X 軸上左右滑動,我們勢必會使用到 position: absolute;
、left
、transition
這三個關鍵的 CSS。
如下圖示意,為了讓圖片能夠左右滑動輪播,我們可以先將圖片排成一整排,並且在可視範圍之外的地方隱藏這些圖片。
排成一整排的方式不能像我們以前那樣使用 flex 佈局來達成,而是會需要算一些數學,計算出每一張圖片的位置,也就是他的 left
值,之後要輪轉的時候,就是去改變這個 left
數值,搭配 transition
過場動畫,就能夠做到我們輪播的效果。
概念上已經說明完了,接下來我們來看程式碼,left 的計算很簡單,就是目前迭代到的這個 image 與正在可視範圍中的那個 current image 的距離,乘上圖片的寬度就是了:
const makePosition = ({ itemIndex }) => (itemIndex - currentIndex) * imageWidth;
<ImageWrapper>
{
dataSource.map((imageUrl, index) => (
<Image
key={imageUrl}
src={imageUrl}
alt=""
$left={makePosition({ itemIndex: index })}
/>
))
}
</ImageWrapper>
切換圖片的左右按鈕
切換左右的按鈕我希望只專注在計算哪一張圖片是 current ,也就是正在可視範圍中的圖片,避免在這些 function 裡面做太多其他的事,保持他功能的單純性。
所以看下面 click next 的 function,我只有計算讓 current index 不斷的 +1
,直到尾部的時候再從頭開始;反之,click prev 的 function 就是讓 current index 不斷的 -1
,直到 0 的時候再從尾部開始:
const getIndexes = () => {
const prevIndex = currentIndex - 1 < 0 ? dataSource.length - 1 : currentIndex - 1;
const nextIndex = (currentIndex + 1) % dataSource.length;
return {
prevIndex, nextIndex,
};
};
const handleClickPrev = () => {
const { prevIndex } = getIndexes();
setCurrentIndex(prevIndex);
};
const handleClickNext = useCallback(() => {
const { nextIndex } = getIndexes();
setCurrentIndex(nextIndex);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentIndex]);
<ControlButtons>
<ArrowLeft onClick={handleClickPrev} />
<ArrowRight onClick={handleClickNext} />
</ControlButtons>
自動播放
自動播放當然就是要靠我們的 setInterval
了,我們每 3 秒就改變 current index 一次,改變的方式我們就直接呼叫上面做好的 handleClickNext
function 就可以了:
useEffect(() => {
let intervalId;
if (autoplay) {
intervalId = setInterval(() => {
handleClickNext();
}, 3000);
}
return () => {
clearInterval(intervalId);
};
}, [autoplay, handleClickNext]);
以上就是我們 Carousel 的重點整理啦!簡單展示一下成果:
Carousel 元件原始碼:
Source code
Storybook:
Carousel