场景描述

在使用JS控制动画时一般需要在动画结束后执行回调去进行DOM的相关操作,所以需要监听动画结束进行回调。JS提供了以下事件用于监听动画的结束,简单总结学习下。

CSS3动画监听事件

transitionEnd事件

transitionEnd事件会在CSS transition动画结束后触发。

动画结束后触发监听事件

<!DOCTYPE html>
<html>
<head>
<title>transtionend demo</title>
<style type="text/css">
*{margin:0;padding: 0;}
.demo{
margin:100px;
width:100px;
height: 100px;
background-color: #ddc;
transition: all 0.5s ease-out;
}
.demo:hover{
width: 200px;
}
</style>
</head>
<body>
<div id="demo" class="demo">
鼠标移入
</div>
<script type="text/javascript">
var element = document.getElementById('demo')
element.addEventListener('transitionend', handle, false)
function handle(){
alert('transitionend事件触发')
}
</script>
</body>
</html>

事件多次触发问题

当存在多个属性过渡变化时,结束时会多次触发transitionend事件。看个例子:

当过渡结束时,width和background-color都发生变化,会触发两次transionend事件

<!DOCTYPE html>
<html>
<head>
<title>transtionend demo</title>
<style type="text/css">
*{margin:0;padding: 0;}
.demo{
width:100px;
height: 100px;
background-color: #ddc;
transition: all 0.5s ease-out;
}
.w200{
width: 200px;
background-color: #fef;
}
</style>
</head>
<body>
<div id="demo" class="demo" onmouseover="change()" onmouseout="change()">
</div>
<script type="text/javascript">
var element = document.getElementById('demo')
element.addEventListener('transitionend', handle, false)
function handle(){
alert('transitionend事件触发')
}
function change() {
element.className = element.className === 'demo' ? 'demo w200': 'demo'
}
</script>
</body>
</html>

事件失效问题及解决方案

1、在transiton动画完成前设置display:none,事件不会触发。

<!DOCTYPE html>
<html>
<head>
<title>transtionend demo</title>
<style type="text/css">
*{margin:0;padding: 0;}
.demo{
width:100px;
height: 100px;
background-color: #ddc;
transition: all 0.5s ease-out;
}
.w200{
width: 200px;
}
</style>
</head>
<body>
<div id="demo" class="demo" onmouseover="change()" onmouseout="change()">
</div>
<script type="text/javascript">
var element = document.getElementById('demo')
element.addEventListener('transitionend', handle, false)
function handle(){
alert('transitionend事件触发')
}
function change() {
element.className = element.className === 'demo' ? 'demo w200': 'demo'
// 500ms后设置display:none
setTimeout(function (){
element.style.display = 'none'
},400)
}
</script>
</body>
</html>

2、当transition完成前移除transition一些属性时,事件也不会触发,例如:

<!DOCTYPE html>
<html>
<head>
<title>transtionend demo</title>
<style type="text/css">
*{margin:0;padding: 0;}
.demo{
width:100px;
height: 100px;
background-color: #ddc;
transition: all 0.5s ease-out;
}
.noTranstion{
width:100px;
height: 100px;
background-color: #ddc;
}
.w200{
width: 200px;
}
</style>
</head>
<body>
<div id="demo" class="demo" onmouseover="change()" onmouseout="change()">
</div>
<script type="text/javascript">
var element = document.getElementById('demo')
element.addEventListener('transitionend', handle, false)
function handle(){
alert('transitionend事件触发')
}
function change() {
element.className = element.className === 'demo' ? 'demo w200': 'demo'
setTimeout(function(){
element.className = 'noTranstion'
},400)
}
</script>
</body>
</html>

3、元素从display:none到block,不会有过渡,导致无法触发transitionend事件

例如:元素从display:none 到block opacity从0到1,无法触发过渡效果。

<!DOCTYPE html>
<html>
<head>
<title>transtionend demo</title>
<style type="text/css">
*{margin:0;padding: 0;}
body{padding: 50px;}
.demo{
width:100px;
height: 100px;
background-color: #ddc;
transition: all 0.5s ease-out;
opacity:0;
display: none;
}
.noTranstion{
width:100px;
height: 100px;
background-color: #ddc;
}
.opt{
display: block;
opacity:1
} .w200{
width: 200px;
}
button{position: absolute;top: 200px;width: 100px;height: 40px;}
</style>
</head>
<body>
<div id="demo" class="demo" onmouseover="change()" onmouseout="change()">
</div>
<button onclick="change()">Click</button>
<script type="text/javascript">
var element = document.getElementById('demo')
element.addEventListener('transitionend', handle, false)
function handle(){
alert('transitionend事件触发')
}
function change() {
element.className = element.className === 'demo' ? 'demo opt': 'demo'
}
</script>
</body>
</html>

无法触发过渡效果原因:

元素从none到block,刚生成未能即时渲染,导致过渡失效。所以需要主动触发页面重绘,刷新DOM。页面重绘可以通过改变一些CSS属性来触发,例如:offsetTop、offsetLeft、offsetWidth、scrollTop等。

触发过渡效果解决方案:

1、通过定时器延迟渲染

<!DOCTYPE html>
<html>
<head>
<title>transtionend demo</title>
<style type="text/css">
*{margin:0;padding: 0;}
body{padding: 50px;}
.demo{
width:100px;
height: 100px;
background-color: #ddc;
transition: all 0.5s ease-out;
opacity: 0;
display: none;
}
.opt{
display: block;
}
button{position: absolute;top: 200px;width: 100px;height: 40px;}
</style>
</head>
<body>
<div id="demo" class="demo">
</div>
<button id="button" onclick="change()">点击</button>
<script type="text/javascript">
var element = document.getElementById('demo')
var button = document.getElementById('button')
element.addEventListener('transitionend', handle, false)
function handle(){
alert('transitionend事件触发')
}
function change() {
element.className = element.className === 'demo' ? 'demo opt': 'demo'
if(element.className === 'demo'){
element.style.opacity = null
button.innerHTML = '点击'
}else{
setTimeout(function(){
element.style.opacity = '1'
button.innerHTML = '重置'
},10)
}
}
</script>
</body>
</html>

2、强制获取当前内联样式

通过window.getComputedStyle()方法返回应用样式后的元的所有CSS属性的值,并解析这些值可能包含的任何基本计算。也就是说返回的属性值是已计算后的值,即DOM元素的样式已经更新了。然后再改变对应属性值触发过渡效果。例如:

<!DOCTYPE html>
<html>
<head>
<title>transtionend demo</title>
<style type="text/css">
*{margin:0;padding: 0;}
body{padding: 50px;}
.demo{
width:100px;
height: 100px;
background-color: #ddc;
transition: all 0.5s ease-out;
opacity: 0;
display: none;
}
.opt{
display: block;
}
button{position: absolute;top: 200px;width: 100px;height: 40px;}
</style>
</head>
<body>
<div id="demo" class="demo">
</div>
<button id="button" onclick="change()">点击</button>
<script type="text/javascript">
var element = document.getElementById('demo')
var button = document.getElementById('button')
element.addEventListener('transitionend', handle, false)
function handle(){
alert('transitionend事件触发')
}
function change() {
element.className = element.className === 'demo' ? 'demo opt': 'demo'
if(element.className === 'demo'){
element.style.opacity = null
button.innerHTML = '点击'
}else{
// setTimeout(function(){
// element.style.opacity = '1'
// button.innerHTML = '重置'
// },10)
window.getComputedStyle(element, null).opacity
element.style.opacity = '1'
button.innerHTML = '重置'
}
}
</script>
</body>
</html>

3、触发重绘刷新DOM

通过clientWidth触发重绘,例如:

<!DOCTYPE html>
<html>
<head>
<title>transtionend demo</title>
<style type="text/css">
*{margin:0;padding: 0;}
body{padding: 50px;}
.demo{
width:100px;
height: 100px;
background-color: #ddc;
transition: all 0.5s ease-out;
opacity: 0;
display: none;
}
.opt{
display: block;
}
button{position: absolute;top: 200px;width: 100px;height: 40px;}
</style>
</head>
<body>
<div id="demo" class="demo">
</div>
<button id="button" onclick="change()">点击</button>
<script type="text/javascript">
var element = document.getElementById('demo')
var button = document.getElementById('button')
element.addEventListener('transitionend', handle, false)
function handle(){
alert('transitionend事件触发')
}
function change() {
element.className = element.className === 'demo' ? 'demo opt': 'demo'
if(element.className === 'demo'){
element.style.opacity = null
button.innerHTML = '点击'
}else{
// setTimeout(function(){
// element.style.opacity = '1'
// button.innerHTML = '重置'
// },10)
// window.getComputedStyle(element, null).opacity
element.clientWidth;
element.style.opacity = '1'
button.innerHTML = '重置'
}
}
</script>
</body>
</html>

浏览器兼容性

移动端基本支持 android2.1+、webkit3.2+

详见浏览器兼容性

animationEnd事件

与transitonend事件类似,详见

Zepto中animate结束回调实现

查看了下zepto动画模块的源代码,animate()方法在动画结束后触发回调也是通过transitionend、animationend事件来触发。

另外在一些低版本的Android手机可能无法触发transitionend事件,需要手动触发。

$.fx = {
off: (eventPrefix === undefined && testEl.style.transitionProperty === undefined),
speeds: { _default: 400, fast: 200, slow: 600 },
cssPrefix: prefix,
transitionEnd: normalizeEvent('TransitionEnd'),
animationEnd: normalizeEvent('AnimationEnd')
}
// 手动触发事件
if (duration > 0){
this.bind(endEvent, wrappedCallback)
// transitionEnd is not always firing on older Android phones
// so make sure it gets fired
setTimeout(function(){
if (fired) return
wrappedCallback.call(that)
}, ((duration + delay) * 1000) + 25)
}

参考链接

zepto动画模块源码

transitionend事件MDN

transtion属性详解MDN

transitionend事件详解

Window.getComputedStyle() 方法

JS如何监听动画结束的更多相关文章

  1. js 实时监听input中值变化

    注意:用到了jquery需要引入jquery.min.js. 需求: 1.每个地方需要分别打分,总分为100; 2.第一个打分总分为40; 3.第二个打分总分为60. 注意:需要判断null.&quo ...

  2. js 事件监听 冒泡事件

    js 事件监听  冒泡事件   的取消 [自己写框架时,才有可能用到] <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitiona ...

  3. js 事件监听 兼容浏览器

    js 事件监听 兼容浏览器   ie 用 attachEvent   w3c(firefox/chrome)  用 addEventListener 删除事件监听 ie 用 detachEven   ...

  4. js事件监听机制(事件捕获)总结

    在前端开发过程中我们经常会遇到给页面元素添加事件的问题,添加事件的js方法也很多,有直接加到页面结构上的,有使用一些js事件监听的方法,由于各个浏览器对事件冒泡事件监听的机制不同,le浏览器只有事件冒 ...

  5. js动态监听dom变化

    原生js 动态监听dom变化,根据不同的类型绑定不同的处理逻辑 // Firefox和Chrome早期版本中带有前缀   var MutationObserver = window.MutationO ...

  6. js事件监听机制(事件捕获)

    在前端开发过程中我们经常会遇到给页面元素添加事件的问题,添加事件的js方法也很多,有直接加到页面结构上的,有使用一些js事件监听的方法,由于各个浏览器对事件冒泡事件监听的机制不同,le浏览器只有事件冒 ...

  7. Vue.js:监听属性

    ylbtech-Vue.js:监听属性 1.返回顶部 1. Vue.js 监听属性 本章节,我们将为大家介绍 Vue.js 监听属性 watch,我们可以通过 watch 来响应数据的变化: 实例 & ...

  8. angular 监听ng-repeat结束时间

    有些时候我们想要监听angular js中的 ng-repeat结束事件,从而好初始化一些我们的第三方或者其他需要初始化的js. 我们可以这样处理: js 中这样定义 : //监听事件 是否加载完毕a ...

  9. [转] js对象监听实现

    前言 随着前端交互复杂度的提升,各类框架如angular,react,vue等也层出不穷,这些框架一个比较重要的技术点就是数据绑定.数据的监听有较多的实现方案,本文将粗略的描述一番,并对其中一个兼容性 ...

随机推荐

  1. linux系统常用运维命令

    目录/文件处理命令 mkdir dirname         创建文件夹 mkdir -p /tmp/a/b         递归创建目录 rm -rf dirname         删除目录及内 ...

  2. U盘被写保护不能重新格式化

    今天一个朋友拿给我一个U盘,说这个U盘是商家送的,他想格式化,但是U盘被写保护了,系统不能格式化. 他想把这个U盘插到车子里听音乐,但是车载系统始终识别的是第一个分区,而这个分区正是被写保护那个,且这 ...

  3. most asked interview questions for C/C++

    1.   compared to prefix  ++, postfix increment needs one more step to create a temporary variable? w ...

  4. Ubuntu解除"输入密码以解锁密钥环”

    解决办法有两种: 1.去掉默认密钥环的密码: 打开应用程序->附件->密码和加密密钥(如果你的没有,在终端中输入 seahorse),切换到密码选项卡,会看到一个密码密钥环(我的密钥环是 ...

  5. 进程、线程、GIL、同步、异步、并行、并发、互斥锁

  6. python的学习笔记

    1逻辑运算符不理解 2 在交互模式中,最后被输出的表达式结果被赋值给变量 _ .例如: >>> tax = 12.5 / 100 >>> price = 100.5 ...

  7. SQL Server2016导出数据表数据

    SQL Server2016导出数据表数据 高文龙关注0人评论3914人阅读2017-09-22 08:41:56 SQL Server2016导出数据表数据 我们前面已经介绍了很多关于SQL Ser ...

  8. java如何将一个List传入Oracle存储过程

    注:本文来源于  深圳gg  < java如何将一个List传入Oracle存储过程   > 一:数据库端建一个PL/SQL的数组. CREATE OR REPLACE TYPE tabl ...

  9. Confluence 6 附件存储文件系统的分级

    从 Confluence 3.0 开始,附件的存储方式有了重大的改变和升级.如果你是从 Confluence 2.10 及其早期版本升级上来的,请参考 Upgrading Confluence 页面中 ...

  10. 【Java】「深入理解Java虚拟机」学习笔记(2)- JVM内存区域

    一.运行时数据区 JVM在执行Java程序的时候,将其运行时数据区划分为若干不同区域.它们的用途和创建及销毁的时间不同. 1.程序计数器(Program Counter Register) 是一块很小 ...