Episode 13: implement a set of UI component library of Vue on PC from scratch (scoring component little star)

Time:2020-3-8

Episode 13: implement a set of UI component library of Vue on PC from scratch (scoring component little star)

1. Positioning of this episode

Speaking of scoring, I first saw that this form was movie website. Each movie got several stars. Later, there was a way for users to manually select stars for scoring. These methods are more intuitive and attract users to participate. In fact, there are many ways to play this component. Compared with loading animation, I can use the continuous lighting of stars as a mapping of loading progress, Many UI libraries of this component make it very fixed. For example, it can only be five stars. When writing this component, my principle is that the number of stars can be as many as you want, and of course, as few as you want, at least one, and at most unlimited. Isn’t it very interesting A kind of .
     Implementation ideas
Because I use SVG to implement the icon component here. At last, I choose to use two rows of the same icon components, overlap them, and then change the width of the top layer to achieve the effect of color in the selection area

2. Requirements review

  1. Read only mode: display only
  2. Selection mode: you can click to set a new score, which will change when you mouse over
  3. The color and size should be set by the user
  4. Feature: the total number of ‘stars’ can be set
  5. The full score can be set
  6. Replaceable graphics, definitely more than ‘stars’
  7. To be compatible with multiple parent components, as well as multiple parent and parent component rolling offset
  8. You can choose one star at a time

3. Foundation construction

First of all, I’ll take a picture of the normal state
Episode 13: implement a set of UI component library of Vue on PC from scratch (scoring component little star)
vue-cc-ui/src/components/Rate/index.js

import Rate from './main/rate.vue'

Rate.install = function(Vue) {
  Vue.component(Rate.name, Rate);
};

export default Rate

vue-cc-ui/src/components/Rate/main/rate.vue

<template>
  <div class='cc-rate'
       :style="{ 
         Cursor: disabled? 'auto': 'pointer', // it is unnecessary to modify the status 
       }"
>
    <i class='cc-rate__box'>
      <span class='cc-rate__dark'>
        <cc-icon v-for='item in num'
                 :key='item'
                 :size="`${size}px`"
                 :name='iconType.name' />
      </span>
      <span class='cc-rate__bright'
            :style="{ width }">
        <cc-icon v-for='item in num'
                 :key='item'
                 :size="`${size}px`"
                 :name='iconType.name' />
      </span>
    </i>
  </div>
</template>

The num above refers to the number of stars defined by the user
Total is the score when the stars are full

props: {
Disabled: Boolean, // when it is true, modification is not allowed
  num: {
      //Show me some stars
      type: Number,
      default: 5 
    },
  total: {
      //What's the total score
      type: Number,
      default: 5
    },
  size: {
      //The size of the stars
      type: Number,
      default: 20
    },
  value: {
     //Current score
      type: [Number, String],
      required: true
    }
}

Add dynamic style to CC icon so that its width changes with the position of my mouse

<span class='cc-rate__bright'
            :style="{ width }">
            // ...
      </span>

Calculate how to display

methods: {
//This.value is the user bound v-mode
  boundary(value) {
       //Less than the boundary is the minimum, greater than the boundary is the maximum
      if (value <= 0) value = 0;
      if (value >= this.numTotal) value = this.numTotal;
      return value;
    }
},
computed: {
  width() {
      //Current value to be displayed, proportion of relative total score!
      let proportion = this.boundary(this.value) / this.numTotal;
      //Use this ratio to calculate the width corresponding to the total width
      return `${proportion * (this.size * this.num)}px`;
    }
 numTotal() {
    //Is the total score passed in valid
    //If it's less than 0, the total score is based on the number of stars
      return this.total <= 0 ? this.num : this.total;
  },
 }

The above code can implement a simple scoring component to show
So let’s do some articles on icon style

<span class='cc-rate__dark'>
        <cc-icon v-for='item in num'
                 :key='item'
                 :size="`${size}px`"
                 :name='iconType.name'
                 :color='iconType.darkColor' />
      </span>
      <span class='cc-rate__bright'
            :style="{ width }">
        <cc-icon v-for='item in num'
                 :key='item'
                 :name='iconType.name'
                 :size="`${size}px`"
                 :color='iconType.brightColor' />
      </span>

You will notice that the icontype keyword is very important, which determines what an icon looks like

props: {
  Type: string, // custom icon
  Darkcolor: string, // custom dark color
  Brightcolor: string, // custom light color
}
computed: {
//Icon style
    iconType() {
    //Temporarily useful properties are defined as
      let { type, darkColor, brightColor } = this,
      //Default style cannot be less
        result = {
          name: type || "cc-stars2",
          brightColor:brightColor || "rgb(247, 186, 42)",
          darkColor: darkColor || "#bbbbbb"
        };
      return result;
    }
}

If you select the dynamic loading icon, it can also rotate
Episode 13: implement a set of UI component library of Vue on PC from scratch (scoring component little star)

4. Interaction with mouse

Talking about mouse movement
Where the mouse moves, the star’s selection score will follow

1. Onmousesave, onmousenter. When the mouse enters the specified element area, the event is triggered. Bubbling is not supported, and the area does not contain child elements.

2. Onmouseout, OnMouseOver, and mouse enter the specified element trigger event, including the sub element area.

 <i ref='box'
       class='cc-rate__box'
       @mouseleave='handelMouseleave()'
       @mousemove='handelMousemove($event)'>

Handelmousemove compares the core code (new calculations will also be added, which will be discussed later)

handelMousemove(e) {
      //The reason for real-time judgment is that the user may not be allowed to change now, but not later!!
      if (!this.disabled) {
        //Get dom of I tag
        let node = this.$refs.box;
        //Gethtmlscroll is a previously encapsulated method to get distance (the next set introduces the new method)
        //The distance between the mouse and the left side, minus the distance between the element and the left side, is the distance between the mouse and the left side of the element
        //The size of the current icon * the total number of icons, that is, the width of the total icons
        //Convert the above proportion to the score in the total score;
        let value =
          ((e.pageX - getHTMLScroll(node).left) / (this.size * this.num)) *
          this.numTotal;
        //Check the value;
        value = this.boundary(value);
          this.$emit("input", value);
      }
    },

Leaving function

data() {
    return {
      oldVal: 0
    };
  },
methods: {
  handelMouseleave() {
      if (!this.disabled) {
        this.$emit("input", this.oldVal);
      }
    },
}

oldVal
This variable is mentioned above. Let me give you an example
Last year, I just started to contact the background management system. Many page layouts have a large number of query criteria and search boxes on the top, and the following is the list of query results. Then there is a problem. For example, the user queries the result list through condition a, and the user queries the list of the next page according to condition a when turning the page, But if the user modifies condition a as condition B, but does not click to search again, but clicks to turn the page. At this time, we are sure to use condition a to query the next page. The condition displayed on the page is B, so it can be seen that behind each condition there are two variables, one is displayed and the other is the real query condition
The problem solved by oldval this time is similar to the one mentioned above. When the user mouse over the component, the component changes the selected state correspondingly, that is, the width of the upper icon, but the user leaves the icon element instead of making a selection. Then the icon state should be restored to the original state, and the state value is oldval

Click to change the selection status
Because of the above logic and structure, this step is very simple

selectValue() {
      //Update to make sure it's done
      this.oldVal = this.value;
      //This value is actually calculated when sliding
      //In order to avoid possible bugs in multi-v-model binding
      this.$emit("change",this.oldVal);
    },

We have done the user configuration above, so the following effects can be achieved;
The full score can be set to 10 by yourself;
And the quantity can be set at will;
Episode 13: implement a set of UI component library of Vue on PC from scratch (scoring component little star)

5. Display scoring and style

DOM structure
Only when the user enters the score will our score display component be displayed to the user

<i>
// ...
 </i>
    <span v-if='score'
          class="cc-rate__score">
      <slot name='score'> {{value | fix}} </slot>
    </span>

Filter above
Add. 0 to the displayed data;
Some students think why not use ToFixed

  1. I’m tired of writing ToFixed
  2. In fact, ToFixed has disadvantages. It will automatically round, so it should be used according to the situation;
export const myToFixed = value => {
  value = value + '';
  if (value.includes('.')) {
    let sp = value.split('.');
    return sp[0] + '.' + sp[1].slice(0, 1);
  } else {
    return value + '.0';
  }
};

You must select a complete star at a time
Sometimes users don’t want. 1.2.3 but want integers, so it’s good to return integers to users every time
Of course, we have to pay a certain amount of calculation. Here, we should pay attention to that the total score is not fixed, so don’t forget to calculate the total score

props: {
    One: Boolean, // only complete each}
 methods: {
    //Calculate finger position
    //Effective when clicked or effective when moved?
    handelMousemove(e) {
      if (!this.disabled) {
        let node = this.$refs.box;
        let value =
          ((e.pageX - getHTMLScroll(node).left) / (this.size * this.num)) *
          this.numTotal;
        value = this.boundary(value);
        //New code---------------------------------------
        //The distance of each one must be complete
        let i = 0,
          //Find the fraction of a star
          oneNum = this.numTotal / this.num;
          //Until it's bigger than this
        while (oneNum * i <= value) {
          i++;
        }
        if (this.one) {
         //Prevent spillage
          value = Math.min(oneNum * i, this.numTotal);
          this.$emit("input", value);
           //New code---------------------------------------
        } else {
          this.$emit("input", value);
        }
      }
    },
 }

Zoom out effect selected: as shown in the figure
Episode 13: implement a set of UI component library of Vue on PC from scratch (scoring component little star)
My idea for this effect is to give an index value. If it’s smaller than index, it’s better to reduce it
Of course, you need to turn on the big mode

<cc-icon v-for='item in num'
    // ...
    :class="{ 'cc-rate--big':item < bigIndex}" />
data() {
    return {
      oldVal: 0,
      bigIndex: 0
    };
  },
  methods: {
    //Calculate finger position
    //Effective when clicked or effective when moved?
    handelMousemove(e) {
      //The reason for real-time judgment is that the user may not be allowed to change it now, and it needs to be changed later!!
      if (!this.disabled) {
       // ...
        //Zoom in front
        //The distance of each one must be complete
        let i = 0,
          oneNum = this.numTotal / this.num;
        while (oneNum * i <= value) {
          i++;
        }
        //New code-------------
        if (this.big) {
        //Offer flowers to Buddha. Use the calculated number of stars directly
          this.bigIndex = i;
        }
         //New code-------------
        if (this.one) {
          //Prevent spillage
          value = Math.min(oneNum * i, this.numTotal)
          this.$emit("input", value);
        } else {
          this.$emit("input", value);
        }
      }
    },
  }

The above I variable can be optimized, because it is not required every time, after all, it is possible for users to generate 1000 stars;

let i, oneNum;
        if (this.big || this.one) {
          i = 0;
          oneNum = this.numTotal / this.num;
          while (oneNum * i <= value) {
            i++;
          }
          if (this.big) {
            this.bigIndex = i;
          }
        }
        If (this. One) {// prevent overflow
          value = Math.min(oneNum * i, this.numTotal);
          this.$emit("input", value);
        } else {
          this.$emit("input", value);
        }

Continue with index

//Leave area
    handelMouseleave() {
    //When you leave, of course, you need to cancel all the magnifications
      this.bigIndex = 0;
      if (!this.disabled) {
        this.$emit("input", this.oldVal);
      }
    },

tail

mounted() {
//At the beginning, the oldvalue should also be started;
    this.oldVal = this.value;
  }

end

I’ve taken it too. I don’t want it if it’s a long time
The next chapter talks about the prompt box component Popover

We can all communicate, learn and progress together, and realize our self-worth as soon as possible!!

Project GitHub address: GitHub
Personal technology blog (official website of component): link description