圖片說明:
public:專門放圖片、照片
src底下放
component管理元件
config管理設定
hooks管理邏輯
//在 component 底下的 carousel.js 檔案(定位為專門處理標籤區)
import positionArray from '../../config/positionArray';// positionArray 檔案定位等同於旗杆位置(資料設計成陣列再包陣列,下圖一)
import prizeArray from '../../config/prizeArray';//prizeArray 檔案定位等同於標籤資料(資料設計成陣列再包物件,下圖二)
import useCarousel from '../../hooks/useCarousel.js';
//把 hooks 資料夾下的 useCarousel 引入,在下面取得 prize的資料
import {
ItemContainer,
FirstMedal,
Item,
Percentage,
Percent,
CarouselContainer,
CarouselItems,
} from './style';//引入 css
export function CarouselItem({
//把會大量重複的 component 獨立出來
//這裡其實取到得值是從 prizeArray (圖二)設計好的資料,
把 key 傳入,傳輸順序是 prizeArray => Carousel => CarouselItem
percent,
picture,
medal,
top,
left,
width,
height,
opacity,
}) {
return (
//因為 這裡的參數 要傳給 css 那邊利用 props 接
<ItemContainer $top={top} $left={left}>
<FirstMedal $medal={medal}></FirstMedal>
<Item
//因為 這裡的參數 要傳給 css 那邊利用 props 接
$picture={picture}
$width={width}
$height={height}
$opacity={opacity}
>
<Percentage>
//因為 這裡的參數 只要顯示出來 所以直接接在標籤
<Percent>{percent}%</Percent>
</Percentage>
</Item>
</ItemContainer>
);
}
export default function Carousel() {
const { prizes, startMove } = useCarousel();
//從 hook (useCarousel)拿 prizes 的資料
//資料來源 prizeArray => prizes => useCarousel
useEffect(() => {
startMove(0, {
left: positionArray[1][0],
//key是left陣列第一個的第0個值 => 276
});
}, []);
useEffect(() => {
console.log(prizes);
}, []);
return (
<>
<CarouselContainer>
<CarouselItems>
//利用.map 條列是渲染到畫面上
{prizes.map((prize) => {
return (
<CarouselItem
key={prize.id}//記得要給 key,通常都是設id為key
picture={prize.picture}//利用.物件取值
left={prize.left}
top={prize.top}
zindex={prize.zindex}
width={prize.width}
height={prize.height}
opacity={prize.opacity}
medal={prize.medal}
percent={prize.percent}
/>
);
})}
</CarouselItems>
</CarouselContainer>
</>
);
}
- positionArray 檔案定位等同於旗杆位置(資料設計成陣列再包陣列,下圖一)
const positionArray = [
[133, -177, '0', 129, 123, 0.4],
[276, -72, '2', 138, 124, 0.6],
];
export default positionArray;
- prizeArray 檔案定位等同於標籤資料(資料設計成陣列再包物件,下圖二)
const prizeArray = [
{
id: 1,
picture: 1,
left: 133,
top: -177,
zindex: '0',
width: 129,
height: 123,
opacity: 0.4,
medal: null,
percent: 21,
},
{
id: 2,
picture: 2,
left: 276,
top: -72,
zindex: '2',
width: 138,
height: 124,
opacity: 0.6,
medal: null,
percent: 21,
},
];
export default prizeArray;
import { useState, useEffect } from 'react';
import positionArray from '../config/positionArray';
import prizeArray from '../config/prizeArray';
export default function useCarousel() {
const [prizes, setPrizes] = useState(prizeArray);
//把 prizeArray 的資料傳進來給 prizes 用
const startMove = (target, json) => {
//寫 startMove function
//target 代表獎品標籤,在陣列第幾個,以數字表示
//json 代表獎品 position
clearInterval(prizes[target].timer);
//.timer類似計數器的概念 是跟 setInterval 配合著使用
//為了防止 setInterval 重新開始跑的時候,
但前面的還沒跑完又開始會壞掉,才設定先停止,讓後面開始時,不會壞掉
prizes[target].timer = setInterval(function () {
var intervalContinue = false;//預設標籤還沒到指定位子
for (var attr in json) {
//var attr in json ( json 物件裡面的 key 值依序賦值到 attr)
//attr 表示 prizeArray => 陣列底下 => 物件底下 => key的值
//ex: prizeArray[1]['left'] => left 值等於 276
setPrizes((prizes) => {
//寫一個 setPrizes 的非同步 function 裡面包各種 if else 條件所回傳的情況,
//裡面接 prizes 參數,因為上面的回圈跑得很快,
//下面又是非同步的 function,
//為了讓每次回圈跑完 prizes 的資料,進到非同步裡是正確的,
//避免忽略前一圈 setPrize 的改變,且每次 cur 都要用到新的值
//所以這裡才要先接 prizes 確保接到前一圈改動完的資料,
//例如:上面的回圈是跑 left ,非同步的 function也是接到 改完的 left 的值
let cur;
/* 抓取調整前的數值 cur */
if (attr === 'opacity') {
cur = Math.round(parseFloat(prizes[target] [`${attr}`]) * 100);
} else {
cur = prizes[target][`${attr}`];
}
console.log(`調整前的 ${attr} :`, cur);
/* 計算調整的幅度 speed */
let speed =
attr === 'opacity'
? (json[attr] * 100 - cur) / 6
: (json[attr] - cur) / 6;
speed = speed > 0
? Math.ceil(speed) : Math.floor(speed);
console.log('調整幅度 speed:', speed);
/* 判斷是否已經調整到目標位置 */
if (attr === 'opacity' && cur / 100 !== json[attr]) intervalContinue = true;
//標籤還沒到指定位子就繼續
if (attr !== 'opacity' && cur !== json[attr]) intervalContinue = true;
//標籤還沒到指定位子就繼續
/* 開始進行調整 */
console.log(`調整後的 ${attr} :`,
attr === 'opacity'
? (cur + speed) / 100
: cur + speed
);
return prizes.map((item, prizeIndex) => {
//利用 .map 遍歷 prizes下面的物件,接收每一圈 item, prizeIndex 的值
if (prizeIndex !== index) return item;
//判斷 prizeIndex 的圖,是否不等於,這一圈 startMove 對應要改變的對象,如果不是就回傳原本的樣子
return {
...item,//展開
[attr]: attr === 'opacity' ? (cur + speed) / 100 : cur + speed,//三元運算值判斷 ture 或 false
};
});
if (continue) {
clearInterval(prizes[target].timer);
}
}, 0);
};
return {
prizes,
startMove,
};
}
移動修正:多個動畫移動殘障問題
原本:
原本把遍歷 position 的回圈寫在 setPrize 外,每一次的回圈跑完都會進行一次 setPrize 調整好一張圖 key 的值
慢慢移動到他指定的位子
由於每次setPrize都會等待上一圈改動
每一圈都要等前一圖調好,下一個圖調整,造成動畫移動 lag 的問題,解決方法:降低 setPrize 執行的次數,從每次一個一個 key 慢慢改,變成五個 key 全部調完再一次 setPrize
改動:
寫一個 tempObject 放每一圈回圈跑完的 key 值
把 for 回圈改在 setPrizes 裡面
讓每一圈所有圖片的 key 值 都一起改動
最後再把每一圈改變每個圖的 key 值資料
全部 return 出去給 setPrizes
import { useState, useEffect } from 'react';
import positionArray from '../config/positionArray';
import prizeArray from '../config/prizeArray';
export default function useCarousel() {
const [prizes, setPrizes] = useState(prizeArray);
const startMove = (index, json) => {
clearInterval(prizes[index].timer);
prizes[index].timer = setInterval(function () {
console.log('===============分隔線=================');
var intervalContinue = false;
setPrizes((prizes) => {
let tempObject = prizes[index];
//寫一個 tempObject 放每一圈回圈跑完的 key 值
//把 for 回圈改在 setPrizes 裡面
console.log(tempObject);
for (let attr in json) {
let cur;
/* 抓取調整前的數值 cur */
if (attr === 'opacity') {
cur = Math.round(parseFloat(tempObject[`${attr}`]) * 100);
} else {
cur = tempObject[`${attr}`];
}
console.log(`調整前的 ${attr} :`, cur);
/* 計算調整的幅度 speed */
let speed =
attr === 'opacity'
? (json[attr] * 100 - cur) / 1.5
: (json[attr] - cur) / 1.5;
speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
console.log('調整幅度 speed:', speed);
/* 判斷是否已經調整到目標位置 */
if (attr === 'opacity' && cur / 100 !== json[attr])
intervalContinue = true;
if (attr !== 'opacity' && cur !== json[attr])
intervalContinue = true;
/* 開始進行調整 */
console.log(
`調整後的 ${attr} :`,
attr === 'opacity' ? (cur + speed) / 100 : cur + speed
);
tempObject = {
//把每個回圈展開更新的資料賦值到 tempObject
...tempObject,
[attr]: attr === 'opacity' ? (cur + speed) / 100 : cur + speed,
};
}
return prizes.map((item, prizeIndex) => {
//回圈結束後,用 .map 遍歷 prizes下面的物件,接收每一圈 item, prizeIndex 的值
if (prizeIndex !== index) return item;
//判斷 prizeIndex 的圖,是否不等於,這一圈 startMove 對應要改變的對象,如果不是就回傳原本的樣子
return tempObject;
//判斷 prizeIndex 的圖,是否不等於,這一圈 startMove 對應要改變的對象,如果是就回傳 tempObject 每一圈跑完的資料
});
});
console.log('intervalContinue: ', intervalContinue);
if (!intervalContinue) {
clearInterval(prizes[index].timer);
}
}, 0);
};
return {
prizes,
startMove,
};
}
- 增加功能:
- 游標 hover 到圖片,輪播圖停止轉動
- 往前 往後 按鈕
- 自動輪播功能
export default function useCarousel() {
const [prizes, setPrizes] = useState(prizeArray);
const [movePosition, setMovePosition] = useState(positionArray);
//為了 button 寫一個資料會動的 Position 資料 => positionArray
//因為 react 寫在 config 的資料,基本上是希望資料是不會變更的
//所以另外複製一份 positionArry 資料,讓 movePosition 使用
const [isHover, setIsHover] = useState(false);
//製作功能:游標 hover 到圖片,輪播圖停止
//預設 isHover 為 false
const handleClickPreButton = () => {
//事件監聽
setMovePosition((movePosition) =>
[movePosition[6], ...movePosition].filter((_, index) => index !== 7)
//先把最後一個放到第一個
//接著按順序排列
//利用.filter篩選出不是第七個的都留起來
);
};
const handleClickNextButton = () => {
//事件監聽
setMovePosition((movePosition) =>
[...movePosition, movePosition[0]].filter((_, index) => index !== 0)
//按順序排列
//先把第一個放到最後一個
//利用.filter篩選出不是第0個的都留起來
);
};
const startMove = useCallback((index, json) => {
clearInterval(prizes[index].timer);
prizes[index].timer = setInterval(function () {
var intervalContinue = false;
setPrizes((prizes) => {
let tempObject = prizes[index];
for (let attr in json) {
let cur;
/* 抓取調整前的數值 cur */
if (attr === 'opacity') {
cur = Math.round(parseFloat(tempObject[`${attr}`]) * 100);
} else {
cur = tempObject[`${attr}`];
}
/* 計算調整的幅度 speed */
let speed =
attr === 'opacity'
? (json[attr] * 100 - cur) / 1.5
: (json[attr] - cur) / 1.5;
speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
/* 判斷是否已經調整到目標位置 */
if (attr === 'opacity' && cur / 100 !== json[attr])
intervalContinue = true;
if (attr !== 'opacity' && cur !== json[attr]) intervalContinue = true;
tempObject = {
...tempObject,
[attr]: attr === 'opacity' ? (cur + speed) / 100 : cur + speed,
};
}
return prizes.map((item, prizeIndex) => {
if (prizeIndex !== index) return item;
return tempObject;
});
});
if (!intervalContinue) {
clearInterval(prizes[index].timer);
}
}, 0);
}, []);
useEffect(() => {
for (let i = 0; i < 7; i++) {
//因為共6張圖,故寫一個回圈跑7次
let j = i === 6 ? 0 : i + 1;
//當 i === 6 就讓 i = 0
//當 i !== 6 就讓 i = i+1
//把結果賦值給 j
startMove(i, {
left: movePosition[j][0],
top: movePosition[j][1],
width: movePosition[j][2],
height: movePosition[j][3],
opacity: movePosition[j][4],
});
}
}, [movePosition, startMove]);
return {
prizes,
movePosition,
isHover,
setIsHover,
setMovePosition,
startMove,
handleClickNextButton,
handleClickPreButton,
};
}
export default function Carousel() {
let moveTimer = useRef();
//把 useRef() 賦值到變數 moveTimer 上
//useRef() 建立出來的變數在被改變時並不會觸發 re-render,
//也就表示當數值變化後並無法即時呈現在畫面中
//useRef 只會回傳一個值,這個值是一個有 current屬性 的 Object 。
const {
prizes,
isHover,
setIsHover,
handleClickPreButton,
handleClickNextButton,
} = useCarousel();
//把 hook 資料引進來的方式
useEffect(() => {
//偵測到 isHover 資料改變時,
if (isHover) clearInterval(moveTimer.current);
//判斷 isHover === true,使用 瀏覽器提供的 clearInterval,
//用 .currn 拿到物件裡的資料
if (!isHover) moveTimer.current = setInterval(handleClickNextButton, 2000);
//判斷 isHover === false,
//使用 瀏覽器提供的 setInterval,
//讓 handleClickNextButton 每隔兩秒觸發一次(形成圖片自動輪播狀態)
}, [isHover]);
return (
<>
<CarouselContainer>
<CarouselItems
onMouseOver={() => {
//react 提供的事件監聽指令
//監聽滑鼠有沒有 hover 在元件的上方
setIsHover(true);
//如果滑鼠 hover 在元件的上方 setIsHover(true) 改成 true
}}
onMouseLeave={() => {
//react 提供的事件監聽指令
//監聽滑鼠有沒有離開 hover 元件
setIsHover(false);
//如果滑鼠 hover 在元件的上方 setIsHover(false) 改成 false
}}
>
{prizes.map((prize) => {
return (
<CarouselItem
key={prize.id}
picture={prize.picture}
left={prize.left}
top={prize.top}
width={prize.width}
height={prize.height}
opacity={prize.opacity}
medal={prize.medal}
percent={prize.percent}
/>
);
})}
</CarouselItems>
<CarouselButton>
//在標籤上加上聽器
<CarouselPrevButton onClick={handleClickPreButton} />
//在標籤上加上聽器
<CarouselNextButton onClick={handleClickNextButton} />
</CarouselButton>
</CarouselContainer>
</>
);
}