头图

星级展示应该是个挺常见的需求,刚好项目中有碰到。所以分享一下实现的过程,方便以后复用

一、需求分析

先看个常见的星级展示 UI

image.png

分析组件需要的参数

属性 含义默认值类型
value当前星级4.5Number
maxValue总星级5Number
activeColor星级颜色#ff7e28String

先看「当前星级」这个参数,考虑到有小数。所以不能用半星、全星之类的简单方法。
图示里的 4.3 表示前 4 个星星都完全展示,最后一个展示宽度的 0.3 。所以我们构建一个父子关系,让高亮部分的宽度,按父元素宽度的百分比展示即可

为了控制「星级颜色」,建议用 svg 或者 font-icon(用图片也可以,多加两个参数传 activeImageplaceholderImage 就行)

二、准备图标

先找个星星图标。我是在 iconfont 上找的。过程不赘述了,直接帖代码

/* icon-star.css */
@font-face {
  font-family: "iconfont";
  src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAALcAAsAAAAABogAAAKNAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGVgCCcAqBCIERATYCJAMICwYABCAFhGcHMhvFBci+QDaGA61+ohmjWdUpRTi3djdG3vv+AydFVwwi+NoP3Xd3AQDFIBMXDWjjomJJRrYKAHVlJ7a6QkUXbf2//7U2h1mHNAnaykXU02lemf9XdHawZqFapfEojUazRCPehY40QHws1yy4q+8HVWZx/WBKLK7PSs3l5Ym0eW581PWGkt85n+dyfAvogNYHsbtrbRprc9Q7oPJAOtDmtrEIe5En3jB2wQu8RKDRklUDm7vHLZg5dSSDOUPs3iBrjNNQOzDGo4oCC/VCbcPKIv5Dup0C7kSfj3/qMUZSk9ktOyc7Pry84IzJl9Wl5RuXy4WBrZExBxTioDG7ZVAwdlBjdsgIOFZj8FNVBR5xVKdgf51d3wyGYS74yZMftZDgsQaaAtYmtRBTte963PSzq+7WbZR++rG3cy+5z8/7ug/25y+g4Qk+6Mun4R/12ePXZ5/6/npxvuf7/kj6LfYsJntdN/3Irfrfx5TpT9GwDlCVHzMCwVs+PGb9sckCbrpfR+GqAsP81O/cG/wks4EdxVBsdyqa1NidTJ3c0OgkqNjBTt9TbV13VAz1uib46wxlyOqNEIWZQY0mK6hVbwONZu2sb9KlMChKAybcAIR2z0hafUPW7pUozBdq9OqjVrt/NDqKlt2aTIS3xeQI+eheYOjlmXKtEmltkD5JHK5LdYirECbQNqxyYY8y4jnmVKe6LqJQcZ7iLj2PkiTHgvOIPDECkWLRNFXTWwwvT2ELIw5BfMh1AYU8uYwK1KX4fQ1EO5Fw8ECik2WVEGpHNoM1gNgzZYPaHmSfyimtTggFKQzyKbTLh5FEIoeK5mER4hGGYEK6sMh01NRQk7G9Jf21fdDItqdwKrP6yzDTnhMAAAAA') format('woff2')
}

.iconfont {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.icon-star:before {
  content: "\e627";
}

新建一个 icon-star.css 文件,把代码帖进去。vue 文件引入之后,在任意标签上加两个属性,就可以展示出我们需要的「星星」了

image.png

<template>
    <div class="iconfont icon-star">星星</div>
</template>

<script>
import "./icon-star.css";
 
export default {}
</script>

三、代码编写

先实现一个可以展示指定宽度的星星

<template>
  <div>
    <div class="iconfont icon-star star-item star-placeholder">
      <div
        class="iconfont icon-star star-item star-active"
        :style="{ width: '60%' }"
      />
    </div>
    星星
  </div>
</template>

<script>
export default {};
</script>


<style scoped>
@font-face {
  font-family: "iconfont";
  src: url("data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAALcAAsAAAAABogAAAKNAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGVgCCcAqBCIERATYCJAMICwYABCAFhGcHMhvFBci+QDaGA61+ohmjWdUpRTi3djdG3vv+AydFVwwi+NoP3Xd3AQDFIBMXDWjjomJJRrYKAHVlJ7a6QkUXbf2//7U2h1mHNAnaykXU02lemf9XdHawZqFapfEojUazRCPehY40QHws1yy4q+8HVWZx/WBKLK7PSs3l5Ym0eW581PWGkt85n+dyfAvogNYHsbtrbRprc9Q7oPJAOtDmtrEIe5En3jB2wQu8RKDRklUDm7vHLZg5dSSDOUPs3iBrjNNQOzDGo4oCC/VCbcPKIv5Dup0C7kSfj3/qMUZSk9ktOyc7Pry84IzJl9Wl5RuXy4WBrZExBxTioDG7ZVAwdlBjdsgIOFZj8FNVBR5xVKdgf51d3wyGYS74yZMftZDgsQaaAtYmtRBTte963PSzq+7WbZR++rG3cy+5z8/7ug/25y+g4Qk+6Mun4R/12ePXZ5/6/npxvuf7/kj6LfYsJntdN/3Irfrfx5TpT9GwDlCVHzMCwVs+PGb9sckCbrpfR+GqAsP81O/cG/wks4EdxVBsdyqa1NidTJ3c0OgkqNjBTt9TbV13VAz1uib46wxlyOqNEIWZQY0mK6hVbwONZu2sb9KlMChKAybcAIR2z0hafUPW7pUozBdq9OqjVrt/NDqKlt2aTIS3xeQI+eheYOjlmXKtEmltkD5JHK5LdYirECbQNqxyYY8y4jnmVKe6LqJQcZ7iLj2PkiTHgvOIPDECkWLRNFXTWwwvT2ELIw5BfMh1AYU8uYwK1KX4fQ1EO5Fw8ECik2WVEGpHNoM1gNgzZYPaHmSfyimtTggFKQzyKbTLh5FEIoeK5mER4hGGYEK6sMh01NRQk7G9Jf21fdDItqdwKrP6yzDTnhMAAAAA")
    format("woff2");
}

.iconfont {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.icon-star:before {
  content: "\e627";
}

.star-item {
  display: inline-block;
  font-size: 40px;
}

.star-placeholder {
  position: relative;
}
.star-active {
  position: absolute;
  top: 0;
  left: 0;
  color: red;
  overflow: hidden;
}
</style>

image.png

再加个循环,并计算每个 star-item 该展示的宽度。最终代码如下:

<template>
  <div>
    <div
      v-for="index in maxValue"
      :key="index"
      class="iconfont icon-star star-item star-placeholder"
    >
      <div
        class="iconfont icon-star star-item star-active"
        :style="{ width: getWidth(index), color: activeColor }"
      />
    </div>
    <span class="star-value">
      {{ starValue }}
    </span>
  </div>
</template>

<script>
import "./icon-star.css";

export default {
  props: {
    maxValue: {
      type: Number,
      default: 5,
    },
    value: {
      type: Number,
      default: 4.5,
    },
    activeColor: {
      type: String,
      default: "#ff7e28",
    },
  },
  computed: {
    starValue() {
      return this.value.toFixed(1);
    },
  },
  methods: {
    getWidth(index) {
      const gap = this.value - index + 1;
      if (gap >= 1) {
        return "100%";
      } else if (gap <= 0) {
        return 0;
      } else {
        return gap * 100 + "%";
      }
    },
  },
};
</script>


<style scoped>
.star-item {
  display: inline-block;
  font-size: 40px;
}

.star-placeholder {
  position: relative;
  color: #e9e9e9;
}
.star-active {
  position: absolute;
  top: 0;
  left: 0;
  overflow: hidden;
}
.star-value {
  font-size: 40px;
  margin-left: 10px;
}
</style>

image.png

四、效果展示

  <star-level :value="4.1" />
  <star-level :value="3" activeColor="blue" />
  <star-level :value="8.8" :maxValue="10" activeColor="green" />

五、选择星级

既然展示都做出来了,不如再稍微改改,做个选择星级的组件

思路就是判断鼠标在当前星级的位置,并结合这是第几颗星,计算出 value 值

<template>
  <div>
    <div
      v-for="index in maxValue"
      :key="index"
      @mousemove="mousemoveStarItem($event, index)"
      class="iconfont icon-star star-item star-placeholder"
    >
      <div
        class="iconfont icon-star star-item star-active"
        :style="{ width: getWidth(index), color: activeColor }"
      />
    </div>
    <span class="star-value" :style="{ color: activeColor }">
      {{ starValue }}
    </span>
  </div>
</template>

<script>
import "./icon-star.css";

export default {
  data() {
    return {
      value: 0,
    };
  },
  props: {
    maxValue: {
      type: Number,
      default: 5,
    },
    activeColor: {
      type: String,
      default: "#ff7e28",
    },
  },
  computed: {
    starValue() {
      return this.value.toFixed(1);
    },
  },
  methods: {
    getWidth(index) {
      const gap = this.value - index + 1;
      if (gap >= 1) {
        return "100%";
      } else if (gap <= 0) {
        return 0;
      } else {
        return gap * 100 + "%";
      }
    },
    mousemoveStarItem(e, index) {
      const width = e.currentTarget.offsetWidth;
      this.value = index + e.offsetX / width - 1;
    },
  },
};
</script>


<style scoped>
.star-item {
  display: inline-block;
  font-size: 40px;
}

.star-placeholder {
  cursor: pointer;
  position: relative;
  color: #e9e9e9;
}
.star-active {
  position: absolute;
  top: 0;
  left: 0;
  overflow: hidden;
}
.star-value {
  font-size: 40px;
  margin-left: 10px;
}
</style>

附录:

CodeSandBox 代码示例:https://codesandbox.io/s/star...


QCY_
22 声望1 粉丝