添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
首发于 elementUI

elementui源码学习之仿写一个el-drawer

本篇文章记录仿写一个 el-drawer 组件细节,从而有助于大家更好理解饿了么ui对应组件具体工作细节。本文是elementui源码学习仿写系列的又一篇文章,后续空闲了会不断更新并仿写其他组件。源码在github上,大家可以拉下来,npm start运行跑起来,结合注释有助于更好的理解。github仓库地址如下: github.com/shuirongshui

什么是抽屉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;