序
在页面制作的过程中,经常需要使用到下拉框组件,有时候使用原生的 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>
复制代码
评论