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上哦(还有其他笔者封装的组件)
相关文章
|
JavaScript 前端开发
layui用layer.open打开子页面并获取子页面的ueditor富文本编辑器的内容
该内容描述了一个Web应用的交互流程,其中父页面通过调用子页面的JavaScript函数来获取富文本编辑器的内容。子页面包含一个富文本编辑器和一个`callbackdata`函数,用于返回编辑器的文本内容。父页面使用`layer.open`打开子页面作为弹窗,并在用户点击提交时,访问子页面的`callbackdata`获取编辑器内容,同时检查其他表单字段,如类型、标题等是否为空,以确保数据完整。
1185 0
|
11月前
|
数据采集 自然语言处理 Java
Playwright 多语言一体化——Python/Java/.NET 全栈采集实战
本文以反面教材形式,剖析了在使用 Playwright 爬取懂车帝车友圈问答数据时常见的配置错误(如未设置代理、Cookie 和 User-Agent),并提供了 Python、Java 和 .NET 三种语言的修复代码示例。通过错误示例 → 问题剖析 → 修复过程 → 总结教训的完整流程,帮助读者掌握如何正确配置爬虫代理及其它必要参数,避免 IP 封禁和反爬检测,实现高效数据采集与分析。
660 3
Playwright 多语言一体化——Python/Java/.NET 全栈采集实战
|
存储 API 开发工具
openim如何与现有系统集成
本文介绍如何将OpenIM集成到现有系统以实现聊天功能。通过REST API,您的应用服务器可与IM服务器对接,完成用户注册、信息修改及获取IM Token等操作。客户端集成OpenIM SDK,实现用户登录和聊天功能。OpenIM是开源即时通讯解决方案的领军者,在GitHub上获得超14,000星标。开发文档和GitHub仓库提供详细支持。
807 1
|
JavaScript
Vite 按需引入 Ant Design Vue 3.0
Vite 按需引入 Ant Design Vue 3.0
|
移动开发 JavaScript 前端开发
webpack学习四:使用webpack配置plugin,来使用HtmlWebpackPlugin、uglifyjs-webpack-plugin、webpack-dev-server等插件简化开发
这篇文章主要介绍了如何通过配置Webpack的插件,如HtmlWebpackPlugin、uglifyjs-webpack-plugin和webpack-dev-server,来简化前端开发流程。
699 0
webpack学习四:使用webpack配置plugin,来使用HtmlWebpackPlugin、uglifyjs-webpack-plugin、webpack-dev-server等插件简化开发
|
安全 应用服务中间件 网络安全
简单比较 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可提高数据安全性和用户信任度。
1543 13
|
弹性计算 固态存储 数据可视化
2022阿里云服务器租用价格表(CPU/内存/带宽/系统盘)
阿里云服务器租用费用1核2G、2核4G、4核8G、8核16G、2核8G、4核16G、8核32G、2核16G、4核32G、8核16G多配置报价,包括CPU内存实例价格、公网带宽费用和系统盘收费标准
2022阿里云服务器租用价格表(CPU/内存/带宽/系统盘)
|
人工智能 算法 安全
深度讲解-互联网算法备案指南和教程
随着人工智能和大数据技术的发展,互联网算法在内容推荐、用户画像等领域日益重要,但也带来了安全风险和合规挑战。国家互联网信息办公室为此发布了《互联网算法备案管理规定》,要求具有舆论属性或社会动员能力的互联网信息服务提供者进行算法备案,以确保算法透明性和合规性,维护网络健康秩序。唯安创远AI合规专家将解析备案的必要性、流程及其对企业的影响,帮助企业顺利完成备案。
1316 3