如何給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項目框架(下)

每天喵:定製一款簡潔的首頁

歡迎一塊兒討論學習

相關文章
相關標籤/搜索