elementui源码学习之仿写一个el-drawer
本篇文章记录仿写一个
el-drawer
组件细节,从而有助于大家更好理解饿了么ui对应组件具体工作细节。本文是elementui源码学习仿写系列的又一篇文章,后续空闲了会不断更新并仿写其他组件。源码在github上,大家可以拉下来,npm start运行跑起来,结合注释有助于更好的理解。github仓库地址如下:
https://
github.com/shuirongshui
fu/elementSrcCodeStudy
什么是抽屉drawer组件
- 同弹框dialog组件类似,UI展示略有不同
- 一般抽屉是左右防线弹出和收回,上下方向不多
- 可在抽屉内部进行代码补充操作
- 某些情况下,抽屉组件比弹框组件更加好用一些
笔者关于抽屉组件的封装,就不写太多的解析说明了,大家可以直接复制粘贴代码,搭配代码中的注释进行使用(结合自己公司业务封装)
笔者的抽屉组件实现,抛砖引玉。实现主要常用的功能,道友们可以进行思维发散
效果图
先看一下抽屉组件的效果图
代码
使用时的代码
<template>
<h4>isShowDrawer.sync属性控制是否显示抽屉</h4>
<h4>title属性控制抽屉的头部标题</h4>
<h4>direction属性控制抽屉的4个方向</h4>
<h4>beforeClose函数属性关闭抽屉前的操作动作</h4>
<h4>showCloseIcon属性控制是否显示抽屉的关闭小按钮</h4>
<h4>isShowHeader属性控制是否显示抽屉的头部内容</h4>
<h4>mask属性控制是否显示抽屉的背景遮罩层</h4>
<h4>slot="title"具名插槽控制头部的标题内容</h4>
<h4>clickMaskClose属性控制是否能够点击背景遮罩层关闭抽屉</h4>
<br />
<my-drawer
:isShowDrawer.sync="isShowDrawer1"
title="上方弹出direction='top'"
direction="top"
:beforeClose="handleClose"
:showCloseIcon="false"
></my-drawer>
<my-drawer
:isShowDrawer.sync="isShowDrawer2"
title="下方弹出"
direction="bottom"
:isShowHeader="false"
<h1>:isShowHeader="false"去掉抽屉的头部内容</h1>
<h1>:isShowHeader="false"去掉抽屉的头部内容</h1>
<h1>:isShowHeader="false"去掉抽屉的头部内容</h1>
<h1>:isShowHeader="false"去掉抽屉的头部内容</h1>
<h1>:isShowHeader="false"去掉抽屉的头部内容</h1>
<h1>:isShowHeader="false"去掉抽屉的头部内容</h1>
<h1>:isShowHeader="false"去掉抽屉的头部内容</h1>
<h1>:isShowHeader="false"去掉抽屉的头部内容</h1>
<h1>:isShowHeader="false"去掉抽屉的头部内容</h1>
<h1>:isShowHeader="false"去掉抽屉的头部内容</h1>
<h1>:isShowHeader="false"去掉抽屉的头部内容</h1>
</my-drawer>
<my-drawer
:isShowDrawer.sync="isShowDrawer3"
direction="left"
:mask="false"
<span slot="title">左侧命名插槽弹出哦^_^</span>
<span>没有背景遮罩层</span>
</my-drawer>
<my-drawer
:isShowDrawer.sync="isShowDrawer4"
direction="right"
:clickMaskClose="false"
<span slot="title">右侧命名插槽弹出哦^_^</span>
<span>设置点击背景遮罩层不关闭,只能点击小箭头,或自定义按钮关闭</span>
<br />
<br />
<br />
<br />
<el-button
@click="isShowDrawer4 = false"
type="success"
size="small"
plain
>自定义关闭</el-button
</my-drawer>
<el-button @click="topOpen" type="success" plain>上方弹出</el-button>
<el-button @click="bottomOpen" type="success" plain>下方弹出</el-button>
<el-button @click="leftOpen" type="success" plain>左侧弹出</el-button>
<el-button @click="rightOpen" type="success" plain>右侧弹出</el-button>
</div>
</template>
<script>
export default {
data() {
return {
isShowDrawer1: false,
isShowDrawer2: false,
isShowDrawer3: false,
isShowDrawer4: false,
methods: {
topOpen() {
this.isShowDrawer1 = true;
bottomOpen() {
this.isShowDrawer2 = true;
leftOpen() {
this.isShowDrawer3 = true;
rightOpen() {
this.isShowDrawer4 = true;
handleClose(close) {
this.$confirm("确认关闭close()函数关闭")
.then((_) => {
close();
.catch((_) => {});
</script>
封装的抽屉组件代码
<template>
<!-- 抽屉打开关闭过渡效果根据name去指定 -->
<transition :name="computedName">
<!-- clickMaskCloseFn搭配@click.stop -->
@click="clickMaskCloseFn"
class="myDrawerWrap"
:class="{ isShowDrawerMask: mask }"
v-show="isShowDrawer"
ref="drawerContentRef"
:class="['drawerContent']"
:style="computedDrawerPosition"
@click.stop
<header v-show="isShowHeader" class="drawerHeader">
<slot name="title">
<span>{{ title }}</span>
</slot>
<i class="el-icon-close" @click="closeDrawer" v-show="showCloseIcon">
</header>
<section class="drawerBody">
<slot></slot>
</section>
</div>
</div>
</transition>
</template>
<script>
const directionArr = ["top", "bottom", "left", "right"]; // "ttb","btt","ltr","rtl"
const moveObj = {
top: "topMove",
bottom: "bottomMove",
left: "leftMove",
right: "rightMove",
export default {
name: "myDrawer",
props: {
// 是否显示抽屉
isShowDrawer: {
type: Boolean,
default: false,
// 是否显示抽屉头部内容
isShowHeader: {
type: Boolean,
default: true,
// 父组件传过来的抽屉标题值
title: {
type: String,
default: "我是title",
// 是否显示关闭小图标
showCloseIcon: {
type: Boolean,
default: true,
// 是否开启抽屉背景遮罩层
mask: {
type: Boolean,
default: true,
// 点击遮罩层关闭默认为true
clickMaskClose: {
type: Boolean,
default: true,
// 校验抽屉的4个方向
direction: {
type: String,
default: "right",
validator(val) {
return directionArr.includes(val);
// 接收父组件传递过来的关闭函数,会中断关闭抽屉的操作
beforeClose: {
type: Function,
computed: {
// 动态控制上下左右的抽屉内容区的位置以及抽屉的宽度
computedDrawerPosition() {
let positionObj = {
width:
(this.direction == "left") | (this.direction == "right")
? "30%"
: "100%",
height:
(this.direction == "top") | (this.direction == "bottom")
? "30%"
: "100%",
positionObj[this.direction] = 0;
return positionObj;
// 动态控制抽屉从上下左右进入和退出
computedName() {
return moveObj[this.direction]; // topMove、bottomMove、leftMove、rightMove
methods: {
// 点击遮罩层关闭弹框
clickMaskCloseFn() {
if (this.clickMaskClose == true) {
this.closeDrawer();
} else {
/* 这里要控制一下冒泡事件,注意第十行使用@click.stop
不控制冒泡的话,点击内容区也会导致弹出框关闭*/
return;
// 准备关闭抽屉弹出框
closeDrawer() {
console.log(888);
// 若传递了beforeClose函数,就抛出关闭函数,供外部使用
if (this.beforeClose) {
this.beforeClose(this.close);
// 没有beforeClose函数,直接关闭即可
else {
this.close();
// 关闭抽屉弹出框
close() {
this.$emit("update:isShowDrawer", false); // 关闭
this.$emit("shutDown"); // 并抛出一个shutDown通知事件
</script>
<style lang='less' scoped>
// 基本样式
.myDrawerWrap {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 999;
overflow: hidden;
.drawerContent {
// 搭配定位的方式控制在上下左右的那个方位
position: absolute;
background-color: #fff;
box-shadow: 2px 2px 12px 0 rgba(0, 0, 0, 0.24);
display: flex;
flex-direction: column;
// 抽屉头部
.drawerHeader {
width: 100%;
height: 48px;
box-sizing: border-box;
padding: 12px;
display: flex;
align-items: center;
justify-content: space-between;
font-weight: bolder;
color: #333;
cursor: pointer;
// 抽屉内容体部分
.drawerBody {
width: 100%;
box-sizing: border-box;
padding: 12px;
flex: 1;
overflow-y: auto;
// 遮罩层即为背景色
.isShowDrawerMask {
background-color: rgba(0, 0, 0, 0.3);
下方是抽屉过渡动画的重点
// 上方进入和退出
.topMove-enter-active,
.topMove-leave-active {
transition: all 0.36s ease-in-out;
transform: translateY(0%);
opacity: 1;
.topMove-enter,
.topMove-leave {
transform: translateY(-100%);
opacity: 0;
.topMove-leave-to {
transform: translateY(-100%);
opacity: 0;
// 下方进入和退出
.bottomMove-enter-active,
.bottomMove-leave-active {
transition: all 0.36s ease-in-out;
transform: translateY(0);
opacity: 1;
.bottomMove-enter,
.bottomMove-leave {
transform: translateY(100%);
opacity: 0;
.bottomMove-leave-to {
transform: translateY(100%);
opacity: 0;
// 左侧进入和退出
.leftMove-enter-active,
.leftMove-leave-active {
transition: all 0.36s ease-in-out;
transform: translateX(0%);
opacity: 1;
.leftMove-enter,
.leftMove-leave {
transform: translateX(-100%);
opacity: 0;
.leftMove-leave-to {
transform: translateX(-100%);
opacity: 0;
// 右侧进入和退出
.rightMove-enter-active,
.rightMove-leave-active {
transition: all 0.36s ease-in-out;
transform: translateX(0);
opacity: 1;
.rightMove-enter,
.rightMove-leave {
transform: translateX(100%);
opacity: 0;