elementui源码学习仿写el-collapse示例

作者:水冗水孚 时间:2024-05-09 15:25:03 

引言

本篇文章记录仿写一个el-collapse组件细节,从而有助于大家更好理解饿了么ui对应组件具体工作细节。

本文是elementui源码学习仿写系列的又一篇文章,后续空闲了会不断更新并仿写其他组件。源码在github上,大家可以拉下来,npm start运行跑起来,结合注释有助于更好的理解。github仓库地址如下:https://github.com/shuirongsh...

组件思考

el-collapse即为折叠面板的意思,一般主要是用于:对复杂区域进行分组和隐藏,保持页面的整洁,有分类整理的意思。

collapse有折叠的意思,不过fold也有折叠的意思。所以笔者这里封装的组件就改名字了,不叫my-collapse,叫做my-fold

组件的需求

我们先看一下下图折叠组件的结构图

elementui源码学习仿写el-collapse示例

结合上图已经工作经验,大致分析组件的需求有以下:

  • 点击折叠头部区域展开或关闭折叠内容体区域

  • 展开或折叠的时候,加上过渡效果

  • 头部区域的内容文字参数定义

  • 是否隐藏折叠的小箭头

  • 手风琴模式的折叠面板(默认是都可以展开折叠的)

组件实现之父组件统一更改所有子组件状态

一般情况下父组件更改子组件数据状态有以下方式:

  • 父组件传递数据,子组件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>

效果图

elementui源码学习仿写el-collapse示例

为什么要提到这个呢?因为手风琴模式下的折叠面板会用到这个方式去更改别的面板,使别的面板关闭

组件实现之高度过渡效果组件的封装

高度的过渡,主要是从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.简单写法效果图

elementui源码学习仿写el-collapse示例

在我们封装折叠面板的时候,这个高度变化的过渡组件是必须要有的,没有的话,折叠面板展开关闭时,会有点突兀,加上一个组件,会丝滑不少。

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。如下图:

elementui源码学习仿写el-collapse示例

  • role属性是html中语义化标签的进一步补充(如 屏幕阅读器,给盲人使用),另举一个例子

  • <div role="checkbox" aria-checked="checked" /> 高度屏幕阅读器,此处有一个复选框,而且已经被选中了

  • aria-multiselectable='true'告知辅助设备,一次可以展开多个项,还是只能展开一个

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

由此可以看出,一套开源的组件,的确是方方面面都要考虑到。

封装的组件

我们先看一下效果图

封装的效果图

elementui源码学习仿写el-collapse示例

使用自己封装的折叠组件

<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>

 上述代码结合注释,更好的理解哦

来源:https://segmentfault.com/a/1190000042397748

标签:elementui,el-collapse,仿写
0
投稿

猜你喜欢

  • Oracle以逗号分隔的字符串拆分为多行数据实例详解

    2024-01-13 16:32:46
  • MySQL的id关联和索引使用的实际优化案例

    2024-01-26 03:43:30
  • 微信小程序(订阅消息)功能

    2024-04-28 09:36:48
  • Go语言在Linux环境下输出彩色字符的方法

    2024-01-30 12:02:36
  • php版银联支付接口开发简明教程

    2023-11-24 00:27:41
  • pytest之assert断言的具体使用

    2021-11-21 18:04:26
  • Pandas DataFrame操作数据增删查改

    2022-07-10 09:37:39
  • Vuex实现购物车小功能

    2024-05-21 10:29:07
  • SQL Server端口更改后的数据库连接方式

    2008-12-29 14:11:00
  • Python实现机器学习算法的分类

    2023-08-31 05:50:36
  • MySQL transaction事务安全示例讲解

    2024-01-25 17:47:38
  • python实现关闭第三方窗口的方法

    2023-11-01 17:25:09
  • Python qqbot 实现qq机器人的示例代码

    2021-05-18 12:43:43
  • 利用python中pymysql操作MySQL数据库的新手指南

    2024-01-24 22:09:49
  • asp 类型转换函数大全第1/2页

    2011-04-07 11:06:00
  • Node.js Domain 模块实例详解

    2024-05-08 09:35:15
  • python实现画出e指数函数的图像

    2023-09-24 22:12:10
  • Golang实现短网址/短链服务的开发笔记分享

    2024-02-08 15:37:18
  • python实现批量修改服务器密码的方法

    2021-09-11 01:23:09
  • Golang HTTP服务超时控制实现原理分析

    2024-05-08 10:52:16
  • asp之家 网络编程 m.aspxhome.com