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" @click="setVal(item)">{{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);
            }
            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>

猜你喜欢

转载自www.cnblogs.com/024-faith/p/select.html
今日推荐