如何给element添加一个抽屉组件

近来由于业务须要,对比iview和element库,发现element确实要比实习期间使用的iview强大点,尤为文档更为友好,可是iview的组件功能更多一点,好比分割线和抽屉组件javascript

今天特地手写一个抽屉组件,方便本身使用element库,写好的组件我已经放在个人githup了,点这里css

1、实践

1.分析

一个抽屉组件的z-index一定是在当前页面之上的,在抽屉主体以外的区域还会有一层半透明的遮罩层,知道这些就很容易了html

// drawer.vue
<template>
    <div class="mask"></div>
    <div class="drawer">
        <div class="drawer_body"></div>
    </div>
</template>

<style scoped> .drawer { position: absolute; height: 100vh; top: 0; bottom: 0; right: 0; left: 0; z-index: 1000000 !important; } .drawer .drawer_body { height: 100%; position: absolute; z-index: 1000001; background-color: #fff; } .mask { height: 100vh; width: 100vw; position: absolute; z-index: 1000000; top: 0; left: 0; background-color: #000; opacity: 0.5; } </style>
复制代码

如今已是咱们想要的样子了,接下来是给drawer_body添加样式vue

做为一个灵活的组件库,咱们但愿样式是能够随时定制的,因此,接下要添加的样式都 使用props动态绑定的

参考iview的样式,除了抽屉的宽度,还须要设置抽屉的方向,当咱们须要抽屉时让它显示出来,不须要时隐藏它,或者为了更加显眼,甚至给抽屉更换背景色......,这些都是能够实现的,看下面的代码java

<script> export default { props: { // 是否显示drawer drawerVisible: Boolean, // drawer方向 direction: { type: String, validator(val) { return ["right", "left"].indexOf(val) !== -1; } }, // drawer宽度 width: { type: Number, default: 400 }, // drawer背景色 background: { type: String, default: "#ffffff" }, // 是否显示遮罩层 mask: { type: Boolean, default: true } } }; </script>
复制代码

对于宽度和背景色,你还须要额外的处理下git

<div class="drawer_body" :style="{'right':direction=='right'?'0':'auto', 'left':direction=='left'?'0':'auto', 'width':width+'px','background':background}" >drawer</div>
复制代码

你只需在使用的地方引入组件,而后提供你想修改的props值github

//index.vue
<template>
  <div>
    ...
    <el-button size="mini" @click="visible">显示抽屉</el-button>
    <Drawer :drawerVisible="drawerVisible" direction="right" :mask="true" background="aquamarine" ></Drawer>
  </div>
</template>
<script> export default { data() { return { drawerVisible: false }; }, methods:{ // 打开抽屉 visible() { this.drawerVisible = true; } } } </script>
复制代码

2.关闭抽屉

在点击遮罩层的时候,咱们但愿能够关闭已经打开的抽屉组件,这里若是你直接修改父组件传过来的drawerVisible值,会报错以下bash

vue.esm.js:629 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders. Instead, use a data or computed property based on the
prop's value. Prop being mutated: "drawerVisible" 复制代码

这是由于vue是单向数据流的,若是想改变父元素的值,必须使用监听事件的方式,可是2.3.0以后添加了.sync修饰符,因此,正确的作法是使用.sync修饰符框架

...
<div v-if="drawerVisible" class="mask"></div>
<transition :name="this.direction=='left'?'slide-right':'slide-left'">
        <div v-if="drawerVisible" @click.stop="closeBtn?'':close" class="drawer">
        <div
          class="drawer_body"
          :style="{ 'right':direction=='right'?'0':'auto', 'left':direction=='left'?'0':'auto', 'width':width+'px', 'background': background}"
        >
        </div>
      </div>
</transition>
...

methods: {
    close() {
        this.$emit("update:drawerVisible", false);
    }
}
复制代码

另外,咱们还但愿在关闭抽屉组件时,咱们能够监听到这个事件而后作出反应iview

methods: {
    close() {
        this.$emit("update:drawerVisible", false);
        this.$emit("close");
    }
}
复制代码

此时须要在抽屉组件上添加

<Drawer :drawerVisible.sync="drawerVisible" @close="close" >
    </Drawer>
    
methods:{
    close(){
        // 关闭抽屉组件时你要作的事
    }
}
复制代码

2.动画

动画是UI的灵魂,因此接下来给抽屉组件的显示和隐藏添加动画,咱们使用transition的css动画作动画过分效果

//drawer.vue
  <div class="drawer">
    <div class="mask"></div>
    <!-- 不一样方向使用不用的动画名称,若是抽屉在左边,则进入方向是朝 → -->
    <transition :name="this.direction=='left'?'slide-right':'slide-left'">
      <div
        class="drawer_body"
        v-if="drawerVisible"
        :style="{'right':direction=='right'?'0':'auto', 'left':direction=='left'?'0':'auto', 'width':width+'px', 'background':background}"
      >drawer</div>
    </transition>
  </div>
</template>
<style scoped>
/*
* ...
*这里省略了写过的样式
*/
.slide-right-enter-active,
.slide-right-leave-active,
.slide-left-enter-active,
.slide-left-leave-active {
  will-change: transform;
  transition: transform 300ms;
  position: absolute;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}
.slide-right-enter,
.slide-right-leave-active {
  transform: translate(-100%, 0);
}
.slide-left-leave-active,
.slide-left-enter {
  transform: translate(100%, 0);
}
</style>

复制代码

虽然如今已经彻底实现了抽屉的功能,可是本着更加精美的原则,我还打算使用slot给它添加辩题和页脚

3.添加标题

标题solt的name值是header

添加标题的目的是为了让抽屉组件看起来更加清楚,此外,除了添加标题,我还想添加个关闭按钮

// 须要添加几个props属性
<script>
export default {
  props: {
    // drawer标题
    title: String,
    // 是否显示关闭按钮
    closeBtn: {
      type: Boolean,
      default: false
    },
  }
};
</script>
复制代码

你能够选择是否添加标题,是否添加关闭按钮,值的注意的是若是添加了关闭按钮,点击遮罩层就不会自动关闭抽屉组件了

<!--这里要啰嗦下布局,若是你只选择开启关闭按钮,那justify-content布局是flex-end
若是二者都开启,那justify-content布局是space-between-->
<slot name="header">
    <div
      v-if="title||closeBtn"
      :style="{'justify-content':title?'space-between':'flex-end'}"
      class="title"
    >
      <div v-if="title">{{title}}</div>
      <el-button
        v-if="closeBtn"
        circle
        size="mini"
        icon="el-icon-close"
        @click="close"
      ></el-button>
    </div>
 </slot>
复制代码

我是这么作到禁用遮罩层点击事件的

<div v-if="drawerVisible" @click.stop="closeBtn?'':close" class="mask"></div>
复制代码

固然这些你可使用具名插槽自定义的

<Drawer
    :width="400"
    direction="right"
    :mask="true"
    title="抽屉组件"
  >
    <div v-slot:header>这里是自定义标题</div>
    <div style="height:100px"></div>
</Drawer>
复制代码

4.添加页脚

页脚solt的name值是footer

为了使得页脚和标题有必定的距离,我给主体内容添加了最小高度
<div style="min-height:82vh;padding: 5px 0">
    <slot></slot>
</div>
复制代码

方法是很相似的,只是我多添加了两个监听事件,肯定和取消

//drawer.vue
<slot name="footer">
    <div class="footer">
      <el-button size="mini" type="primary" @click="footerOk">确认</el-button>
      <el-button size="mini" @click="footerCal">取消</el-button>
    </div>
</slot>
复制代码
//引入的页面
<Drawer
    :width="400"
    direction="right"
    :mask="true"
    title="抽屉组件"
    :footer-ok="footerOk"
    :footer-cal="footerCal"
  >
</Drawer>
复制代码

还须要在props中添加对应的值

props: {
    footerOk: Function,
    footerCal: Function
  },
复制代码

关于页脚的布局是这样的

.footer {
    border-top: 0.1px solid #ddd;
    display: flex;
    justify-content: flex-end;
    padding-top: 10px;
}
复制代码

固然这些你也是可使用具名插槽自定义的

<Drawer
    :width="400"
    direction="right"
    :mask="true"
    title="抽屉组件"
  >
    <div v-slot:header>这里是自定义标题</div>
    <div style="height:100px"></div>
    <div v-slot:footer>这里是自定义页脚</div>
</Drawer>
复制代码

5.主体是否能够滚动

前面说过给主体添加了最小高度,可是超过最小高度,可能会被撑开布局,因此我给它添加了滚动功能

// props添加
    // 是否开启滚动
    scroll: {
      type: Boolean,
      default: false
    }
复制代码

在drawer_body的样式末尾追加overflow-y样式

<div
    class="drawer_body"
    :style="{ 'right':direction=='right'?'0':'auto', 'left':direction=='left'?'0':'auto', 'width':width+'px', 'background': background, 'overflow-y':scroll?'scroll':'hidden'}"
>
</div>
复制代码
scroll默认是不开启的,若是你的抽屉要放的内容少,就不用理这个属性,可是当内容撑开抽屉时记得手动开启这个功能
复制代码

6.细节的优化

这里说下本身的不足之处,而且如何改进它

a.滚动条bug

当选择抽屉在右边时,动画过程当中会出现滚动条,看起来让个人UI组件大打折扣,针对这个问题我打算在组件中监听drawerVisible,当它须要被展现时禁用body的滚动效果,当它不须要被展现时,还原body的展现效果

watch: {
    drawerVisible(n, o) {
      if (n == true) {
        document.documentElement.style.overflowY = "hidden";
        document.documentElement.style.overflowX = "hidden";
      }
    }
  },
复制代码
b.向下冒泡bug

在点击抽屉之外的区域能够正常关闭抽屉,可是我发现当我点击抽屉非按钮区域时,也会关闭抽屉,这是向下冒泡的bug,这个bug个人解决方案是在drawer_body上添加个无心义的事件,并阻止向上冒泡

<div
    @click.stop="clickBg_" // 注意这里
    class="drawer_body"
    :style="{ 'right':direction=='right'?'0':'auto', 'left':direction=='left'?'0':'auto', 'width':width+'px', 'background': background, 'overflow-y':scroll?'scroll':'hidden'}"
>
</div>
复制代码

2、API文档

1.属性

属性 描述 类型 默认
drawerVisible 是否显示drawer Boolean false
direction drawer方向 String left
width drawer宽度 Number 400
background drawer背景色 String #ffffff
mask 是否显示遮罩层 Boolean true
title drawer标题 Boolean true
closeBtn 是否显示关闭按钮 String ---
scroll 是否开启滚动 Boolean false

2.事件

事件 描述 返回值
close 监听关闭事件
footerOk 页脚确认绑定事件,使用默认页脚时有效
footerCal 页脚取消绑定事件,使用默认页脚时有效

3.slot

name 描述
header 页头插槽名称
default 抽屉主体部分,可省略
footer 页脚插槽名称
注意:插槽里的按钮都是使用element内置的组件,若是你的项目里没有引入element库
那最好请使用具名插槽重写页头和页脚部分
复制代码

有兴趣的同窗能够看看我以前的文章

徒手撸个vue项目框架(上)

徒手撸个vue项目框架(下)

每天喵:定制一款简洁的首页

欢迎一块儿讨论学习

相关文章
相关标签/搜索