【Day08】数据输入元件 - Rate

元件介绍

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


<<:  Day 6 - Search Insert Position

>>:  DAY 9 『 CollectionView 』Part2

[D02]k8s基本名词认识-01

写在前面 今天加班,所以等等把文章顺过之後再补上,先发文 今天加班,所以等等把文章顺过之後再补上,先...

规划

大家好, 因为工作不太常用到AI/ML, 所以我自身会想要去多看多了解, 才不会脱钩 想当初整整研究...

Day30 Redis架构实战-Redis Request Routing/效能监控与调教

Redis Request Routing 在Redis Server丛集中所有的操作透过Reque...

Day 23 (Js)

1.is 开头的大部分回传值是booling ex:isNaN() https://www.w3sc...

AE极光制作2-Day8

接续昨天的练习:https://ithelp.ithome.com.tw/articles/1026...