display中的transition实现
这里讨论的transition
,是针对于display:none/block;
下的实现或替代性方案。
因为过渡是基于数值和时间来计算的,比如长度、颜色、角度等属性值,是可以在单位时间内变化一定数值,从而达到过渡的动画效果。
像我们常见的opacity/width/height/margin/padding
等属性,都是可以应用过渡效果的,然而display
这个属性比较尴尬,两种状态,要么显示,要么隐藏,因此无法应用transition
效果。
下面探究一下如何变相地实现显隐过渡效果。
1. 原生js实现显隐transition
方案一:visibility+opacity
对于display
属性无法应用过渡的问题,可以使用visibility
来代替。
虽然visibility
也是两种状态hidden
和visible
,但这个属性比较特别,hidden
相当于0,visible
相当于1,从hidden
到visible
的转化是可以过渡的,只是并不会有动画效果。
也就是说,设置了transition: visibility 1s linear;
,当其visibility
值变为hidden
时,元素并不会马上消失,而是经过1s后,突然消失,也不会有渐隐的动画效果。
因此,我们需要配合opacity
来做到渐隐渐显的过渡效果。
<button id="btn">点击显隐</button>
<div id="box" class=""><div>
<style>
#box{
width: 100px;
height: 100px;
background-color: green;
visibility: visible;
opacity: 1;
transition: all 1s linear;
}
.hide{
opacity: 0;
visibility: hidden;
}
</style>
<script>
var box = document.getElementById("box");
var btn = document.getElementById("btn");
btn.onclick = function(){
if(box.className.indexOf("hide") != -1){
box.className = "";
}else{
box.className += "hide";
}
};
</script>
两个问题:
- 虽然单独使用
opacity
也可以达到效果,但是这样的盒子,不能透过它去触发下面元素的点击事件,即使它看不见了,也会造成阻碍。而设置了visibility
为hidden
,就不影响其他元素的点击事件。 - 使用这种方案,并不能真正的隐藏元素,依旧会在文档流中占据位置,在页面上留下一块空白。并且
visibility
属性在IE9及以下的浏览器不支持。
方案二:timeout
前面提到display
属性不支持过渡,那么我们可以让其他可见的属性过渡,并与display
值的变化分开执行,看起来就像display
过渡了一样。
需要注意:
box.style.opacity = 0;
box.style.display = "none";
这样不可行,因为浏览器的执行机制,两步其实会合并执行,元素会直接从文档流中移除,因此根本看不到透明度的过渡变化。
我们可以使用定时器,强制拆开这两步。
<button id="btn">点击显隐</button>
<div id="box" class=""><div>
<style>
#box{
width: 100px;
height: 100px;
background-color: green;
opacity: 1;
transition: all 1s linear;
}
</style>
<script>
var box = document.getElementById("box");
var btn = document.getElementById("btn");
btn.onclick = function(){
if(getComputedStyle(box,null)["display"] == "block"){
box.style.opacity = 0;
setTimeout(function(){
box.style.display = "none";
},1000);//这里因为要等待过渡的1s,然后才消失
}else{
setTimeout(function(){
box.style.display = "block";
});//因为紧接着要过渡,所以delay为0
box.style.opacity = 1;
}
};
</script>
方案三:transitionend
当transition
动画结束时,会触发一个事件,叫做transitionend
,我们可以用它替换定时器。
box.style.opacity = 0;
box.addEventListener("transitionend", function(e){
//propertyName是触发事件相关联的属性,详见MDN
if(e.propertyName == "opacity"){
box.style.display = "none";
}
});
2. jQuery的显隐transition
在jQuery中做到这个过渡效果非常简单,使用animate
即可,内部已经自动给我们处理好了。
//toggle是控制显隐的按钮
//form是需要显隐的元素
$('.toggle').click(function(){
$('.form').animate({
//'padding-top': 'toggle',
//'padding-bottom': 'toggle',
opacity: "toggle",
height: "toggle"
}, 'slow');
});
不需要我们主动判断display
的值,只要使用了toggle
作为动画效果值,jQuery
会自动切换show
和hide
,并且这里还可以看到透明度,高度同时变化。
如果是两个元素连续排列,初始display
状态分别为block
和none
,只需要给它们设置类名都为form
,当点击按钮时,会交替显示元素,并且切换的过渡效果相当自然,常用来做登录和注册的表单显示切换。
3. 框架内部的显隐transition
其实框架内部的过渡动画效果,底层都是用CSS3中transition
和animation
实现的。
3.1 Vue的过渡
Vue2.0的过渡是一个内置组件,transition
元素会把过渡效果应用到其包裹的内容上,而不会额外渲染DOM元素。
使用方式:
data:{show:true}
<input type="button" value="点击隐藏显示" @click="show=!show">
<transition name='fade'>
<div v-show='show'></div>
</transition>
给transition
指定name
为fade
后,将会自动生成几个过渡状态的class
:
.fade-enter{}
—— 出现的初始状态.fade-enter-active{}
—— 当元素进入,变化过渡的趋向状态.fade-leave{}
—— 离开的初始状态(通常不使用).fade-leave-active{}
—— 当元素离开,变化过渡的趋向状态
一般将过渡效果transition
设置给active:
.fade-enter-active,.fade-leave-active{
transition: all .5s linear;
}
.fade-enter{
opacity: 0;
}
当然,我们也可在Vue中使用第三方css动画效果库。
3.2 Angular的过渡
这里使用的版本是Angular1
,用到了angular-animate.js
插件。
<html ng-app="myapp">
<body ng-controller="test" ng-init="showFlag = false">
<button id="btn">点击显隐</button>
<div id="box" class="myanimate" ng-show="showFlag"><div>
</body>
</html>
<style>
#box{
width: 100px;
height: 100px;
background-color: green;
}
.myanimate.ng-hide-add,
.myanimate.ng-hide-remove {
transition: all linear 0.5s;
}
.myanimate.ng-hide{
opacity: 0;
}
</style>
<script src="https://cdn.bootcss.com/angular.js/1.7.0/angular.js"></script>
<script src="https://cdn.bootcss.com/angular.js/1.7.0/angular-animate.js"></script>
<script>
angular.module("myapp",['ngAnimate']).controller("test",function($scope){});
</script>
因为angular-animate
插件,在元素向ng-hide
的状态切换时,会给该元素自动添加两个class:ng-hide-add/ng-hide-remove
,分别代表向ng-hide
转换和从ng-hide
变为其他状态。
所以我们可以给这两个class设置transition
,再给ng-hide
设置一个最终的消失状态,这样就有了过渡效果。与上面Vue
中的例子类似。
myanimate
是我自己加的class,使用了交集选择器,是为了精确定位需要应用过渡效果的元素。
个人感觉Angular
中的过渡变化相对于Vue
来说比较复杂,不同的指令还有不同的animation class
类,需要对照文档来使用。
详细介绍见官方文档。
我们也可以使用Angular中现成的第三方css动画效果库:ngAnimate.css,通过增删class很方便地控制过渡效果。
3.3 collapse效果实现
有时候有这样的需求,类似collapse
的过渡效果,当点击按钮,下拉选项框会往上收拢消失或向下扩张显示,我们用display
的过渡,只能实现透明度的变化,而不能做到高度的过渡效果,因为display:none;
的元素宽高都是auto
。
transition: all 1s linear;
的结果就是,经过1秒,元素的透明度由1变为0,紧接着突然消失,然后下面的元素盒子由于有了空间,迅速顶上来,让人觉得很突兀。
我想到的有两种纯css的实现方案:
正常的设置过渡效果,然后给该过渡元素设置浮动,当元素显示时,下面的盒子需要设置
margin-top
为该元素的高度,元素隐藏时,给下面盒子移除掉margin-top
属性,同时给下面盒子也设置过渡,这样相当于用下面盒子帮助实现了collapse
的效果。<div class="form myanimate" ng-show="showFlag"></div> <div class="table" ng-class="{'mt':showFlag}" style="width: 500px;height: 300px;background-color: green;"></div> <style> .myanimate.ng-hide-add, .myanimate.ng-hide-remove { transition: all linear 0.5s; } .myanimate.ng-hide{ opacity: 0; } .form{ float: left; } .table{ transition: all 0.5s linear; /*提高层级,以免被浮动遮住*/ position: relative; } .mt{ margin-top: 96px; } </style>
舍弃
ng-animate
的过渡效果,直接添加class
类,当ng-hide
状态时,让元素高度变为0,并且设置transition
,简单粗暴有效。<div class="form" ng-show="showFlag" ng-class="{'end': !showFlag}"> <style> .form{ transition: all linear 0.5s; max-height: 96px; /*稍微大一点也没事*/ overflow: hidden; opacity: 1; } .end{ max-height: 0; opacity: 0; } </style>