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

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

什么是抽屉drawer组件

  • 同弹框dialog组件类似,UI展示略有不同
  • 一般抽屉是左右防线弹出和收回,上下方向不多
  • 可在抽屉内部进行代码补充操作
  • 某些情况下,抽屉组件比弹框组件更加好用一些

笔者关于抽屉组件的封装,就不写太多的解析说明了,大家可以直接复制粘贴代码,搭配代码中的注释进行使用(结合自己公司业务封装)

笔者的抽屉组件实现,抛砖引玉。实现主要常用的功能,道友们可以进行思维发散

效果图

先看一下抽屉组件的效果图

demo.gif

代码

使用时的代码

<template>
  <div>
    <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 -->
    <div
      @click="clickMaskCloseFn"
      class="myDrawerWrap"
      :class="{ isShowDrawerMask: mask }"
      v-show="isShowDrawer"
    >
      <div
        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">
          </i>
        </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;
      i {
        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;
}
.rightMove-leave-to {
  transform: translateX(100%);
  opacity: 0;
}
</style>

总结

  • A bad pen is better than a good memory
  • 完整代码在github上哦(还有其他笔者封装的组件)
相关文章
|
数据可视化 UED 容器
JavaFX布局详解与代码实例
JavaFX布局详解与代码实例
303 0
|
6月前
|
数据采集 自然语言处理 Java
Playwright 多语言一体化——Python/Java/.NET 全栈采集实战
本文以反面教材形式,剖析了在使用 Playwright 爬取懂车帝车友圈问答数据时常见的配置错误(如未设置代理、Cookie 和 User-Agent),并提供了 Python、Java 和 .NET 三种语言的修复代码示例。通过错误示例 → 问题剖析 → 修复过程 → 总结教训的完整流程,帮助读者掌握如何正确配置爬虫代理及其它必要参数,避免 IP 封禁和反爬检测,实现高效数据采集与分析。
404 3
Playwright 多语言一体化——Python/Java/.NET 全栈采集实战
|
8月前
|
机器学习/深度学习 C++
强化学习:实践理解Markov决策过程(MDP)(干中学系列)——手把手教你入门强化学习(三)
本博客以实践为主,带领读者巩固上期关于“Markov决策过程”的核心概念。通过构建学生马尔可夫奖励模型、计算收获值与状态价值,进一步验证贝尔曼方程。详细介绍了转移概率、奖励值及策略概率的设置,并实现了均匀随机策略下的状态价值计算与最优策略的价值评估。结合代码实例,帮助读者深入理解强化学习理论。适合初学者实践与进阶学习。
326 63
|
9月前
|
存储 API 开发工具
openim如何与现有系统集成
本文介绍如何将OpenIM集成到现有系统以实现聊天功能。通过REST API,您的应用服务器可与IM服务器对接,完成用户注册、信息修改及获取IM Token等操作。客户端集成OpenIM SDK,实现用户登录和聊天功能。OpenIM是开源即时通讯解决方案的领军者,在GitHub上获得超14,000星标。开发文档和GitHub仓库提供详细支持。
606 1
|
移动开发 JavaScript 前端开发
webpack学习四:使用webpack配置plugin,来使用HtmlWebpackPlugin、uglifyjs-webpack-plugin、webpack-dev-server等插件简化开发
这篇文章主要介绍了如何通过配置Webpack的插件,如HtmlWebpackPlugin、uglifyjs-webpack-plugin和webpack-dev-server,来简化前端开发流程。
557 0
webpack学习四:使用webpack配置plugin,来使用HtmlWebpackPlugin、uglifyjs-webpack-plugin、webpack-dev-server等插件简化开发
|
存储 关系型数据库 MySQL
mysql8.0中的mysql.ibd
`mysql.ibd`文件在MySQL 8.0中扮演着重要角色,负责存储InnoDB表的数据和索引。通过了解其结构和管理方法,可以有效维护数据库的性能和数据完整性。希望本文对 `mysql.ibd`文件的深入解析能帮助您更好地理解和管理MySQL数据库。
774 1
|
缓存 Linux 调度
[kvm]硬盘IO优化
[kvm]硬盘IO优化
280 2
|
安全 应用服务中间件 网络安全
简单比较 http https http2,我们要如何把http升级为https
【9月更文挑战第13天】本文对比了HTTP、HTTPS和HTTP/2的特点与适用场景。HTTP以明文传输,适合低安全要求的环境;HTTPS通过SSL/TLS加密,适用于电子商务等安全要求高的场景;HTTP/2采用二进制格式和多路复用,适合高性能Web应用。文章还详细介绍了将HTTP升级为HTTPS的步骤,包括申请和安装SSL证书、配置Web服务器、重定向HTTP流量到HTTPS以及测试HTTPS功能。升级到HTTPS可提高数据安全性和用户信任度。
549 13
|
JavaScript
Vite 按需引入 Ant Design Vue 3.0
Vite 按需引入 Ant Design Vue 3.0