vue自定義select組件


1.目的

  看了很多element-ui的源碼,決定自己實現一個簡單的select組件,遇到的幾個難點,便記錄下來.

2.難點一

  element-ui中的select組件通過v-model可以綁定數據,但在我平時用v-model只是在input中使用過,只知道v-model可以雙向綁定數據,但並不清楚其中的實現過程,所以 需要清晰的了解v-model是什么,如下.

<input v-model="test"/>  

<input :value="test" @input="test = $event.target.value"/> // 第一行和第二行的性質是一樣的,v-model是一個vue的語法糖
 

  以上是input輸入框中的v-model,input標簽在輸入的時候默認會觸發'input'事件, 但是自定義的組件並不會,所以需要我們自己手動發送一個'input'事件,其次是,使用了v-model指令以后會默認動態綁定一個屬性值value,因此我們在自定義組件中可以在props接收value,並綁定到組件當中,從而實現了雙向綁定,具體可以看參考完整代碼.

3.難點二

  當select組件顯示選擇框時,合理的邏輯是是點擊空白或者點擊自身都要將選擇框關閉, 起初實現是在document中綁定一個click事件用於關閉選擇框,當然select點擊得阻止事件冒泡,這樣的實現方式是在一個頁面只有一個select組件是沒有問題的,但是當出現多個select組件就會出現一個bug,點擊完一個select以后點擊另外一個是無法關閉前一個select框的選擇框的,問題出在因為每個select框都被阻止了事件的冒泡,自然不會觸發document的click事件,從而無法關閉,知曉原因,解決方案如下:

// 顯示選擇框
showSel(){
        this.show = true;
       addEvent(document, 'click',this.hideSel, true);
}

// 隱藏選擇框
hideSel(e){
    this.show = false;
  // 如果是子元素,則阻止事件捕獲 if(this.$refs.sel && this.$refs.sel.contains(e.target)){ stopEvent(e); } removeEvent(document,'click',this.hideSel,true); } // 顯示或隱藏 toggle(){ this.show && this.hideSel() || this.showSel(); } // 注意:其中addEvent,removeEvent,stopEvent是為了兼容處理而自定義的方法

  以上就是這次編寫select組件的所得,附上完整實例代碼.

<template>
    <div class="select" @click="toggle" ref="sel">
        <div class="input">
            <input
                type="text" 
                 :placeholder="placeholder"
                 readonly
                 :value = 'value'
                 @blur="handle">
            <img src="../images/drop.svg">
        </div>
        <ul
            class="content"
            :class="{'bottom' : position == 'bottom', 'top' : position == 'top'}"
            v-show="show && values.length"
            ref="content">

            <li v-for="item in values">{{item}}</li>
        </ul>

    </div>
</template>

<script>
import { addEvent, removeEvent, stopEvent } from '../service/utli.js';
export default {

    name : 'comSelect',
    data(){
        return{
            val : '',
            show : false,
            position : 'bottom'
        }
    },
    props : {
        values : {
            type : Array,
            default(){
                return []
            }
        },
        value : {
            
        },
        placeholder:{
            type : String,
            default : '請選擇'
        },
    },
    mounted(){
        this.computePos();
    },
    methods:{
        getElementTop(element){
            var actualTop = element.offsetTop;
             var current   = element.offsetParent;
         while (current !== null){
                actualTop += current.offsetTop;
                current = current.offsetParent;
         }
            return actualTop;
        },

        // 計算選擇框是往上彈出還是往下彈出
        computePos(){

            let elHeight       = this.$refs.sel.offsetHeight;
            let absPos            = this.getElementTop(this.$refs.sel);
            let contentHeight = this.values.length*40;

            let docScrollHei  = document.body.scrollTop 
                || document.documentElement.scrollTop || 0;

            let docHeight =  document.documentElement.clientHeight
                || document.body.clientHeight || 0;

            if((elHeight+absPos+contentHeight-docScrollHei)>docHeight){
                this.position = 'top';
            }else{
                this.position = 'bottom';
            }
        },
        setVal(item){
            this.$emit('input',item);
        },
        handle(){
            this.$emit('blur');
        },
        showSel(){
            this.show = true;
            addEvent(document, 'click',this.hideSel, true);
        },
        hideSel(e){
            this.show = false;
            console.log(this.$refs.sel.contains(e.target));
            if(this.$refs.sel && this.$refs.sel.contains(e.target)){
                // 如果是子元素則阻止事件捕獲
                stopEvent(e);
          this.setVal(e.target.innerHtml); } removeEvent(document,
'click',this.hideSel,true); }, toggle(){ this.show && this.hideSel() || this.showSel(); } } } </script> <style scoped lang="scss"> @import '../style/mixin.scss'; .select{ width: 100%; height: 100%; position: relative; cursor: pointer; } .input{ width: 100%; height: 100%; position: relative; cursor: pointer; } .input>input{ width: 100%; height: 100%; cursor: pointer; } .input>img{ right: 0; top: 50%; width: 12px; height: 12px; position: absolute; transform: translateY(-50%); } .content{ width: 100%; max-height: px(300); overflow-y: scroll; border-radius: 10px; @include padding(4px 0); position: absolute; left: 0; background-color: white; box-shadow: 0 0 20px 2px #ccc; @include prix(transform, translateY(5px)); z-index: 2; } .content::-webkit-scrollbar {display: none;} .bottom{ top: 100%; } .top{ bottom: 125%; } .content>li{ height: 40px; line-height: 40px; width: 100%; @include padding(0 0 0 10px); } .content>li:hover{ color: #409eff; background-color: rgba(33,33,33,.2); } </style>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM