星级评分的实现
星级评分是一种常见的打分方式,一般满分为5颗星,用户通过勾选星星的数量来给产品的某一维度进行打分,效果如下图所示:
当鼠标移动到某个星星上面时,当前星星及其前面的星星需处于激活状态;当鼠标移开时,所有星星恢复原状;只有当鼠标点击某个星星时,当前星星及其前面的星星处于激活状态,评分生效,此时鼠标移开时,星星状态不用恢复原状。
从上述分析可以看出,用到了鼠标进入、离开事件、以及点击事件,当移入到或点击某个星星时,需要确定出当前触发的是第几个星星,然后将当前星星及其前面的星星置为激活状态;离开星星时,如果之前没有触发点击事件,则需要将星星状态还原,具体实现如下:
原生js版
用html画出五个星星:
<div class="star-evaluation-wrapper">
<span class="star">★</span>
<span class="star">★</span>
<span class="star">★</span>
<span class="star">★</span>
<span class="star">★</span>
</div>
<div class="result"></div>
星星的样式:
star{
font-size: 18px;
color: #666;
}
.star.active{
color: #F78233;
}
评分逻辑:
const resultEle = document.querySelector('.result')
// 全局变量,用于存放用户的评分
let score = 0
const starWrapperEle = document.querySelector('.star-evaluation-wrapper')
const starEle = starWrapperEle.querySelectorAll('.star')
// 遍历每个星星元素,为每个星星添加索引、鼠标进入事件(onmouseenter)、以及点击事件
// 利用事件代理为星星容器添加鼠标移出事件(onmouseleave)
for (let i = 0; i < starEle.length; i++) {
// 添加索引
starEle[i].index = i
// 添加鼠标进入事件
starEle[i].onmouseenter = function (e) {
// 将鼠标移入到的当前星星以及其前面的星星设置为激活状态,剩余的恢复原状
for (let j = 0; j < starEle.length; j++) {
if (j > this.index) {
starEle[j].classList.remove('active')
} else {
starEle[j].classList.add('active')
}
}
// 将鼠标移入时的评分显示出来
resultEle.innerHTML = this.index + 1
}
// 通过事件代理给星星的容器添加鼠标移出事件
starWrapperEle.onmouseleave = function (e) {
// 鼠标移出时需判断全局变量score是否有值,有值的话鼠标离开时,需将score值对应的星星保持激活状态,否则将星星状态还原
for (let j = 0; j < starEle.length; j++) {
if (j > score - 1) {
starEle[j].classList.remove('active')
} else {
starEle[j].classList.add('active')
}
}
// 鼠标离开时将评分显示出来
resultEle.innerHTML = score
}
// 为每个星星添加点击事件
starEle[i].onclick = function (e) {
for (let j = 0; j < starEle.length; j++) {
// 点击某个星星时,需将该星星及其前面的星星置为激活状态
if (j > this.index) {
starEle[j].classList.remove('active')
} else {
starEle[j].classList.add('active')
}
}
// 保存评分
score = this.index + 1
// 将评分显示出来
resultEle.innerHTML = score
}
}
vue版
封装出的StarEvaluation组件:
<template>
<div class="star-evaluation-wrapper">
<div :class="['star-wrapper', {'read-only': readOnly}]">
<span :class="['star', {'active': value > 0}]">★</span>
<span :class="['star', {'active': value > 1}]">★</span>
<span :class="['star', {'active': value > 2}]">★</span>
<span :class="['star', {'active': value > 3}]">★</span>
<span :class="['star', {'active': value > 4}]">★</span>
</div>
</div>
</template>
<script>
export default {
name: 'star-evaluation',
props: {
value: {
type: Number,
default: 0,
},
readOnly: {
type: Boolean,
default: false,
},
},
data() {
return {
};
},
mounted() {
// 只读模式时不可以评分
if (!this.readOnly) {
this.evaluate();
}
},
methods: {
// 评分逻辑
evaluate() {
const starWrapperEle = this.$el.querySelector('.star-wrapper');
const starEle = starWrapperEle.querySelectorAll('.star');
for (let i = 0; i < starEle.length; i++) {
starEle[i].index = i;
starEle[i].onmouseenter = (e) => {
// 与原生js不一样,此处不可以用this表示当前遍历的星星,而是指向当前组件,因此用变量_this储存当前遍历的星星
const _this = starEle[i];
for (let j = 0; j < starEle.length; j++) {
if (j > _this.index) {
starEle[j].classList.remove('active');
} else {
starEle[j].classList.add('active');
}
}
};
starWrapperEle.onmouseleave = (e) => {
for (let j = 0; j < starEle.length; j++) {
if (j > this.value - 1) {
starEle[j].classList.remove('active');
} else {
starEle[j].classList.add('active');
}
}
};
starEle[i].onclick = (e) => {
const _this = starEle[i];
for (let j = 0; j < starEle.length; j++) {
if (j > _this.index) {
starEle[j].classList.remove('active');
} else {
starEle[j].classList.add('active');
}
}
this.$emit('input', _this.index + 1);
};
}
},
},
};
</script>
<style lang="scss" scoped>
.star-evaluation-wrapper{
.star-wrapper{
cursor: pointer;
&.read-only{
cursor: default;
}
.star{
margin-right: 8px;
font-size: 18px;
color: #666;
&.active{
color: #F46200;
}
}
}
}
</style>
StarEvaluation组件有两种模式,一种是只读模式,仅用于展示分数;另一种是可写模式,可以进行评分;使用方法如下:
// 可写模式
<star-evaluation v-model="score"></star-evaluation>
// 只读模式
<star-evaluation v-model="score" :read-only="true"></star-evaluation>