如何使用jQuery等待CSS3过渡效果结束?

219

我想淡出一个元素(将其不透明度过渡到0),然后当完成后从DOM中删除该元素。

在jQuery中,这很简单,因为您可以指定“删除”在动画完成后发生。但是,如果我希望使用CSS3过渡进行动画处理,有没有办法知道过渡/动画何时完成?

我希望能够为您提供帮助!

3
请提供需要翻译的内容。 - Nicolas Modrzyk
6
重复:https://dev59.com/lnI95IYBdhLWcg3w7SpZ意思是要在CSS过渡完成后执行回调函数。 - Varun Singh
6个回答

348

您可以使用以下方式通过jQuery检测转换结束的情况:

$("#someSelector").bind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(){ ... });

Mozilla提供了一份绝佳的参考资料:

https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions#Detecting_the_start_and_completion_of_a_transition

对于动画,这也非常类似:

$("#someSelector").bind("animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", function(){ ... });

请注意,您可以同时将所有带有浏览器前缀的事件字符串传递到bind()方法中,以支持在支持它的所有浏览器上触发事件。

更新:

根据Duck留下的评论:您可以使用jQuery的.one()方法确保处理程序仅触发一次。例如:

$("#someSelector").one("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(){ ... });

$("#someSelector").one("animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", function(){ ... });

更新2:

jQuery bind()方法已被弃用,jQuery 1.7版本之后推荐使用on()方法。bind()

你也可以在回调函数上使用off()方法,以确保它只会被触发一次。以下示例与使用one()方法等价:

$("#someSelector")
.on("animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd",
 function(e){
    // do something here
    $(this).off(e);
 });

参考资料:


15
值得注意的是,回调函数将对每个过渡的子元素进行触发。如果你想知道为什么回调函数被触发的次数比你预期的要多,那么这一点非常重要。我目前还不知道任何解决方法。 - SikoSoft
24
你可以通过比较事件的currentTarget和target来轻松地缓解这个问题,代码应该像这样:function(event){ if(event.target === event.currentTarget){ /* 做一些操作 */ } } - Jim Jeffers
5
我留言之后不久就明白了。>_< 感谢您的发布,我相信它会帮助其他人! :) - SikoSoft
9
jQuery v1.7+ 中我们使用 .on() 而不是 .bind()。http://api.jquery.com/bind/ - olo
4
现代浏览器已经支持未加前缀的事件。 http://caniuse.com/#feat=css-transitions同时请注意,在Chrome浏览器中,如果你有"transitionend webkitTransitionEnd"的代码,它会被触发两次。 - WebDevNerdStuff
显示剩余8条评论

25

有一个可以被观察到的animationend事件,请参阅文档此处,同时对于css transition 动画,您可以使用 transitionend 事件。

这些都可以在原生JS中使用,无需额外的库。

document.getElementById("myDIV").addEventListener("transitionend", myEndFunction);
function myEndFunction() {
 this.innerHTML = "transition event ended";
}
#myDIV {transition: top 2s; position: relative; top: 0;}
div {background: #ede;cursor: pointer;padding: 20px;}
<div id="myDIV" onclick="this.style.top = '55px';">Click me to start animation.</div>


2
这是一个边缘仅链接答案。您应该扩展您的答案,包括尽可能多的信息,并仅将链接用于参考。 - Blue
9
我会翻译此内容:我支持这个,因为这是第一个不依赖于jQuery的选项。砍掉整棵树取一枝枝也没有意义。 - Aaron Mason
2
现在浏览器更加同步了 - 例如,不需要前缀 - 这个原生JavaScript解决方案可能是当今(2020年)被接受的答案。因为没有依赖jQuery来回答一个相对简单的问题而获得了点赞。 - A Haworth
1
我同意"Vanilla JS"这个词汇,它只是普通的JavaScript,但问题是关于jQuery特定的。供你参考。 - raphie
@raphie 根据谷歌和 Stack Overflow 的工作方式,许多寻找无 JQuery 答案的人都会来到这里,因此在这里提供答案是很有意义的。 - Yehuda Schwartz
@YehudaSchwartz 我明白,但问题仍然与jQuery有关,我认为SO规定答案必须与问题相关。即使首选答案是“Vanilla”或纯JS,答案也应基于问题。而不是基于个人偏好。顺便说一下,我也更喜欢纯JS。 - raphie

16

另一种选择是使用jQuery Transit Framework来处理CSS3转换。 这些转换/效果在移动设备上表现良好,您无需在CSS文件中添加混乱的CSS3转换代码即可实现动画效果。

以下是一个示例,当您单击它时,将把元素的不透明度过渡到0,并在过渡完成后将其删除:

$("#element").click( function () {
    $('#element').transition({ opacity: 0 }, function () { $(this).remove(); });
});

JS Fiddle演示


回调函数没起作用 - 也许是 transit API 的更改导致的,我不确定,但 fiddle 的示例无法正常工作。它在动画运行之前触发了 hide 方法(在 Chrome 中测试过)。 - Jonathan Liuti
@JonathanLiuti测试了FireFox 25,IE11,Chrome 31。运行良好。 - Gaff
是的,@ROFLwTIME,你说得完全正确——看来我的 Chrome 只是出了点问题。今天在 Chrome 干净重启后重新测试了一下这个 fiddle,它已经按预期工作了。我错了!对此感到抱歉。 - Jonathan Liuti

7

如果有人需要,这里提供一个依赖于jQuery的函数,可以通过应用CSS类来实现CSS动画,并在之后获取回调。它可能不完美,因为我是在Backbone.js应用程序中使用它的,但可能很有用。

var cssAnimate = function(cssClass, callback) {
    var self = this;

    // Checks if correct animation has ended
    var setAnimationListener = function() {
        self.one(
            "webkitAnimationEnd oanimationend msAnimationEnd animationend",
            function(e) {
                if(
                    e.originalEvent.animationName == cssClass &&
                    e.target === e.currentTarget
                ) {
                    callback();
                } else {
                    setAnimationListener();
                }
            }
        );
    }

    self.addClass(cssClass);
    setAnimationListener();
}

我用它就像这样。
cssAnimate.call($("#something"), "fadeIn", function() {
    console.log("Animation is complete");
    // Remove animation class name?
});

原始想法来自 http://mikefowler.me/2013/11/18/page-transitions-in-backbone/

这个看起来很方便: http://api.jqueryui.com/addClass/


更新

在与上述代码和其他选项的斗争中,我建议非常谨慎地监听CSS动画结束。随着多个动画的进行,事件监听可能会变得非常混乱。我强烈建议使用动画库,例如GSAP,即使是小的动画。


谢谢分享,我已经使用过它并添加了以下内容: e.stopImmediatePropagation(); self.trigger(this.whichAnimationEvent()); //用于清除现有事件 callback.apply(self); - BomAle

6
目前被接受的答案对于Chrome浏览器中的动画会触发两次。这可能是因为它同时识别了webkitAnimationEndanimationEnd。下面的代码一定只会触发一次:
/* From Modernizr */
function whichTransitionEvent(){

    var el = document.createElement('fakeelement');
    var transitions = {
        'animation':'animationend',
        'OAnimation':'oAnimationEnd',
        'MSAnimation':'MSAnimationEnd',
        'WebkitAnimation':'webkitAnimationEnd'
    };

    for(var t in transitions){
        if( transitions.hasOwnProperty(t) && el.style[t] !== undefined ){
            return transitions[t];
        }
    }
}

$("#elementToListenTo")
    .on(whichTransitionEvent(),
        function(e){
            console.log('Transition complete!  This is the callback!');
            $(this).off(e);
        });

3
建议使用whichAnimationEvent()函数进行调用,因为它处理动画事件。 - Jakob Løkke Madsen

0

使用 Promises 实现可链式的单向事件

如果你需要像 JQuery 的 one() 一样的单向事件,我发现这种模式非常方便:

function awaitTransitionEnd(transitionProperty, el, triggerFunction) {
    return new Promise((resolve, reject) => {
        const handler = (e) => {
            if (e.propertyName !== transitionProperty) {
                return;
            }
            el.removeEventListener('transitionend', handler);
            resolve(e);
        }
        el.addEventListener('transitionend', handler);
        triggerFunction(el);
    });
}

你可以像这个例子一样链接CSS过渡效果:

awaitTransitionEnd(
    'background-color', myEl, () => myEl.classList.replace('bg-red', 'bg-green')
).then(() => awaitTransitionEnd(
    'opacity', myEl, () => myEl.classList.add('opacity-0')
)).then(() => awaitTransitionEnd(
    'opacity', myEl, () => myEl.classList.remove('opacity-0')
));

如果您不想使用箭头函数,您必须像这样传递事件+元素:

awaitTransitionEnd('background-color', myEl, function(el) {
    el.classList.replace('bg-red', 'bg-green');
}).then(function(e) {
    return awaitTransitionEnd('opacity', e.target, function(el) {
        el.classList.add('opacity-0');
    });  
}).then(function(e) {
    return awaitTransitionEnd('opacity', e.target, function(el) {
        el.classList.remove('opacity-0');
    });
});

awaitTransitionEnd 是一个类方法且您不想使用箭头函数时,您必须将 this 绑定到每个 then() 闭包上:

//[...]
.then(function(e) {
    return this.awaitTransitionEnd('opacity', e.target, function(el) {
        el.classList.add('opacity-0');
    });  
}.bind(this)).then(//[...]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接