Collapse组件在作内容折叠与展开显示的时候,仍是用到不少的。这一个组件的内容相对于Badge和Tag组件更多一点,因此打算分红三篇文章来说。javascript
第一篇先来说一讲对于高度不肯定的元素,怎么作高度的展开动画效果。css
看一下大佬对transition为何不能对height:auto实现过渡动画
的解释:html
这里贴一个css trick上对于这种问题的解决方案:Using CSS Transitions on Auto Dimensionsjava
说一下在element-ui中的实现思路吧:css3
0 逐步增大到 scrollHeight
scrollHeight 逐步减少到 0
https://jsfiddle.net/huang_ju...编程
经过自适应文本高度的输入框那篇文章,咱们已经了解到scrollHeight的高度是包含了padding的,因此在这里的动画时,须要对padding-top,padding-bottom,height三个属性都作变化。
element-ui
其次,对于展开时高度的值,须要scrollHeight - paddingSize浏览器
let Transition = { beforeEnter(el){ if(!el.dataset) el.dataset = {}; let styles = window.getComputedStyle(el); // 记录展开前的属性值 el.dataset.oldOverflow = styles.getPropertyValue('overflow'); el.dataset.oldPaddingTop = styles.getPropertyValue('padding-top'); el.dataset.oldPaddingBottom = styles.getPropertyValue('padding-bottom'); // 这三个都为0,scrollHeight的高度就是真实的内容高度了 el.style.height = 0; el.style.paddingTop = 0; el.style.paddingBottom = 0; el.classList.add('collapse-transition'); el.style.overflow = 'hidden'; }, enter(el) { if(el.scrollHeight !== 0) { // 动画过程当中,逐渐增大到展开前应占的高度值 el.style.height = el.scrollHeight + 'px'; el.style.paddingTop = el.dataset.oldPaddingTop; el.style.paddingBottom = el.dataset.oldPaddingBottom; } }, afterEnter(el) { el.classList.remove('collapse-transition'); el.style.height = ''; el.style.overflow = el.dataset.oldOverflow; } // .... }
观察jsfiddle的demo示例代码,会发现这句缓存
let Transition = { // ... leave(el) { if (el.scrollHeight !== 0) { el.style.height = 0; el.style.paddingTop = 0; el.style.paddingBottom = 0; } } // ... }
为何要加el.scrollHeight !== 0的判断呢?
函数
试一下,若是不加这个判断,直接变化height,paddingTop,paddingBottom的值到0,这个时候,收缩时并不会有过渡动画,元素立刻就消失了。
咱们能够替换一下上面的代码
let Transition = { // ... leave(el) { setTimeout(() => { el.style.height = 0; el.style.paddingTop = 0; el.style.paddingBottom = 0; }, 20) } // ... }
这时候,收缩也会有过渡动画。但当咱们尝试将延迟时间改成0的时候,并没有效,延迟时间须要大于0才会有过渡效果。因此,这个过渡动画彷佛和执行队列就没什么关联了。
接下来,咱们把目标锁定在了重绘和重排上。
着重看一下大佬的这篇文章:高性能JavaScript 重排与重绘
1.什么是重排和重绘
浏览器解析页面生成DOM树
和渲染树
,DOM树表示节点结构,渲染树表示节点如何显示。
DOM树节点的属性发生变化时,浏览器可能会从新计算去绘制渲染树,这个过程叫重排
,渲染树映射到屏幕上显示的过程就叫重绘
。
2.重排何时发生每次重排,必然会致使重绘,那么,重排会在哪些状况下发生?
能够总结为,当一个节点的位置、大小、内容发生改变,或者增删节点的状况下会发生重排。
3.渲染树变化的排队和刷新浏览器会把屡次对节点的修改“保存”起来(大多数浏览器经过队列化修改并批量执行来优化重排过程),最终一次完成!可是,有些时候你可能会(常常是不知不觉)强制刷新队列并要求计划任务当即执行
。获取布局信息的操做会致使队列刷新,好比:
能够这么理解,要获取这些值,浏览器就须要把前面缓存的重排操做先给执行了,才能计算最新的,正确的节点信息。而这种中断,打断了浏览器自身对于重排的优化,是须要咱们避免的。
4.最小化重排和重绘
虽然浏览器对重排进行了优化,但咱们不经意的操做会打断这种优化。因此,修改样式信息时,尽可能集中操做
5.fragment元素的应用前面说了,重排大多数都是因为节点位置、大小,增删形成的。节点位置,大小的改变咱们能够经过集中操做来规范以优化性能。而对于节点的增删就能够经过fragment元素,来避免频繁的重排和重绘了
。
尽可能不要在布局信息改变时作查询(会致使渲染队列强制刷新)
同一个DOM的多个属性改变能够写在一块儿(减小DOM访问,同时把强制渲染队列刷新的风险降为0)
若是要批量添加DOM,能够先让元素脱离文档流,操做完后再带入文档流,这样只会触发一次重排(fragment元素的应用)
咱们再看一下出问题的这段代码,若是不加scrollHeight的时候:
let Transition = { // ... beforeLeave(el) { // ... el.style.height = el.scrollHeight - parseFloat(el.dataset.oldPaddingTop) - parseFloat(el.dataset.oldPaddingBottom) + 'px'; el.style.overflow = 'hidden'; el.classList.add('collapse-transition'); //var tmp = el.offsetTop; }, leave(el) { el.style.height = 0; el.style.paddingTop = 0; el.style.paddingBottom = 0; }, afterLeave(el) { //... } }
咱们进行了一系列操做,浏览器缓存了这几回的重排。等到主线程结束,开始渲染的时候,直接就往下一直渲染到了height:0;这句。因此就没有了过渡动画。
而加上大于零的定时器,应该是因为,1.先运行js主线程。2.执行重排重绘。3.执行定时回调,变化height的值
能够试下,讲注释的这行代码(var tmp = el.offsetTop;)加入,也有过渡动画效果。
结合上面对重排和重绘的学习,能够很容易的理解到:这一句代码强制刷新了重排的队列。让前面设置的属性,增长的过渡效果的类名。让它先渲染好,而后运行到leave函数的时候,再变化高度值,transition就起做用了。
经过过渡效果的实现:
参考文章:
1.Using CSS Transitions on Auto Dimensions
2.css3怎么实现高度从固定到自动的过渡动画?
3.高性能JavaScript 重排与重绘