你需要了解的知识:
- display:none无法应用transition效果,甚至还会有破坏作用 因为 浏览器渲染dom时,如果某个节点是 display: none 的,那么就不会在渲染树中显示,而且display:none会引发回流,改变布局,自然也就不会有过渡过程了
- opacity化的元素配合transition依然可以点击触发事件,hover效果等,仅仅用opcityl来过渡动画是不行的, 有人说可以结合visibility:visibile/hidden ,但是这中隐藏的元素是会占位的,此种方法也不可取
那我们隐藏节点的时候怎么添加动画呢?
我们以一个弹框显示隐藏的案例,来看以下几种方案的优劣。【以下方案都是伪代码,只讨论实现方式】
方案1:使用vue的transiton内置动画配合v-if/v-show来控制显示隐藏【推荐】
vue会相应地插入或删除 dom元素
方案2:opacity配合 scale3d来控制弹框显隐**
// 动画过渡
.dialogMask{
transition: all .3s ease-in;
}
//弹框隐藏
.hide{
opacity: 0;
transform: scale3d(1, 1, 0);
}
//弹框显示
.show{
opacity: 1;
transform: scale3d(1, 1, 1);
}
opacity控制过渡动画 scale Z轴隐藏时变为0是为了删除dialogMask内部的节点。这样当弹框隐藏时就只有一个外部的节点,可以减少初始时渲染的开销
注意点:假如隐藏时让内部内容立马隐藏,外部遮罩慢慢隐藏,可以给内部内容加wx:if="{{show}}"
此方法适用于原生js或者小程序
方案3:display配合setTimeout
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>显示隐藏动画</title>
<style>
.model {
width: 100px;
height: 100px;
background: #787878;
margin: 0 auto;
transition: all .3s;
}
</style>
</head>
<body>
<button onclick='dianji()'>点击</button>
<div class="model">
内容
</div>
<script>
let model = document.querySelector('.model')
function dianji() {
if (model.style.display === 'none') {
model.style.display = 'block'
// 宏任务特性
setTimeout(() => {
model.style.opacity = 1
}, 0)
} else {
model.style.opacity = 0
setTimeout(() => {
model.style.display = 'none'
}, 300);
}
}
</script>
</body>
</html>
注意:显示时加动画是利用宏任务的特性setTimeout(()=>{},0) 隐藏时setTimeout需要设置和transition过渡的时常一样。当然你也可以监听transitionend /animationend动画结束事件,当结束时隐藏或者移除元素
方案4:添加移除dom时添加动画
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>删除移除元素时添加动画</title>
</head>
<style>
#app {
width: 100px;
height: 100px;
transition: all 300ms cubic-bezier(0.23, 1, 0.32, 1);
transform-origin: center top;
border: 1px solid sienna;
text-align: center;
line-height: 100px;
margin-top: 15px;
}
.show {
opacity: 1;
transform: scaleY(1);
}
.hide {
opacity: 0;
transform: scaleY(0);
}
</style>
<body>
<button id="btn">点击</button>
<div id="app" class="show">
内容
</div>
<script>
let element = document.getElementById("app");
let btn = document.getElementById('btn')
let copy = null
function transition(ele, fn) {
console.log(1)
ele.addEventListener("transitionend", () => {
if (document.body.contains(ele)) {
document.body.removeChild(ele)
fn()
}
}, false);
}
btn.addEventListener('click', () => {
if (!document.body.contains(element)) {
let ele = document.body.appendChild(copy)
setTimeout(() => {
ele.classList.replace("hide", "show");
element = ele
}, 0)
} else {
element.classList.replace("show", "hide");
copy = element.cloneNode(true); //深克隆
transition(element, () => {
element = null
})
}
})
</script>
</body>
</html>
预览
方案5:用动画库来处理过渡动画
//startMove是封装好的动画库
let model = document.querySelector('.model')
function dianji() {
if (model.style.display === 'none') {
model.style.display = 'block'
startMove(model, {
opacity: 100
})
} else {
startMove(model, {
opacity: 0
}, () => {
model.style.display = 'none'
})
}
}
movejs
function startMove(obj, json, fn) {
clearInterval(obj.iTimer);
var iCur = 0;
var iSpeed = 0;
obj.iTimer = setInterval(function () {
var iBtn = true;
for (var attr in json) {
var iTarget = json[attr];
if (attr == 'opacity') {
iCur = Math.round(css(obj, 'opacity') * 100);
} else {
iCur = parseInt(css(obj, attr));
}
iSpeed = (iTarget - iCur) / 8;
iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
if (iCur != iTarget) {
iBtn = false;
if (attr == 'opacity') {
obj.style.opacity = (iCur + iSpeed) / 100;
obj.style.filter = 'alpha(opacity=' + (iCur + iSpeed) + ')';
} else {
obj.style[attr] = iCur + iSpeed + 'px';
}
}
}
if (iBtn) {
clearInterval(obj.iTimer);
fn && fn.call(obj);
}
}, 30);
}
function css(obj, attr) {
// 兼容ie
if (obj.currentStyle) {
return obj.currentStyle[attr];
} else {
// 第二个参数表示的是:after、:before之类的伪类 如果不用伪类的话设置为null即可
return getComputedStyle(obj, false)[attr];
}
}
参考文章: