【问题标题】:How to use jQuery to wait for the end of CSS3 transitions?如何使用 jQuery 等待 CSS3 过渡结束?
【发布时间】:2012-03-04 13:05:31
【问题描述】:

我想淡出一个元素(将其不透明度转换为 0),然后在完成后从 DOM 中删除该元素。

在 jQuery 中这是直截了当的,因为您可以指定“删除”在动画完成后发生。但是,如果我想使用 CSS3 过渡制作动画,是否知道过渡/动画何时完成?

【问题讨论】:

标签: javascript jquery css css-transitions


【解决方案1】:

带有承诺的可链式单向事件

如果您需要像 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()-closure:

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

【讨论】:

    【解决方案2】:

    有一个animationend 可以观察到的事件,请参阅文档here, 对于 css transition 动画,您也可以使用 transitionend 事件

    不需要额外的库,这些都可以与 vanilla 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>

    【讨论】:

    • 这是边界线link-only answer。您应该在此处扩展您的答案以包含尽可能多的信息,并使用该链接仅供参考。
    • 投票赞成这个,因为它是第一个不依赖 jQuery 的。砍掉整棵树换树枝是没有意义的。
    • 现在浏览器更加步调一致——例如,不需要前缀——这种香草 Javascript 解决方案可能会成为当今(2020 年)公认的答案。因不依赖 jQuery 来回答一个相对简单的问题而被投票赞成。
    【解决方案3】:

    对于过渡,您可以使用以下内容通过 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() 方法已被弃用,on() 方法自jQuery 1.7 起是首选方法。 bind()

    您也可以在回调函数上使用off() 方法来确保它只会被触发一次。这是一个等效于使用one() 方法的示例:

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

    参考资料:

    【讨论】:

    • 值得注意的是,回调会为 每个 子元素触发。记住这一点非常重要,以防您想知道为什么您的回调被触发的次数比您预期的要多。到目前为止,我不知道有任何解决方法。
    • @Lev 您可以通过将事件的 currentTarget 与其目标进行比较来轻松缓解这种情况。比如:function(event){ if(event.target === event.currentTarget){ /* 做事 */ } }
    • 是的,我在写评论后不久就明白了。 >_
    • 对于 jQuery v1.7+,我们使用 .on() 而不是 .bind() api.jquery.com/bind
    • 所有现代浏览器现在都支持无前缀事件。 caniuse.com/#feat=css-transitions 另请注意,如果您有“transitionend webkitTransitionEnd”,它将在 Chrome 中触发两次。
    【解决方案4】:

    Chrome 中的动画当前会触发两次已接受的答案。这大概是因为它可以识别webkitAnimationEnd 以及animationEnd。以下肯定只会触发一次:

    /* 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);
            });
    

    【讨论】:

    • 我建议调用 whichAnimationEvent() 函数,因为它处理动画事件。
    【解决方案5】:

    对于任何这可能很方便的人,这里有一个依赖 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()); //for purge existing event callback.apply(self);
    【解决方案6】:

    另一种选择是使用 jQuery Transit Framework 来处理您的 CSS3 过渡。过渡/效果在移动设备上表现良好,您不必在 CSS 文件中添加一行凌乱的 CSS3 过渡来制作动画效果。

    下面是一个示例,当您单击一个元素时,它会将其不透明度转换为 0,并在转换完成后将其移除:

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

    JS Fiddle Demo

    【讨论】:

    • 回调不起作用 - 也许是由于我不知道的传输 API 的变化,但小提琴示例不起作用。它在动画运行之前触发 hide 方法(在 chrome 中尝试过)
    • @JonathanLiuti 使用 FireFox 25、IE11、Chrome 31 进行了测试。工作正常。
    • 是的 @ROFLwTIME 你是完全正确的 - 看来我的 chrome 快疯了。在彻底重启 chrome 后,今天重新测试了小提琴,它按预期工作。我的错 !对不起,这个。
    猜你喜欢
    • 2017-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-03
    • 1970-01-01
    • 1970-01-01
    • 2011-06-23
    相关资源
    最近更新 更多