写点什么

自己动手丰衣足食——自定义下拉框 vue 组件

用户头像
空城机
关注
发布于: 2021 年 05 月 11 日
自己动手丰衣足食——自定义下拉框vue组件

在页面制作的过程中,经常需要使用到下拉框组件,有时候使用原生的 select 标签十分不便,因为存在 shadow root,shadow root 导致我们有时候修改样式时会非常麻烦

《ShadowRoot介绍》


使用 element UI 下拉框时,又遇到了选择的选项文字内容过长,不能够及时变省略号的现象,需要再次用鼠标点击后才能生效(果然 bug 依旧是程序员不变的主题

element UI下拉框问题


虽然网上还有其他 UI 组件,但是因为当时我总体使用的是 element UI,不方便改用其他 UI 组件了。

没有条件那就只有自己创造,自己动手丰衣足食。

之前我也写过一篇下拉框组件的博客:自定义一个适应vue的下拉框组件


这一次稍做修改,现在的下拉框组件由两个组件构成

实现的效果:

自己做的下拉框效果


正文开始

构思

下拉框组件准备分成两个模块:

  • 第一块是用户能直接看到的内容框,有一个三角形箭头

  • 第二块是用户点击内容框后触发的下拉列表框

用户直接看到点击选择


外层

首先,定义一个外部的模块组件 wzc-select.vue

外部组件样式如下

旁边的三角符号为 font-awesome 图形,font-awesome是一个免费的图标字体库

  • 使用npm install font-awesome下载

  • 如果使用 main.js 的话,添加 import 'font-awesome/css/font-awesome.min.css';

  • 使用 CDN 的话,用 link 引入:<link href="//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">

  • 使用时直接在 class 中添加相应的图表名,如<i class="imgthree fa fa-caret-up" ></i>


此外部组件调用时,可以对宽高还有 placeholder 进行设置

这里的数据传值由父组件传给子组件数值 props 来完成。

如果想要了解组件之间传值的方式,可以参考此文章:《Vue 组件通信的 8 种方式》

使用 props 接收 width、height、placeholder 三个值,可以使用 default 去设置默认值,如果外部没有传值,就会使用 default 中的数值

props: {  placeholder: {    type: String,    default: '请选择'  },  width: {    type: Number,    default: 180  },    height: {      type: Number,        default: 40    },}
复制代码


部分属性使用:root 的方式添加,这里可以自己了解 《:root - CSS》

在 vue 中,可以使用 compute 去设置一个styleVar对象

computed: {  styleVar() {    return {      '--select-height': this.height + 'px',      '--select-width': this.width + 'px'    }  }}
复制代码

在 div 中通过:style 绑定styleVar

<div class="wzc_select" :style="styleVar>

这样就可以在下面的 style 中写 CSS 时直接使用这些宽高

.wzc_select {  border: 1px solid #E6E6E6;  border-radius: 5px;  height: var(--select-height);  width: var(--select-width);  line-height: var(--select-height);}
复制代码


外层的分为两部分,下面的 Selectlist 下拉框列表默认是隐藏的。

<template>    <div class="wzc_select" :style="styleVar" >        <!-- 选择框 -->        <div class="divSelect" :class="{ 'drop_down': isListShow }" ref="divSelect" >            <div class="divSelectinput" @click="dropDownSelect">                <!-- 选中后的内容 -->                <div class="selectinfos" :title="label" :class="{ 'no_select': label == '请选择' }"> {{ label }} </div>                <!-- 三角形图标 isListShow判断三角形图标是否旋转 -->                <i class="imgthree fa fa-caret-up" :class="{ 'is-reverse': isListShow }"></i>            </div>        </div>        <!-- 下拉框列表 -->        <transition name="drop-down" >          	<!-- 下拉框列表isListShow来决定是否收起 -->            <div class="Selectlist" v-show="isListShow" ref="dropDown">                <div class="select_triangle"></div>                <ul class="wzc_option_list">                    <slot name="wzc_option"></slot>                </ul>            </div>        </transition>    </div></template>
复制代码

如果需要在点击弹出下拉框时增加一些动作效果的话,可以使用<transition>框住下拉框

在 CSS 中写入 transition 动画效果

.drop-down-enter {  opacity: 0;  transform:translate(0px, -80px) scaleY(0.2);}.drop-down-leave-to {  opacity: 0;  transform:translate(0px, -80px) scaleY(0.2);}.drop-down-enter-active {	transition: all 0.5s ease-in;}.drop-down-leave-active {	transition: all 0.5s ease;}
复制代码


在点击弹出和收起下拉框时需要做一个 document 的点击判断,如果点击在页面其他部分,就将下拉框收起

document.addEventListener("click", function( e ){  if(_this.$refs.divSelect) {    if ( !!_this.$refs.divSelect.contains(e.target) || !!_this.$refs.dropDown.contains(e.target) )       return;    else      _this.isListShow = false;  }   })
复制代码


这样经过一些的考虑和操作之后,外层的部分就已经写完了

让我们使用 import 将 wzc-select.vue 导入到页面中去吧,注意要在 components 中注册


导入:import wzcSelect from './wzc-select'

注册:components:{ wzcSelect }

调用: <wzc-select class="wzcs" :width="240" :height="40"></wzc-select>


目前当前的效果已经有了,不过比较基础

当前外层效果


内层

内层代码其实相对来说要简单的多,只需要展示外部传入的数据即可

<template>  <li class="wzc_option" :style="styleVar" @click="currentSelect">    <div class="wzc_option_dropdown_item">{{ label }}</div>  </li></template>
复制代码

在 props 当中接收 CSS 的宽高属性,和 label 内容与 optionid 属性

props: {  // 宽  width: {    type: Number,    default: -1,  },  // 高  height: {    type: Number,    default: 34,  },  // 内容  label: {    type: String,  },  // id  optionid: {    type: String,   },},
复制代码

在点击选择时,使用 $parent 去传递数据给外层 wzc-select.vue 组件

currentSelect() {

      this.$parent.label = this.label;

      this.$parent.optionid = this.optionid;

      this.$parent.isListShow = !this.$parent.isListShow;

}

当然别忘记导入内层组件

import wzcOption from './wzc-option'


外层和内层结合


内层主要是一个<li></li>对象,在外层使用时,可以使用 slot 去让内层存放在相应显示的位置

有关于 slot 插槽的介绍可以参考 ( 当然具体的学习得自己慢慢过一遍 ):


在父组件中调用时就可以添加完全了

<wzc-select class="wzcs" :width="240" :height="40">  <template v-slot:wzc_option>    <wzc_option      v-for="item in showlist"      :key="item.item_id"      :label="item.item_name"      :optionid="item.item_id"    ></wzc_option>  </template></wzc-select>
复制代码

使用 showlist 为测试数据

showlist: [  {    item_name: "选项00000000000000000000000000000",    item_id: "0",  },  {    item_name: "选项11111111111111111111111111111",    item_id: "1",  },  {    item_name: "选项222222222222222222222222222222",    item_id: "2",  },  {    item_name: "选项33333333333333333333333333333333",    item_id: "3",  },],
复制代码

好了,现在下拉框的实现效果就达到了需要的样式了

效果


外层 wzc-select.vue 完整代码

<template>    <div class="wzc_select" :style="styleVar" >        <div class="divSelect" :class="{ 'drop_down': isListShow }" ref="divSelect" >            <div class="divSelectinput" @click="dropDownSelect">                <!-- 选中后的内容 -->                <div class="selectinfos" :title="label" :class="{ 'no_select': label == '请选择' }"> {{ label }} </div>                <!-- 三角形图标 -->                <i class="imgthree fa fa-caret-up" :class="{ 'is-reverse': isListShow }"></i>            </div>        </div>        <!-- 下拉框列表 -->        <transition name="drop-down" >            <div class="Selectlist" v-show="isListShow" ref="dropDown">                <div class="select_triangle"></div>                <ul class="wzc_option_list">                    <slot name="wzc_option"></slot>                </ul>            </div>        </transition>    </div></template>
<script>export default { name:'wzc_select', components: {}, props: { placeholder: { type: String, default: '请选择' }, width: { type: Number, default: 180 }, height: { type: Number, default: 40 }, }, data() { return { label: '', isListShow: false, optionid: '' }; }, created() { this.label = this.placeholder; }, mounted() { let _this = this; document.addEventListener("click", function( e ){ if(_this.$refs.divSelect) { if ( !!_this.$refs.divSelect.contains(e.target) || !!_this.$refs.dropDown.contains(e.target) ) return; else _this.isListShow = false; } }) }, computed: { styleVar() { return { '--select-height': this.height + 'px', '--select-width': this.width + 'px' } } }, methods: { dropDownSelect() { this.isListShow = !this.isListShow; }, },};</script><style scoped> .wzc_select { border: 1px solid #E6E6E6; border-radius: 5px; height: var(--select-height); width: var(--select-width); line-height: var(--select-height); } .divSelect { width: 100%; height: 100%; border-radius: 5px; } .drop_down { box-shadow: 0px 0px 6px #709DF7; } .divSelectinput { width: calc(100% - 20px); height: 100%; margin: 0 5px 0 15px; display: flex; } .selectinfos { width: 87.5%; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .no_select { color: #D3DCE6; } .imgthree { width: 12.5%; line-height: var(--select-height); text-align: center; transform: rotate(180deg); transition: all 0.3s; } .imgthree:before { cursor: pointer; color: #D3DCE6; } .imgthree.is-reverse { transform: rotate(0deg); }
.Selectlist { margin-top: 10px; z-index: 800; position: relative; background-color: #fff; } .wzc_option_list { border-radius:5px; border:1px solid #E4E7ED; width: 100%; padding: 3px 0px; box-shadow: 0px 0px 6px #709DF7; background-color: #fff; margin: 0; } .select_triangle { width: 14px; height: 7px; position: relative; left: 15px; } .select_triangle::before { position: absolute; content: ""; left: 0px; width: 0; height: 0; border-top: 0px solid transparent; border-left: 9px solid transparent; border-right: 9px solid transparent; border-bottom: 8px solid #EBEEF5; } .select_triangle::after { position: absolute; left: 2px; top: 2px; content: ""; width: 0; height: 0; border-top: 0px solid transparent; border-left: 7px solid transparent; border-right: 7px solid transparent; border-bottom: 8px solid #fff; } .drop-down-enter { opacity: 0; transform:translate(0px, -80px) scaleY(0.2); } .drop-down-leave-to { opacity: 0; transform:translate(0px, -80px) scaleY(0.2); } .drop-down-enter-active { transition: all 0.5s ease-in; } .drop-down-leave-active { transition: all 0.5s ease; }</style>
复制代码


内层 wzc-option.vue 完整代码

<template>  <li class="wzc_option" :style="styleVar" @click="currentSelect">    <div class="wzc_option_dropdown_item">{{ label }}</div>  </li></template>
<script>export default { name: "wzc_select", components: {}, props: { width: { type: Number, default: -1, }, height: { type: Number, default: 34, }, label: { type: String, }, optionid: { type: String, }, }, data() { return {}; }, created() {}, mounted() {}, watch: {}, computed: { styleVar() { return { "--option-height": this.height + "px", "--option-width": this.width == -1? "100%" : this.width + "px", }; }, }, methods: { currentSelect() { this.$parent.label = this.label; this.$parent.optionid = this.optionid; this.$parent.isListShow = !this.$parent.isListShow; // this.$emit('slot-content', {label: this.label, optionid: this.optionid} ); } },};</script><style scoped>.wzc_option { list-style: none; height: var(--option-height); width: var(--option-width); }.wzc_option:hover { color: #409eff; font-weight: 700; background-color: #f5f7fa;}.wzc_option_dropdown_item { height: 100%; width: calc(100% - 30px); line-height: var(--option-height); cursor: pointer; margin: 0 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
</style>
复制代码


发布于: 2021 年 05 月 11 日阅读数: 41
用户头像

空城机

关注

曾经沧海难为水,只是当时已惘然 2021.03.22 加入

业余作者,在线水文 主要干前端的活,业余会学学python 欢迎各位关注,互相学习,互相进步

评论

发布
暂无评论
自己动手丰衣足食——自定义下拉框vue组件