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

目录
  • 引言
  • 组件思考
    • 组件的需求
  • 组件实现之父组件统一更改所有子组件状态
  • 组件实现之高度过渡效果组件的封装
  • 关于组件中的role属性和aria-multiselectable等
  • 封装的组件
    • 封装的效果图
    • 使用自己封装的折叠组件
  • my-fold组件
    • my-fold-item组件

引言

本篇文章记录仿写一个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的资料请关注我们其它相关文章!

(0)

相关推荐

  • element ui中表单el-form的label如何设置宽度

    目录 element ui中表单el-form的label设置宽度 在el-form-item 重置label宽度 element ui中表单el-form的label设置宽度 测试要求把输入框前面的名字带括号的把括号部分显示在下一行,可以通过设置前面的label宽度来进行换行. 在具体的el-form-item中设置label-width, 也可以为整个表单el-form设置label-width,这样整个页面的label宽度统一. 例: <el-form label-width="10

  • ElementUI Upload源码组件上传流程解析

    目录 引言 源文件 流程图 1️⃣. 获取文件 2️⃣. 文件个数校验 校验文件最大个数 非多文件情况处理 3️⃣. 构造FileItem对象 FileItem对象 4️⃣. 上传阶段 beforeUpload前置操作 beforeUpload中断情况 上传 Uploading

  • 如何处理elementUI中表格多选框禁用的问题

    目录 处理elementUI中表格多选框禁用 elementUI多选表格禁用某一行不被选择 处理elementUI中表格多选框禁用 在 el-table-column type 类型为 selection 组件中,添加 :selectable='方法名' <el-table :data="tableData" v-loading="loading" max-height="570" stripe :header-cell-style=&qu

  • element ui循环调用this.$alert 消息提示只显示最后一个

    目录 需求背景 问题分析 MessageBox 类的实现 查看 showNextMsg 方法的实现 DEMO演示 需求背景 有一个需求,使用element-ui 中的$alert 方法提示 用户几条信息,不能一次性提示.仅能一条一条的提示,提示完第一条,点击确定后如果还有待提示消息,就弹出提示第二条,以此类推,直到消耗完所有需要提示的消息结束:现在模拟复现一下这个需求的期望如下图: 那这还不简单??? 我们听完需求心中已经想好了代码怎么写了.于是我们摩拳擦掌 说干就干, 一顿 C & V 操作,

  • 去除element-ui中Dialog对话框遮罩层方法详解

    目录 前言 modal 属性 示例代码: 前言 本文主要介绍了如何去除 element-ui 中 Dialog 对话框遮罩层的方法,并给出了示例代码以及页面效果作为参考. modal 属性 在使用element-ui的Dialog时,默认会有遮罩层,想要去除遮罩层,官方文档给出了 modal 属性: modal 是一个布尔值,表示是否需要遮罩层: 然而,将 modal 赋值为 false 时并不生效: 这里官方文档也给出了提示: 当 modal 的值为 false 时,请一定要确保 append

  • element ui富文本编辑器的使用效果与步骤(quill-editor)

    目录 效果展示:(可以上传图片及其视频) 第一步.首先安装富文本编辑器插件 第二步.然后在main.js文件中,全局注册 第三步.在vue界面中使用quill-editor 第四步:配置video.js(要有上传视频且回显的功能需要配置) 总结 效果展示:(可以上传图片及其视频) 可以拖拽图片大小及其位置 第一步.首先安装富文本编辑器插件 cnpm install vue-quill-editor --save cnpm install quill-image-drop-module --sav

  • elementui源码学习仿写el-link示例详解

    目录 正文 组件思考 组件的需求 组件的效果图 组件实现分析 给link组件加上链接样式 给link组件加上鼠标悬浮时下划线 通过传参控制是否加上下划线(即:是否加上这个下划线类名) 使用v-bind="$attrs"兜底a标签的其他的未在props中声明的参数 代码 使用代码 封装组件代码 正文 本篇文章记录仿写一个el-link组件细节,从而有助于大家更好理解饿了么ui对应组件具体工作细节.本文是elementui源码学习仿写系列的又一篇文章,后续空闲了会不断更新并仿写其他组件.源

  • elementui源码学习之仿写一个el-divider组件

    目录 正文 组件需求分析 组件中用到的知识点 函数式组件 函数式组件的两种定义方式 解决一像素太粗的问题 组件封装 组件封装的效果图 组件封装的代码 正文 本篇文章记录仿写一个el-divider组件细节,从而有助于大家更好理解饿了么ui对应组件具体工作细节.本文是elementui源码学习仿写系列的又一篇文章,后续空闲了会不断更新并仿写其他组件.源码在github上,大家可以拉下来,npm start运行跑起来,结合注释有助于更好的理解. github仓库地址如下:github.com/shu

  • Vue源码学习记录之手写vm.$mount方法

    目录 一.概述 二.使用方式 三.完整版vm.$mount的实现原理 四.只包含运行时版本的vm.$mount的实现原理 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.概述 在我们开发中,经常要用到Vue.extend创建出Vue的子类来构造函数,通过new 得到子类的实例,然后通过$mount挂载到节点,如代码: <div id="mount-point"></div> <!-- 创建构造器 --> var Profile =

  • vue 2.5.1 源码学习 之Vue.extend 和 data的合并策略

    1. 子类父类 2.Vue.extend()      //创建vue的子类 组件的语法器 Vue.extend(options) Profile().$mount('#app') // 挂在app上,并替换app 新建 initExend ==> Vue.extend 3. strat.data ==> if(!vm){子组件中data的值是一个方法function ==> mergeDataorFn()} // 数据的合并 ==> else {} //通过实例绑定的data 实

  • Bootstrap源码学习笔记之bootstrap进度条

    基本样式 要实现进度条效果要使用两个容器,外容器使用"progress"样式,子容器使用"progress-bar"样式.例如: <div class="progress"> <div class="progress-bar" style="width:40%"></div> </div> progress样式主要设置进度条容器的背景色,容器高度.间距等,pr

  • Android源码学习之组合模式定义及应用

    组合模式定义: Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly. 将对象组合成树形结构以表示"部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性. 如上图所示(截取自<Head First De

  • 通过JDK源码学习InputStream详解

    概况 本文主要给大家介绍了通过JDK源码学习InputStream的相关内容,JDK 给我们提供了很多实用的输入流 xxxInputStream,而 InputStream 是所有字节输入流的抽象.包括 ByteArrayInputStream .FilterInputStream .BufferedInputStream .DataInputStream 和 PushbackInputStream 等等.下面话不多说了,来一起看看详细的介绍吧. 如何阅读JDK源码. 以看核心虚拟机(hotsp

  • 详解Vue源码学习之双向绑定

    原理 当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter.Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器. 上面那段话是Vue官方文档中截取的,可以看到是使用Object.defineProperty实现对数据改变的监听.Vue主要使用了观

  • Vue源码学习之响应式是如何实现的

    目录 前言 一.一个响应式系统的关键要素 1.如何监听数据变化 2.如何进行依赖收集--实现 Dep 类 3.数据变化时如何更新--实现 Watcher 类 二.虚拟 DOM 和 diff 1.虚拟 DOM 是什么? 2.diff 算法--新旧节点对比 三.nextTick 四.总结 前言 作为前端开发,我们的日常工作就是将数据渲染到页面➕处理用户交互.在 Vue 中,数据变化时页面会重新渲染,比如我们在页面上显示一个数字,旁边有一个点击按钮,每次点击一下按钮,页面上所显示的数字会加一,这要怎么

  • Python内建类型str源码学习

    目录 引言 1 Unicode 2 Python中的Unicode 2.1 Unicode对象的好处 2.2 Python对Unicode的优化 3 Unicode对象的底层结构体 3.1 PyASCIIObject 3.2 PyCompactUnicodeObject 3.3 PyUnicodeObject 3.4 示例 4 interned机制 5 总结 引言 “深入认识Python内建类型”这部分的内容会从源码角度为大家介绍Python中各种常用的内建类型. 在介绍常用类型str之前,在上

随机推荐