場景描述
在使用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:nonesetTimeout(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 = nullbutton.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 = nullbutton.innerHTML = '點擊'}else{// setTimeout(function(){// element.style.opacity = '1'// button.innerHTML = '重置'// },10)window.getComputedStyle(element, null).opacityelement.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 = nullbutton.innerHTML = '點擊'}else{// setTimeout(function(){// element.style.opacity = '1'// button.innerHTML = '重置'// },10)// window.getComputedStyle(element, null).opacityelement.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 firedsetTimeout(function(){if (fired) returnwrappedCallback.call(that)}, ((duration + delay) * 1000) + 25)}
參考鏈接
zepto動畫模塊源碼
transitionend事件MDN
transtion屬性詳解MDN
transitionend事件詳解
Window.getComputedStyle() 方法