Rate
是一個評分元件。一方面可以對於評價的數據展示,另一方面可以讓人進行對評分的操作。
因為 MUI 目前的版本還沒有 Rate 元件,因此我們這邊先只參考 Antd 的元件。
count
雖然我們平常常見的評分都是五顆星星,但這邊給了這個 count 的參數讓我們不被限制於只能五顆星星,我覺得還蠻不錯的,這邊的預設也是五顆星。
allowHalf
這個參數允許我們選擇半顆星星,而且他很酷的是,他允許我們只 hover 一半的星星,到底是怎麼做到的呢?這邊來看一下他的 html 結構:
另外,我們這邊發現 ant-rate-star-first 這個 div 節點有一個關鍵的 css 屬性,就是 width: 50%;
,如下圖所示:
透過上述的結構,我們可以大概猜出實作 hover 一半星星的邏輯,首先我們要準備 active 星星全顆,還有 inactive 星星全顆。
接著,在初始狀態,我們先把 width: 50%;
的 active 星星完全重疊在 inactive 星星上面(就是圖中的 star-first div),這邊是透過 position: absolute;
屬性來實作重疊的效果,並且讓 active 星星先隱藏,這邊隱藏的方式是把 opacity 變成 0。
當我們 hover 在左半邊的 star-first div 上面時,就讓 active 的星星顯現(opacity: 1;
),並且保持 width: 50%;
,這樣看起來就是半顆星 active 的效果;當我們 hover 在右半邊的 star-second div 上面時,我們就改變被疊在下面的星星的顏色,從灰色變成黃色,也就是從 inactive 變成 active,這樣看起來就會變成一顆全星的效果。
disabled
設定了 disabled 為 true 時, Rate 就不能夠讓使用者操作,變成已讀的狀態,也就是純展示的功能。
character
character 屬性也是讓我蠻驚艷的屬性之一,就是他做到可以替換 Rate 字符,言外之意就是你不一定要被他限制住是星星圖案,你也可以是愛心,甚至也可以是文字,我覺得這個非常的酷,因為上面我們解析他的半顆 hover 功能是透過改變 color 來實現,感覺傳入的屬性也是需要能夠支援 color 可以被改變,因此這邊文件寫說限定的型別就是 ReactNode,從範例中也可以看出,他支援 icon 以及文字的傳入。
屬性 | 說明 | 類型 | 默認值 |
---|---|---|---|
count | star 總數 | number | 5 |
allowHalf | 是否允許半顆星星 | boolean | false |
disabled | 是否能進行交互 | boolean | false |
defaultValue | 預設分數 | number | |
themeColor | 主題顏色 | number | |
size | star 大小 | number | 32 |
character | 自定義字符 | ReactNode , String |
簡化來看的話,我們 Rate 整體邏輯架構概念回如下,由一個 <RateWrapper />
的根節點包覆住整個元件,並且也由這個跟節點決定內部元件佈局排版,以 Rate 為例,應該是 row 方向的佈局,因此我們可以用 flex 來實現。
我們決定 star character 總數的 props 是 count
,因此由下面程式碼範例,我們傳入的 count
為多少,就能夠產生長度為多少的陣列:
<RateWrapper>
{
[...Array(count).keys()].map((itemKey) => (
<Character key={itemKey} />
))
}
</RateWrapper>
接著我們來實現 Character
,我們預設的 Character 是星星 <StarIcon />
,由先前元件分析可知,要做到能夠允許選取半顆星星,需要兩個元件 <CharacterFirst />
和 <CharacterSecond />
一起來搭配才能實現:
<RateWrapper>
{
[...Array(count).keys()].map((itemKey) => (
<CharacterWrapper key={itemKey}>
<CharacterFirst>{character}</CharacterFirst>
<CharacterSecond>{character}</CharacterSecond>
</CharacterWrapper>
))
}
</RateWrapper>
佈局上,為了實現半星選取,<CharacterFirst />
必須要設為 position: absolute;
,如此 <CharacterFirst />
和 <CharacterSecond />
才能夠重疊,並且 <CharacterFirst />
的 width 需要設為 50% 來表示半星。
當我們不需要伴星選取的時候,只需要隱藏 <CharacterFirst />
就能夠做到了。
const CharacterFirst = styled.div`
position: absolute;
color: ${(props) => (props.$isActive ? props.$starColor : '#F0F0F0')};
width: 50%;
overflow: hidden;
cursor: pointer;
`;
佈局完成之後,接著我們要做的是 hover 的時候能夠預覽選取樣式,因此 hover 到哪裡就要 active 到哪裡,當滑鼠移開的時候,則回覆到原本選取狀態
因此我們需要一個 state 用來記錄預覽狀態,另一個 state 則是用來記錄實際上的選取狀態:
const [innerValue, setInnerValue] = useState(defaultValue);
const [previewValue, setPreviewValue] = useState(innerValue);
當滑鼠 hover 上去的時候,我們呼叫 onMouseOver
事件,若 hover 在 <CharacterFirst />
表示半星,所以要 +0.5
;若 hover 在 <CharacterSecond />
表示全顆星,所以要 +1
。
<CharacterWrapper key={itemKey}>
<CharacterFirst
className="rate__character-first"
$starColor={starColor}
$isActive={itemKey + 0.5 <= previewValue}
onMouseOver={() => handleChangePreviewValue(itemKey + 0.5)}
onMouseLeave={() => handleChangePreviewValue(innerValue)}
onClick={() => handleOnClick(itemKey + 0.5)}
>
{character}
</CharacterFirst>
<CharacterSecond
className="rate__character-second"
$starColor={starColor}
$isActive={itemKey + 1 <= previewValue}
onMouseOver={() => handleChangePreviewValue(itemKey + 1)}
onMouseLeave={() => handleChangePreviewValue(innerValue)}
onClick={() => handleOnClick(itemKey + 1)}
>
{character}
</CharacterSecond>
</CharacterWrapper>
當滑鼠移開的時候,則透過 onMouseLeave
事件來改變 previewValue
,設定回原本該有的值:
const handleChangePreviewValue = (currentValue) => {
if (!isDisabled) {
setPreviewValue(currentValue);
}
};
onClick
事件則是確定選取的時候呼叫,因此要改變 innerValue
,那如果 onClick
的時候我們發現選取值與原本的值一樣,則表示他想要取消選擇,此時我們將 innerValue
設為 0:
const handleOnClick = (clickedValue) => {
if (isDisabled) return;
setInnerValue((previousValue) => (previousValue === clickedValue ? 0 : clickedValue));
};
那到目前為止,關於 Rate 主要的關鍵功能就都完成了!透過以上的方法,我們藉由 props 來改變 character
也不會是難事了:
Rate 元件原始碼:
Source code
Storybook:
Rate