You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何无队列完整执行jQuery动画?多次点击按钮动画异常求助

jQuery Animation Glitch on Rapid Button Clicks

我最近在做一个随机名言生成器项目,碰到了个头疼的问题——快速多次点击#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&amp;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&amp;related=freecodecamp&amp;text="; twitter += '"' + qt.text + '" '; twitter += qt.author; var tumblr = "https://www.tumblr.com/widgets/share/tool?posttype=quote&amp;tags=quotes,freecodecamp&amp;caption="; tumblr += qt.author; tumblr += "&amp;content="; tumblr += qt.text; tumblr += "&amp;canonicalUrl=https%3A%2F%2Fwww.tumblr.com%2Fbuttons&amp;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(); };

问题根源分析

我帮你排查了下,问题主要出在这几个地方:

  1. 动画队列堆积:每次点击按钮都会往.quote-container的动画队列里塞新任务,快速点击就会导致一堆任务排队执行,动画自然就乱了。
  2. 同步AJAX阻塞UI:你的getNewQuote用了async: false的同步请求,这会卡死浏览器的UI线程,动画和交互都会卡顿,进一步放大异常。
  3. 无重复触发控制:没有限制按钮的点击频率,用户短时间内点多少次就触发多少次动画,完全没拦住。

修复方案

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 });
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:37:26