Talk is cheap. Show me your code

评分组件 Rate 的别样解法

各种偏门捷径实现如图所示的评分组件

 

一、低配版

const rate = (num) =>"★★★★★☆☆☆☆☆".substring(5 - num, 10 - num);

没错,只需一行代码就能实现评分(狗头)

虽然星星的样式一言难尽,也没有星星的交互,但这种实现方式你想到过吗?

 

 

二、标准版

rc-rate 的实现思路,ant-design 就是用的这个 rate 组件

首先实现单个星星 Star 组件

import React from 'react';

export default class Star extends React.Component {
  onHover = e => {
    const { onHover, index } = this.props;
    onHover(e, index);
  };

  onClick = e => {
    const { onClick, index } = this.props;
    onClick(e, index);
  };

  getClassName() {
    // 根据当前评分修改 star 的 class
    const { index, value } = this.props;
    const starValue = index + 1;
    let className = starValue <= value ? 'full' : 'zero';
    return className;
  }

  render() {
    const { onHover, onClick } = this;
    const { index, count, value, character } = this.props;
    // character 用于自定义星星图标
    const characterNode = typeof character === 'function' ? character(this.props) : character;
    const start = (
      <li className={this.getClassName()}>
        <div
          onClick={onClick}
          onMouseMove={onHover}
          role="radio"
          aria-checked={value > index ? 'true' : 'false'}
          aria-posinset={index + 1}
          aria-setsize={count}
        >
          {/* 如果要做半星,就把 characterNode 拆成两个 div */}
          {characterNode}
        </div>
      </li>
    );

    return start;
  }
}

在 Star 组件中,暴露出 onClick 和 onHover 事件

然后基于当前评分 value 和当前位置 index 来切换自身的 class,以实现普通状态和高亮状态

 

然后是 Rate 组件:

import React from 'react';
import Star from './star';

export default class Rate extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: undefined,
      hoverValue: undefined,
    };
  }

  // 鼠标移出组件时,清空 hoverValue
  onMouseLeave = () => {
    this.setState({
      hoverValue: undefined,
    });
  };

  onClick = (event, index) => {
    this.onMouseLeave();
    this.setState({
      value: index + 1,
    });
  };

  onHover = (event, index) => {
    this.setState({
      hoverValue: index + 1,
    });
  };


  render() {
    const {
      count,
      className,
      character,
    } = this.props;
    const { value, hoverValue } = this.state;
    const stars = [];

    // 根据当前 value 生成所有星星
    for (let index = 0; index < count; index += 1) {
      stars.push(
        <Star
          key={index}
          index={index}
          count={count}
          value={hoverValue || value}
          onClick={this.onClick}
          onHover={this.onHover}
          character={character}
        />,
      );
    }

    return (
      <ul
        className={className}
        onMouseLeave={this.onMouseLeave}
        role="radiogroup"
      >
        {stars}
      </ul>
    );
  }
}

在 Rate 组件中主要记录了实际评分 value 和 hover 状态下的临时评分 hoverValue

当鼠标移动的时候,以 hoverValue 渲染组件

当鼠标移出 Rate 组件时清空 hoverValue,以 value 渲染组件

 

 

三、青春版

这种方案以 CSS 为主,HTML 部分相当简单:

摊平了也就是一个简单的 div 包裹了几个 input-radio 元素,这些 radio 都添加了同一个 name

接下来就用神奇的 CSS 一步一步实现评分组件的交互

 

首先实现选中元素时的效果

/* less */
@color-full: coral;
@color-zero: #eee;

.rate {
  margin: 0;
  padding: 0;

  // 重置原本的 input-radio 样式
  input[name="rate"] {
    -webkit-appearance: none;
    border: none;
    outline: none;
    cursor: pointer;
    background: @color-zero;

    // 点击评分后的效果
    &:checked,
    // 鼠标移入的效果
    &:hover {
      background: @color-full;
    }
  }
}

通过 radio 选中时的 :checked 状态,可以修改其点击后的样式

然后再通过相邻元素选择器 ~ ,修改兄弟元素的样式

input[name="rate"] {
  // 点击评分后的效果
  &:checked,
  &:hover,
  // 兄弟元素的样式
  &:checked ~ input[name="rate"],
  &:hover ~ input[name="rate"] {
    background: @color-full;
  }
}

只是这时候的评分是反向的,没关系,用 flex-flow: row-reverse; 将元素反向排列即可

.rate {
  display: flex;
  flex-flow: row-reverse;
}

最后通过 data-set 给 radio 绑定值即可实现取值,不再赘述

 

 

参考资料:

《tiny-rate》

《react-component/rate》

《讲道理,仅3行核心css代码的rate评分组件,我被自己秀到头皮发麻》

posted @ 2020-12-22 11:49  Wise.Wrong  阅读(229)  评论(0编辑  收藏