JS如何監聽動畫結束

場景描述

在使用JS控制動畫時通常須要在動畫結束後執行回調去進行DOM的相關操做,因此須要監聽動畫結束進行回調。JS提供瞭如下事件用於監聽動畫的結束,簡單總結學習下。javascript

CSS3動畫監聽事件

transitionEnd事件

transitionEnd事件會在CSS transition動畫結束後觸發。css

動畫結束後觸發監聽事件

<!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事件html

<!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>

事件失效問題及解決方案

一、在transiton動畫完成前設置display:none,事件不會觸發。java

<!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>

二、當transition完成前移除transition一些屬性時,事件也不會觸發,例如:android

<!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>

三、元素從display:none到block,不會有過渡,致使沒法觸發transitionend事件
例如:元素從display:none 到block opacity從0到1,沒法觸發過渡效果。css3

<!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等。
觸發過渡效果解決方案:
一、經過定時器延遲渲染git

<!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>

二、強制獲取當前內聯樣式
經過window.getComputedStyle()方法返回應用樣式後的元的全部CSS屬性的值,並解析這些值可能包含的任何基本計算。也就是說返回的屬性值是已計算後的值,即DOM元素的樣式已經更新了。而後再改變對應屬性值觸發過渡效果。例如:github

<!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>

三、觸發重繪刷新DOM
經過clientWidth觸發重繪,例如:web

<!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() 方法

相關文章
相關標籤/搜索