首頁 > 軟體

elementui原始碼學習仿寫el-collapse範例

2022-12-21 14:00:11

引言

本篇文章記錄仿寫一個el-collapse元件細節,從而有助於大家更好理解餓了麼ui對應元件具體工作細節。

本文是elementui原始碼學習仿寫系列的又一篇文章,後續空閒了會不斷更新並仿寫其他元件。原始碼在github上,大家可以拉下來,npm start執行跑起來,結合註釋有助於更好的理解。github倉庫地址如下:https://github.com/shuirongsh...

元件思考

el-collapse即為摺疊面板的意思,一般主要是用於:對複雜區域進行分組和隱藏,保持頁面的整潔,有分類整理的意思。

collapse有摺疊的意思,不過fold也有摺疊的意思。所以筆者這裡封裝的元件就改名字了,不叫my-collapse,叫做my-fold

元件的需求

我們先看一下下圖摺疊元件的結構圖

結合上圖已經工作經驗,大致分析元件的需求有以下:

  • 點選摺疊頭部區域展開或關閉摺疊內容體區域
  • 展開或摺疊的時候,加上過渡效果
  • 頭部區域的內容文字引數定義
  • 是否隱藏摺疊的小箭頭
  • 手風琴模式的摺疊面板(預設是都可以展開摺疊的)

元件實現之父元件統一更改所有子元件狀態

一般情況下父元件更改子元件資料狀態有以下方式:

  • 父元件傳遞資料,子元件props接收。更改父元件資料,子元件也就自動更改更新了
  • 使用this.$refs.child.xxx = yyy,給子元件打一個ref,直接更改對應值即可
  • 使用this.$children可以存取所有的子元件範例物件。所以,也可以直接更改,如下:

父元件程式碼

// html
<template>
  <div>
    <h2>下方為三個子元件</h2>
    <child1 />
    <child2 />
    <child3 />
    <button @click="changeChildData">點選按鈕更改所有子元件資料</button>
  </div>
</template>
// js
changeChildData() {
  // this.$children拿到所有子元件範例物件的陣列,遍歷存取到資料,更改之
  this.$children.forEach((child) => {
    child.flag = !child.flag;
  });
},

其中一個子元件程式碼,另外兩個也一樣

// html
<template>
  <div>child1中的flag--> {{ flag }}</div>
</template>
// js
<script>
export default {
  data() { return { flag: false } },
};
</script>

效果圖

為什麼要提到這個呢?因為手風琴模式下的摺疊面板會用到這個方式去更改別的面板,使別的面板關閉

元件實現之高度過渡效果元件的封裝

高度的過渡,主要是從0到某個高度,以及從某個高度到0的變化,需要搭配transition以及overflow屬性去控制。我們先看一下簡單的寫法和效果圖,再看一下封裝的元件的程式碼

1.簡單寫法

伸手黨福利,複製貼上即可使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .target {
            width: 120px;
            height: 120px;
            line-height: 120px;
            text-align: center;
            background-color: #baf;
            /* 以下兩個是必要的樣式控制屬性 */
            transition: height 0.2s linear;
            overflow: hidden;
        }
    </style>
</head>
<body>
    <button>點選高度變化</button>
    <br>
    <br>
    <div class="target">過渡的dom</div>
    <script>
        let isOpen = true // 初始情況下,標識狀態為開啟狀態
        let btn = document.querySelector('button')
        let targetDom = document.querySelector('.target')
        btn.onclick = () => {
            // 若為展開狀態,就將其高度置為0,因為css有過渡程式碼,所以高度過渡效果就出來了
            if (isOpen) { 
                targetDom.style.height = 0 + 'px'
                isOpen = false
            } 
            // 若為關閉狀態,就將其高度置為原來,因為css有過渡程式碼,所以高度過渡效果就出來了
            else { 
                targetDom.style.height = 120 + 'px'
                isOpen = true
            }
        }
    </script>
</body>
</html>

2.簡單寫法效果圖

在我們封裝摺疊面板的時候,這個高度變化的過渡元件是必須要有的,沒有的話,摺疊面板展開關閉時,會有點突兀,加上一個元件,會絲滑不少。

3.摺疊元件的封裝

理解了上述的簡單案例,再將其思路應用到元件封裝中去即可

高度元件封裝程式碼思路:

根據show變數的標識,去更改dom.style.height;

初始載入時,獲取初始高度`dom.offsetHeight更改一次、當show變數標識發生變化的時候,再更改一次。

同時搭配高度的transition樣式控制即可(即:監聽props中show`標識的變化更改之)

封裝好的高度過渡元件程式碼如下:

<template>
  <div class="transitionWrap" ref="transitionWrap">
    <slot></slot>
  </div>
</template>
<script>
export default {
  props: {
    // 布林值show標識關閉還是展開
    show: Boolean,
  },
  data() {
    return {
      height: 0,
    };
  },
  mounted() {
    /* dom載入完畢,然後根據標識show去手動更新高度 */
    this.$nextTick(() => {
      this.height = this.$refs.transitionWrap.offsetHeight;
      this.$refs.transitionWrap.style.height = this.show
        ? `${this.height}px`
        : 0;
    });
    // this.$nextTick().then(() => { ... }
  },
  watch: {
    /* 再監聽標識的變化,從而更改高度,即關閉還是展開 */ 
    show(newVal) {
      this.$refs.transitionWrap.style.height = newVal ? `${this.height}px` : 0;
    },
  },
};
</script>
<style scoped>
/* 關鍵css樣式,高度線性勻速過渡 */
.transitionWrap {
  transition: height 0.2s linear;
  overflow: hidden;
}
</style>

 另外餓了麼UI也提供了el-collapse-transition元件,也是一個不錯的選擇

關於元件中的role屬性和aria-multiselectable等

封裝一套強大的開源元件其實要考慮的東西很多,比如需要適配螢幕閱讀器,我們看一下餓了麼UI的el-collapse元件使用到的兩個螢幕閱讀器屬性rolearia-multiselectable。如下圖:

  • role屬性是html中語意化標籤的進一步補充(如 螢幕閱讀器,給盲人使用),另舉一個例子
  • <div role="checkbox" aria-checked="checked" /> 高度螢幕閱讀器,此處有一個核取方塊,而且已經被選中了
  • aria-multiselectable='true'告知輔助裝置,一次可以展開多個項,還是隻能展開一個

詳情 css http://edu.jb51.net/jqueryui/jqueryui-intro.html

由此可以看出,一套開源的元件,的確是方方面面都要考慮到。

封裝的元件

我們先看一下效果圖

封裝的效果圖

使用自己封裝的摺疊元件

<template>
  <div>
    <!-- 手風琴模式 -->
    <my-fold v-model="openArr" accordion @change="changeFn">
      <my-fold-item title="第一項" name="one">我是第一項的內容</my-fold-item>
      <my-fold-item title="第二項" name="two">
        <p>我是第二項的內容</p>
        <p>我是第二項的內容</p>
      </my-fold-item>
      <my-fold-item title="第三項" name="three">
        <p>我是第三項的內容</p>
        <p>我是第三項的內容</p>
        <p>我是第三項的內容</p>
      </my-fold-item>
      <my-fold-item title="第四項" name="four">
        <p>我是第四項的內容</p>
        <p>我是第四項的內容</p>
        <p>我是第四項的內容</p>
        <p>我是第四項的內容</p>
      </my-fold-item>
    </my-fold>
    <br />
    <!-- 可展開多個模式 -->
    <my-fold v-model="openArr2" @change="changeFn">
      <my-fold-item title="第一項" name="one">我是第一項的內容</my-fold-item>
      <my-fold-item title="第二項" name="two">
        <p>我是第二項的內容</p>
        <p>我是第二項的內容</p>
      </my-fold-item>
      <my-fold-item title="第三項" name="three">
        <p>我是第三項的內容</p>
        <p>我是第三項的內容</p>
        <p>我是第三項的內容</p>
      </my-fold-item>
      <my-fold-item title="第四項" name="four">
        <p>我是第四項的內容</p>
        <p>我是第四項的內容</p>
        <p>我是第四項的內容</p>
        <p>我是第四項的內容</p>
      </my-fold-item>
    </my-fold>
  </div>
</template>
<script>
export default {
  data() {
    return {
      // 手風琴模式的陣列項要麼沒有項,要麼只能有一個項
      openArr: [],
      // 可展開多個的陣列,可以有多個項
      openArr2: ["one", "two"],
    };
  },
  methods: {
    changeFn(name, isOpen, vNode) {
      console.log(name, isOpen, vNode);
    },
  },
};
</script>

my-fold元件

<template>
  <div class="myFoldWrap">
    <slot></slot>
  </div>
</template>
<script>
export default {
  name: "myFold",
  props: {
    // 是否開啟手風琴模式(每次只能展開一個面板)
    accordion: {
      type: Boolean,
      default: false, // 預設不開啟(可展開多個)
    },
    // 父元件v-model傳參,子元件props中key為'value'接收,'input'事件更改
    value: {
      type: Array, // 手風琴模式的陣列項只能有一個,反之可以有多個
      default() {
        return [];
      },
    },
  },
  data() {
    return {
      // 展開的項可一個,可多個(使用層v-model陣列傳的有誰,就展開誰)
      openArr: this.value, // 收集誰需要展開
    };
  },
  mounted() {
    // 手動加一個校驗
    if (this.accordion & (this.value.length > 1)) {
      console.error("手風琴模式下,繫結的陣列最多一項");
    }
  },
  watch: {
    // 監聽props中value的變化,及時更新
    value(value) {
      this.openArr = value;
    },
  },
  methods: {
    updateVModel(name, isOpen, vNode) {
      // 若為手風琴模式
      if (this.accordion) {
        // 當某一項開啟的時候,才去關閉其他項
        isOpen ? this.closeOther(name) : null;
        this.openArr = [name]; // 手風琴模式只保留(展開)一個
      }
      // 若為可展開多項模式
      else {
        let i = this.openArr.indexOf(name);
        // 包含就刪掉、不包含就追加
        i > -1 ? this.openArr.splice(i, 1) : this.openArr.push(name);
      }
      // 無論那種模式,都需要更改並通知外層使用元件
      this.$emit("input", this.openArr); // input事件控制v-model的資料更改
      this.$emit("change", name, isOpen, vNode); // change事件丟擲去,供使用者使用
    },
    closeOther(name) {
      this.$children.forEach((item) => {
        // 將除了自身以外的都置為false,故其他的就都摺疊上了
        if (item.name != name) {
          item.isOpen = false;
        }
      });
    },
  },
};
</script>
<style lang="less" scoped>
.myFoldWrap {
  border: 1px solid #e9e9e9;
}
</style>

my-fold-item元件

<template>
  <div class="foldItem">
    <!-- 頭部部分,主要是點選時展開內容,以及做小箭頭的旋轉,和頭部的標題呈現 -->
    <div class="foldItemHeader" @click="handleHeaderClick">
      <i
        v-if="!hiddenArrow"
        class="el-icon-arrow-right"
        :class="{ rotate90deg: isOpen }"
      ></i>
      {{ title }}
    </div>
    <!-- 內容體部分,主要是展開摺疊時加上高度過渡效果,這裡封裝了一個額外的工具元件 -->
    <transition-height class="transitionHeight" :show="isOpen">
      <div class="foldItemBody">
        <slot></slot>
      </div>
    </transition-height>
  </div>
</template>
<script>
import transitionHeight from "@/components/myUtils/transitionHeight/index.vue";
export default {
  name: "myFoldItem",
  components: {
    transitionHeight, // 高度過渡元件
  },
  props: {
    title: String, // 摺疊面板的標題
    name: String, // 摺疊面板的名字,即為唯一識別符號(不可與其他重複!)
    // 是否隱藏小箭頭,預設false,即展示小箭頭
    hiddenArrow: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      // true為展開即open,false為摺疊
      // 初始情況下取到父元件myFold元件的展開的陣列,看看自身是否在其中
      isOpen: this.$parent.openArr.includes(this.name),
    };
  },
  methods: {
    // 點選展開或摺疊
    handleHeaderClick() {
      this.isOpen = !this.isOpen; // 內容依託於變數isOpen直接更新即可
      this.$parent.updateVModel(this.name, this.isOpen, this); // 於此同時也要通知父元件去更新
    },
  },
};
</script>
<style lang="less" scoped>
.foldItem {
  width: 100%;
  height: auto; // 高度由內容區撐開
  .foldItemHeader {
    box-sizing: border-box;
    padding-left: 8px;
    min-height: 50px;
    display: flex;
    align-items: center;
    background-color: #fafafa;
    cursor: pointer;
    border-bottom: 1px solid #e9e9e9;
    // 展開摺疊項時,小圖示旋轉效果
    i {
      transform: rotate(0deg);
      transition: all 0.24s;
      margin-right: 8px;
    }
    .rotate90deg {
      transform: rotate(90deg);
      transition: all 0.24s;
    }
  }
  .foldItemBody {
    width: 100%;
    height: auto;
    box-sizing: border-box;
    padding: 12px 12px 12px 8px;
    border-bottom: 1px solid #e9e9e9;
  }
}
// 去除和父元件的邊框重疊
.foldItem:last-child .foldItemHeader {
  border-bottom: none !important;
}
.foldItem:last-child .transitionHeight .foldItemBody {
  border-top: 1px solid #e9e9e9;
  border-bottom: none !important;
}
</style>

 上述程式碼結合註釋,更好的理解哦

以上就是elementui原始碼學習仿寫el-collapse範例的詳細內容,更多關於elementui仿寫el-collapse的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com