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>

原文地址:https://www.cnblogs.com/024-faith/p/select.html

时间: 2024-08-04 00:17:29

vue自定义select组件的相关文章

vue自定义分页组件---切图网

vue2.5自定义分页组件 Pagination.vue,可设置每页显示条数,带跳转框直接跳转到相应页面,亲测有用.目前很多框架自带有分页组件比如elementUI,不过在面对一个拿到PSD稿,然后重新切图,重新VUE开发的项目来说,自定义分页组件才是应万变的最佳打开方式. html <template> <div class="pagination" v-if="totalPage>1"> <span v-if="!s

Vue自定义日历组件

今天给大家介绍Vue的日历组件,可自定义样式.日历类型及支持扩展,可自定义事件回调.Props数据传输. 线上demo效果 示例 Template: <Calendar :sundayStart="true" :calendarType="1" :markDate="markDate" :markDateClass="markDateClass" :agoDayPrevent="agoDayPrevent&qu

教你撸一个简单的Vue自定义动态组件

components下创建 toast 文件夹, 文件夹里面创建 toast.vue 和 index.js toast.vue: <template> <div id="toastWrap" :class="[className,showAnimation ?'fadein':'fadeout',appointId==''?'fixed':'absolute']" v-if="show"> <span :class=

vue 自定义封装组件 使用 model 选项

自定义组件的 v-model 一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框.复选框等类型的输入控件可能会将 value 特性用于不同的目的.model 选项可以用来避免这样的冲突: Vue.component('base-checkbox', { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, template: ` <input

vue自定义日期组件

vue-datepicker 基于 vuejs 2.x 可自定义主题的日期组件 github Usage 只需要在项目中加入 calendar.vue,就可以使用了. 向组件传入 prop 即可改变 calendar 的样式 Props type(String): 默认 single(选择单天),可选 range(选择多天) / time(选择单天 + 时间) isAbandon(Boolean): 默认 true,早于系统日期的日期选项是否不可选 showInput(Boolean): 默认

vue 自定义模版组件

vue的组件可以自定义内容,属性 又有slot作配合 可以做出很多自定义模版 例子 <body> <div id="app"> <m-modal modal-title="提醒" @on-ok="ok"></m-modal> <m-modal> <ul slot="modal-content"> <li v-for="item of lis

vue 自定义image组件

介绍 1:当图片加载失败时,给出错误提示. 2:当图片加载中时,给出加载提示. 3:图片处理模式:等比缩放/裁剪/填充/... 1.图片加载状态处理 通过给图片绑定load事件与error事件处理函数来判断图片加载状态.当图片加载完成时会触发load事件:图片加载出错会触发error事件 // 样本 <img src="..." @load=onLoad @error=onError> 2.图片模式 通过css属性 object-fit(https://developer.

Vue自定义插件(组件)Loading

vue.use()方法可以用来注册组件或者插件. 只要传入一个install()方法既可以注册 install(Vue,option){} 可以通过几种方式来自定义开发 Vue.$loading = -//直接挂载在Vue类上 Vue.propertype.$loading = -//直接挂载在Vue原型链上,可以通过this.$loading调用 Vue.component()//注册一个全局组件 Vue.directive()//注册全局指令 Vue.mixin()//全局混入,可以理解为继

vue学习笔记の实现select组件

通过腾讯训练营这几天的学习,初步实现了自定义的选择下拉框组件,其中,可以把下拉选项抽离出来作为子组件,整个组件为父组件,其主要由<div>.<input>.<custom-select>.<ul>.<li>等标签构成基本的选择组件页面结构.主要的功能项:输入框及button按钮,构成初次展现的页面,通过点击输入框按钮,下拉列表选择项出现,当点击选择项中的某一项内容时,输入框中会出现相应的内容,再点击输入框,下拉选择项列表隐藏.同时,通过父组件与子