0%

jQuery通过添加类来触发动画时遇到的问题及解决办法

这两天在编写交互式媒体大作业的时候遇到了这样一个问题:已经事先在CSS中写好了一个animation,绑定在一个类上,该类名为’tracking-in-expand’,同时html中定义好了这样一个段落标签

1
2
3
4
5
6
<div class="mode">
<div>
<div class="text"> <span><p>LARGE IMAGES</p></span></div>
/*...some codes...*/
</div>
</div>

需要实现这样一个功能:点击某一个按钮,使得该段落标签中的内容改变,并且触发CSS中定义好的动画。思路很简单,只需要通过jQuery选择到该标签,然后通过text()函数改变其内容,并且通过addClass()为其添加绑定有动画的类即可,在添加前需要先通过removeClass()移除该类。于是我编写了如下代码

1
$("that<p>").text("someText").removeClass().addClass('tracking-in-expand'); //这段代码在该按钮的点击事件绑定的函数中

但在实际运行的时候,该动画并没有被触发,但通过F12可以看到该class='tracking-in-expand'已经被成功添加,为了探究原因,我重新更改了该代码

1
$("that<p>").text("someText").addClass('tracking-in-expand');//这段代码在该按钮的点击事件绑定的函数中

这样在第一次点击的时候,动画应该会被触发,事实也是如此,这说明添加类可以触发动画,问题是出在了removeClass()addClass()之间,于是我尝试了另一种写法

1
2
3
4
5
6
7
$("that<p>").text("someText").addClass('tracking-in-expand');
$("that<p>").unbind('animationend');
$("that<p>").bind('animationend', e =>{
console.log('end');
$(e.currentTarget).removeClass('tracking-in-expand');
});
//两端代码均在该按钮的点击事件绑定的函数中

运行后,结果与预期的一样,在每次点击之后动画会正确的被触发,同时在前一次动画未播放完时点击按钮,不会直接结束正在播放的动画,而是等到这一次动画播放完毕后才会播放下一次,这与代码的逻辑一致,但并不是想要的效果。需要实现的应当是立刻停止当前动画并播放下一次动画。暂且抛开这个小瑕疵不谈,到目前为止,基本可以确定动画不触发的问题所在了,即原先的链式调用的写法,会导致removeClass()addClass()的执行顺序并不是如我一开始所设想的——先移除后添加,但我并不明白实际上二者的触发顺序和其中的原理,于是我在网上搜寻资料后,看到一种写法,是通过jQuery中的animate()方法,将animate({},0)添加在removeClass()addClass()之间,即可实现想要的效果,代码如下

1
$("that<p>").text("someText").removeClass().animate({},0).addClass('tracking-in-expand'); //这段代码在该按钮的点击事件绑定的函数中

运行后,效果与要求的完全一致,但是我并不明白其中的原理,为什么通过animate({},0)就能让removeClass()addClass()按照正确的顺序执行呢?于是我继续修改代码

1
2
3
4
$("that<p>").text("someText").removeClass('tracking-in-expand');
setTimeout(() => {
$("that<p>").text("someText").addClass('tracking-in-expand');
}, 1000);//这段代码在该按钮的点击事件绑定的函数中

运行后,动画在一秒后被正确的触发了,同时,我将setTimeout()这段代码移到 removeClass()上方,同样可以正确触发,这说明setTimeout()里的代码和普通的代码执行顺序不同,在目前这样的情况下,它始终在普通代码之后运行,搜索资料后发现,这涉及到JavaScript中的同步异步的概念:

JavaScript是单线程的,于是出现了“任务队列”的概念,即所有任务排成一队等待处理,但是这样存在着明显的弊端,比如某一段代码执行时间较长,会导致后续任务被“堵塞”,为了克服这一弊端,JavaScript引入了异步任务的概念,用来处理一些耗时间的任务,setTimeout()就是其中的一种情况。所以任务被分为了同步任务异步任务同步任务就是那些例如console.log()或者变量声明,这种任务是指在主线程上排队的任务,必须一个一个按顺序执行,而异步任务是指独立在主线程之外的,只有等到主线程任务执行完毕后才会通知主线程,请求执行任务,然后该任务才会进入主线程执行。

因此setTimeout()里的代码和普通的代码执行顺序不同的问题得到了解释,同时我尝试将setTimeout()里的时间参数改成“0”,执行顺序不变,说明setTimeout()是异步执行的,执行顺序始终在同步任务之后。在回看这段代码

1
$("that<p>").text("someText").removeClass().animate({},0).addClass('tracking-in-expand'); //这段代码在该按钮的点击事件绑定的函数中

在搜索资料后得知,animate()函数是属于异步任务,因此这段代码能成功实现效果的原因是animate()属于异步任务,因此它的执行顺序一定在它前面的removeClass()之后,但是它之后的addClass()的执行的主体是animate()返回的对象,因此addClass()此时也成为了一个异步任务,被挂起,等到主线程的任务执行完毕后才会被执行,所以最终程序按照我们想要的顺序进行执行——先移除类,再添加类。如果不添加animate()或者setTimeout()之类的异步任务将两个函数的执行顺序人为地分割开,JavaScript引擎会将这两个任务同步进行,最终无法成功触发动画。

-------------本文结束感谢您的阅读-------------