仿一個掘金APP會員等級輪播
· 閱讀時間約 5 分鐘
使用的是我開源庫 bear-react-carousel
主要試試看能做到什麼程度,之前公司也有類似的需求
廢話不多說,直接開始
需求
首先我們先看掘金在APP端的預覽結果
- 可滑動的部分只有 卡片的部分
- 並且在滑動時等級卡片會往上移動
- 等級卡片顯示為置中為 選取項目
- 等級卡下方需要被遮住(圓弧),不顯示完全
- 等級名稱項目需同步移動
- 等級名稱項目移動時圓弧移動
- 等級名稱線需同步移動
- API 撈回時,預設需要移動到選取的等級(無動畫移動)
开发輪播項目
輪播項目可切分為:
- 等級卡片,顯示1.2個並且置
<BearCarousel
slidesPerView={1.2}
spaceBetween={20}
isCenteredSlides={true}
// ...ignore
/>
- 等級名稱
- 水平線 兩者都應顯示 3 並居中。
<BearCarousel
slidesPerView={3}
isCenteredSlides={true}
// ...ignore
/>
所以開發目標是在移動等級卡片
時同步等級名稱
和等級線
的移動。
import React, {useCallback, useEffect, useRef, useState} from 'react';
import BearCarousel, {TMoveEffectFn, TBearSlideItemDataList, BearSlideCard, elClassName, Controller, TOnSlideChange} from 'bear-react-carousel';
const MemberLevelWrapper = () => {
const carouselMainRef = useRef<BearCarousel>(null);
const carouselMetaRef = useRef<BearCarousel>(null);
const carouselLineRef = useRef<BearCarousel>(null);
return <>
{/* Level Card */}
<BearCarousel
ref={carouselMainRef}
syncCarouselRefs={[carouselMetaRef, carouselLineRef]}
slidesPerView={1.2}
spaceBetween={20}
isCenteredSlides={true}
// ...ignore
/>
{/* Level Name*/}
<BearCarousel
ref={carouselMetaRef}
// ...ignore
/>
{/* Level Line */}
<BearCarousel
ref={carouselLineRef}
// ...ignore
/>
</>;
};
接下來,滾動時,等級卡片會向上移動,我們需要實現一個新的動畫效果功能
import BearCarousel, {TMoveEffectFn} from 'bear-react-carousel';
const mainMoveEffectFn: TMoveEffectFn = useCallback((percentageInfo) => {
const transformY = 40;
return {
transform: `translate(0px, ${-transformY * (percentageInfo.calcPercentage - 1)}px)`,
};
}, []);
<BearCarousel
ref={carouselMainRef}
syncCarouselRefs={[carouselMetaRef, carouselLineRef]}
moveEffect={{
moveFn: mainMoveEffectFn,
}}
// ...ignore
/>
所以開發目標是 移動等級卡時,同步移動 等級名稱與等級線
const levelNameMoveEffectFn: TMoveEffectFn = useCallback((percentageInfo) => {
const transformY = -19;
return {
transform: `translate(0px, ${-transformY * (percentageInfo.calcPercentage - 1)}px)`,
};
}, []);
<BearCarousel
ref={carouselMainRef}
moveEffect={{
moveFn: levelNameMoveEffectFn,
}}
// ...ignore
/>
至於圓弧的部分,我們最後使用遮罩的方式來顯示,並且關閉 NavButton、Pagination、MouseMove,畫SVG不熟的話,可以使用這個工具
<LevelLine>
<LineBearCarousel
ref={carouselLineRef}
data={lineData}
slidesPerView={3}
isCenteredSlides={true}
isEnableNavButton={false}
isEnablePagination={false}
isEnableMouseMove={false}
/>
<svg height="100%" width="100%">
<clipPath id="wave12">
{/*跟隨線*/}
<path d="M 0 4 C 175 30 175 30 356 4 L 356 2 C 175 28 175 28 0 2" stroke="black" fill="transparent"/>
</clipPath>
</svg>
</LevelLine>
而 Level Card 遮罩的部分一樣,但我用了3塊
<svg height="100%" width="100%">
<clipPath id="wave10">
<path d="M 0,0 356,0 356,130 0,130" stroke="black" fill="transparent"/>
{/* 圓弧 */}
<path d="M 0 130 C 175 155 175 155 356 130" stroke="black" fill="transparent"/>
{/* 下箭頭 */}
<path d="M 152 143 L 176 153 L 178 153 L 202 143" stroke="black" fill="transparent"/>
</clipPath>
</svg>
最後就是 預設選取等級的部分(無動畫方式移動)
const [carouselMainController, setMainController] = useState<Controller>();
const [currLevel, setCurrLevel] = useState<{lv: number,count: number}|undefined>();
useEffect(() => {
carouselMainController?.slideToPage(5, false);
}, [carouselMainController]);
<BearCarousel
ref={carouselMainRef}
syncCarouselRefs={[carouselMetaRef]}
onSlideChange={handleSlideChange}
setController={setMainController}
slidesPerView={1.2}
spaceBetween={20}
isCenteredSlides={true}
// ...ignore
/>
完成結果
以上就完成了,基本上如果兩邊都需要移動的話,互相同步在(bear-react-carousel)[https://bear-react-carousel.pages.dev] 因為是進行獨立的同步控制,所以不會循環互控,但也因為這樣所以目前是無法A控制B , B 自動再控制C,所以在這邊可以發現是A控制B和C。