如何无队列完整执行jQuery动画?多次点击按钮动画异常求助
我最近在做一个随机名言生成器项目,碰到了个头疼的问题——快速多次点击#new-quote按钮时,页面的动画(容器高度变化、文字淡入淡出这些)就会乱掉,要么高度跳得不对,要么文字显示错乱。
我已经查阅了3篇关于无队列执行jQuery动画的资料,但照着示例实现后问题依然存在。下面是我的项目代码,AJAX在本地无法正常工作,但在在线编辑器里是正常的:
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js" integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU=" crossorigin="anonymous"></script> <script src="https://use.fontawesome.com/911574fea4.js"></script> <div class="v-wrap"> <div class="quote-container" style=""> <div class="quote-text"> </div> <div class="quote-author"></div> <a id="tweet-quote" class="button"><i class="fa fa-twitter"></i></a> <a id="tumblr-quote" class="button"><i class="fa fa-tumblr"></i></a> </div> <div id="new-quote" class="button">New quote</div> <footer> <a href="https://codepen.io/Kestis500">Created by LukasLSC</a> </footer> </div>
CSS
html, body { height: 100%; width: 100%; } body { margin: 0; padding: 0; background: #333; color: #333; font-family: sans-serif; } .v-wrap { height: 100%; text-align: center; } .v-wrap:before { content: ""; display: inline-block; vertical-align: middle; width: 0; height: 100%; } .quote-container { width: 31.25rem; background: #fff; margin: 0; display: inline-block; vertical-align: middle; border-radius: 0.1875rem; border-top-right-radius: 0; border-bottom-right-radius: 0; visibility: hidden; padding: 0 2.5rem; } .quote-text { font-size: 1.625rem; } .quote-text i { margin-right: 0.6rem; } .quote-text p { display: inline; } .quote-author { font-size: 1rem; margin: 0 0.4rem 2rem 0; text-align: right; } .button { padding: 0.75rem; text-align: center; font-size: 1rem; color: #fff; border-radius: .1875rem; display: inline-block; cursor: pointer; -webkit-user-select: none; user-select: none; } .button:not(#new-quote):hover { opacity: .8 !important; } .button:not(#new-quote) { min-width: 1rem; min-height: 1rem; } .button i { vertical-align: middle; } #new-quote { white-space: nowrap; writing-mode: vertical-lr; height: 50%; border-top-left-radius: 0; border-bottom-left-radius: 0; vertical-align: middle; background: #fff !important; margin: 0; position: relative; right: 0.25625rem; color: #333; visibility: hidden; } #new-quote:before { content: ""; position: absolute; height: 100%; width: 0.0625rem; bottom: 0; left: 0; visibility: hidden; -webkit-transform: scaleY(0); transform: scaleY(0); -webkit-transition: all .3s ease-in-out; transition: all .3s ease-in-out; } #new-quote:hover:before { visibility: visible; -webkit-transform: scaleY(1); transform: scaleY(1); } footer { font-size: 0.85rem; margin: 1rem 0; } footer a { text-decoration: none; color: #fff; position: relative; } footer a:before { content: ""; position: absolute; width: 100%; height: .0625rem; bottom: 0; left: 0; background: #fff; visibility: hidden; -webkit-transform: scaleX(0); transform: scaleX(0); -webkit-transition: all .3s ease-in-out 0s; transition: all .3s ease-in-out 0s; } footer a:hover:before { visibility: visible; -webkit-transform: scaleX(1); transform: scaleX(1); }
JavaScript
var getNewQuote = function() { var quote = {}; $.ajax({ url: "https://quotesondesign.com/wp-json/posts?filter[orderby]=rand&post", success: function(d) { quote.text = $(d[0].content).text().trim(); quote.author = d[0].title; }, datatype: "json", async: false, cache: false }); return quote; }; var getRandomColor = function() { var colors = [ "#ff9966", "#7f00ff", "#396afc", "0cebeb", "#06beb6", "#642b73", "#36d1dc", "#cb356b", "#3a1c71", "#ef3b36", "#159957", "#000046", "#007991", "#56ccf2", "#f2994a", "#e44d26", "#4ac29a", "#f7971e", "#34e89e", "#6190e8", "#3494e6", "#ee0979" ], randomNumber = Math.floor(Math.random() * colors.length); return colors[randomNumber]; }; var updateText = function($t, qt) { var twitter = "https://twitter.com/intent/tweet?hashtags=quotes&related=freecodecamp&text="; twitter += '"' + qt.text + '" '; twitter += qt.author; var tumblr = "https://www.tumblr.com/widgets/share/tool?posttype=quote&tags=quotes,freecodecamp&caption="; tumblr += qt.author; tumblr += "&content="; tumblr += qt.text; tumblr += "&canonicalUrl=https%3A%2F%2Fwww.tumblr.com%2Fbuttons&shareSource=tumblr_share_button"; var $icon = $("<i class='fa fa-quote-left'>").prop("aria-hidden", true); $t.find(".quote-text").html("").append($icon, qt.text); $t.find(".quote-author").html("- " + qt.author); $("#tweet-quote").attr("href", twitter); $("#tumblr-quote").attr("href", tumblr); }; var calcNewHeight = function(q) { var $temp = $("<div>", { class: "quote-container temp", }).appendTo($("body")); $temp.append($("<div>", { class: "quote-text" }), $("<div>", { class: "quote-author" })); updateText($temp, q); var h = $temp.height() + 40; $temp.remove(); return h; }; var changeColor = function(newColor) { $("body, .button:not(#new-quote)").animate({ backgroundColor: newColor }); $("#new-quote").animate({ color: newColor }); $(".quote-text, .quote-author").css("color", newColor); if ($("#modStyle").length === 0) { $("head").append( "<style id='modStyle'>#new-quote:before {background:" + newColor + ";}</style>" ); } else { $("head style#modStyle").html("#new-quote:before {background:" + newColor + ";}"); } }; var getQuote = function(e) { var nq, nc, nh = 0; $(".quote-container").queue(function() { nq = getNewQuote(); nc = getRandomColor(); $(".quote-container").children().css("opacity", 0); changeColor(nc); $(this).dequeue(); }) .queue(function() { nh = calcNewHeight(nq); $(".quote-container, #new-quote").animate({ height: nh / 16 + "rem", }, { duration: 1000, queue: false }); $(".quote-container").animate({ padding: "2.5rem" }, { duration: 1000, queue: false }); $("#new-quote").animate({ padding: "2.5rem .75rem" }, { duration: 1000, queue: false }); $(this).dequeue(); }) .queue(function() { updateText($(".quote-container"), nq); $(".quote-container").children().fadeTo(750, 1); $(this).dequeue(); }) }; $(function() { $("#new-quote").on("click", getQuote); $(".quote-container, #new-quote").css({ visibility: "visible", height: 0 }); $("#new-quote").css("padding", "0 .75rem"); getQuote(); };
问题根源分析
我帮你排查了下,问题主要出在这几个地方:
- 动画队列堆积:每次点击按钮都会往
.quote-container的动画队列里塞新任务,快速点击就会导致一堆任务排队执行,动画自然就乱了。 - 同步AJAX阻塞UI:你的
getNewQuote用了async: false的同步请求,这会卡死浏览器的UI线程,动画和交互都会卡顿,进一步放大异常。 - 无重复触发控制:没有限制按钮的点击频率,用户短时间内点多少次就触发多少次动画,完全没拦住。
修复方案
1. 清空动画队列,终止当前动画
每次触发getQuote时,先把相关元素的动画队列清空,终止正在跑的动画,避免任务堆积:
var getQuote = function(e) { // 清空容器和按钮的动画队列,直接跳转到动画结束状态 $(".quote-container, #new-quote").stop(true, true).clearQueue(); // ... 剩下的代码 }
stop(true, true):第一个true清空所有待执行的队列任务,第二个true让当前动画直接完成,避免元素停在中间状态。clearQueue():彻底清除队列里的剩余任务,防止遗漏。
2. 把同步AJAX改成异步
同步AJAX会阻塞UI,改成异步后不会影响动画流畅度,同时用回调函数处理返回的数据:
var getNewQuote = function(callback) { $.ajax({ url: "https://quotesondesign.com/wp-json/posts?filter[orderby]=rand&post", success: function(d) { var quote = { text: $(d[0].content).text().trim(), author: d[0].title }; callback(quote); // 用回调传递名言数据 }, datatype: "json", async: true, // 改为异步请求 cache: false }); };
3. 控制按钮状态,防止重复点击
动画执行期间把按钮禁用,等所有动画都完成了再启用,从根源上避免多次触发:
var getQuote = function(e) { $(".quote-container, #new-quote").stop(true, true).clearQueue(); // 禁用按钮,防止重复点击 $("#new-quote").prop("disabled", true).css("cursor", "not-allowed"); getNewQuote(function(nq) { var nc = getRandomColor(); $(".quote-container").children().css("opacity", 0); changeColor(nc); var nh = calcNewHeight(nq); // 执行容器和按钮的尺寸动画 $(".quote-container, #new-quote").animate({ height: nh / 16 + "rem", }, { duration: 1000, queue: false }); $(".quote-container").animate({ padding: "2.5rem" }, { duration: 1000, queue: false }); $("#new-quote").animate({ padding: "2.5rem .75rem" }, { duration: 1000, queue: false }); // 等尺寸动画完成后更新文本并淡入 setTimeout(function() { updateText($(".quote-container"), nq); $(".quote-container").children().fadeTo(750, 1, function() { // 淡入动画完成后重新启用按钮 $("#new-quote").prop("disabled", false).css("cursor", "pointer"); }); }, 1000); // 对应尺寸动画的时长 }); };
4. 移除冗余的队列链式调用
原来的.queue()链式调用是导致队列堆积的原因之一,改成上面的异步+回调逻辑后,完全可以去掉,代码逻辑会更清晰。
完整修复后的JavaScript代码
var getNewQuote = function(callback) { $.ajax({ url: "https://quotesondesign.com/wp-json/posts?filter[orderby]=rand&post", success: function(d) { var quote = { text: $(d[0].content).text().trim(), author: d[0].title }; callback(quote); }, datatype: "json", async: true, cache: false }); }; var getRandomColor = function() { var colors = [ "#ff9966", "#7f00ff", "#396afc", "#0cebeb", "#06beb6", "#642b73", "#36d1dc", "#cb356b", "#3a1c71", "#ef3b36", "#159957", "#000046", "#007991", "#56ccf2", "#f2994a", "#e44d26", "#4ac29a", "#f7971e", "#34e89e", "#6190e8", "#3494e6", "#ee0979" ], randomNumber = Math.floor(Math.random() * colors.length); return colors[randomNumber]; }; var updateText = function($t, qt) { var twitter = "https://twitter.com/intent/tweet?hashtags=quotes&related=freecodecamp&text="; twitter += '"' + qt.text + '" '; twitter += qt.author; var tumblr = "https://www.tumblr.com/widgets/share/tool?posttype=quote&tags=quotes,freecodecamp&caption="; tumblr += qt.author; tumblr += "&content="; tumblr += qt.text; tumblr += "&canonicalUrl=https%3A%2F%2Fwww.tumblr.com%2Fbuttons&shareSource=tumblr_share_button"; var $icon = $("<i class='fa fa-quote-left'>").prop("aria-hidden", true); $t.find(".quote-text").html("").append($icon, qt.text); $t.find(".quote-author").html("- " + qt.author); $("#tweet-quote").attr("href", twitter); $("#tumblr-quote").attr("href", tumblr); }; var calcNewHeight = function(q) { var $temp = $("<div>", { class: "quote-container temp" }).appendTo($("body")); $temp.append($("<div>", { class: "quote-text" }), $("<div>", { class: "quote-author" })); updateText($temp, q); var h = $temp.height() + 40; $temp.remove(); return h; }; var changeColor = function(newColor) { $("body, .button:not(#new-quote)").animate({ backgroundColor: newColor }); $("#new-quote").animate({ color: newColor }); $(".quote-text, .quote-author").css("color", newColor); if ($("#modStyle").length === 0) { $("head").append("<style id='modStyle'>#new-quote:before {background:" + newColor + ";}</style>"); } else { $("head style#modStyle").html("#new-quote:before {background:" + newColor + ";}"); } }; var getQuote = function(e) { $(".quote-container, #new-quote").stop(true, true).clearQueue(); $("#new-quote").prop("disabled", true).css("cursor", "not-allowed"); getNewQuote(function(nq) { var nc = getRandomColor(); $(".quote-container").children().css("opacity", 0); changeColor(nc); var nh = calcNewHeight(nq); $(".quote-container, #new-quote").animate({ height: nh / 16 + "rem", }, { duration: 1000, queue: false }); $(".quote-container").animate({ padding: "2.5rem" }, { duration: 1000, queue: false });
相关产品推荐
相关产品推荐

