jQuery / Make Elements Appear in Order

Use .delay(index * gap).fadeIn(duration) to start elements at intervals. These are staggered starts, so animations overlap when duration exceeds the gap. To wait until one element finishes before starting the next, launch the next element from the previous completion callback.

Start intervals versus completion order

.delay() waits before the next queued task on each element. A collection does not share one universal queue. Equal delays therefore start together; multiplying by index staggers them. Waiting does not block all JavaScript.

The example uses gap 200ms and .fadeIn() duration 400ms. Staggering lets Beta start before Alpha ends. Serial mode waits for Alpha’s completion and then another 200ms before Beta. These are scheduling targets, not exact browser timing guarantees.

.each() follows collection order. Direct .item children of #items are Alpha, Beta and Gamma here. A separately sorted collection or CSS-reordered layout can differ from visual reading order; decide which order users should experience.

Complete comparison example

Save everything as UTF-8 appearance-demo.html and open it in a browser. Internet loads the official full jQuery file 4.0.0. Timed effects require effects, so do not substitute slim. No server or installation is needed. Order, reset and replay were checked in Windows Edge with 3.7.1 and 4.0.0.

The HTML contains visible elements until the ready script hides them. A library-loading failure therefore does not leave important content permanently hidden by CSS. The following output is after JavaScript initialization.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Staggered versus strictly sequential appearance</title>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <style>
    body { font: 16px/1.6 sans-serif; max-width: 760px; margin: 20px; }
    button, select { font: inherit; margin: 4px; }
    .item { border: 2px solid teal; padding: 12px; margin: 12px 0; background: #e7f5f1; }
    pre { white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
  <label>Mode <select id="mode"><option value="stagger">Staggered starts</option><option value="serial">Wait for completion</option></select></label>
  <button type="button" id="start">Start</button>
  <button type="button" id="reset">Reset</button>
  <div id="items"><div class="item" id="alpha">Alpha</div><div class="item" id="beta">Beta</div><div class="item" id="gamma">Gamma</div></div>
  <pre id="result" aria-live="polite"></pre>
  <pre id="events"></pre>
  <script>
    jQuery(function ($) {
      const $items = $("#items > .item"), gap = 200, duration = 400;
      let generation = 0, busy = false, state = "ready", completed = [], events = [];
      function report() {
        $("#result").text(`mode=${$("#mode").val()}\nstate=${state}\nvisible=${$items.filter(":visible").length}/${$items.length}\ncompleted=${completed.join(",") || "(none)"}`);
        $("#events").text(events.join("\n") || "(none)");
      }
      function reset() {
        generation++; // Invalidate callbacks before settling old effects.
        $items.stop(true, true).hide();
        busy = false; state = "ready"; completed = []; events = [];
        $("#start").prop("disabled", false); report();
      }
      function start() {
        if (busy) return;
        reset(); busy = true; state = "running"; $("#start").prop("disabled", true);
        const run = generation, mode = $("#mode").val();
        const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
        if (reduce) {
          $items.show(); completed = $items.map(function () { return this.id; }).get();
          events = ["Reduced motion: shown immediately"];
          busy = false; state = "done"; $("#start").prop("disabled", false); report(); return;
        }
        function reveal(index, wait) {
          const $item = $items.eq(index), id = $item[0].id;
          $item.delay(wait).queue(function (next) {
            if (run !== generation) return;
            events.push("start " + id); next(); report();
          }).fadeIn(duration, function () {
            if (run !== generation) return;
            completed.push(id); events.push("done " + id);
            if (completed.length === $items.length) {
              busy = false; state = "done"; $("#start").prop("disabled", false);
            } else if (mode === "serial") reveal(index + 1, gap);
            report();
          });
        }
        if (!$items.length) { busy = false; state = "done"; $("#start").prop("disabled", false); report(); return; }
        if (mode === "stagger") $items.each(function (index) { reveal(index, index * gap); });
        else reveal(0, 0);
        report();
      }
      $("#start").on("click", start);
      $("#reset").on("click", reset);
      $("#mode").on("change", reset);
      reset();
    });
  </script>
</body>
</html>

Initially zero elements are visible and none completed.

mode=stagger
state=ready
visible=0/3
completed=(none)
  1. Start in Staggered starts. start beta can precede done alpha; starts are separated by 200ms targets.
  2. Completion shows state=done, visible=3/3 and completed=alpha,beta,gamma. Start becomes available again.
  3. Select Wait for completion, which resets. Start gives start alpha → done alpha → start beta → done beta → start gamma → done gamma.
  4. Reset during playback returns to hidden initial state. Waiting should not revive old effects. Start again to replay.
  5. Enable reduced motion in the OS or developer tools, then Start: all items show immediately with Reduced motion: shown immediately.

Repeated clicks and reset

Start is disabled while busy, and the function checks busy as well. Programmatic calls therefore cannot stack another sequence. Completion is collected per element; one callback does not mean the entire set finished.

.stop(true, true) clears waiting effects and settles the current effect at its end. Reset first advances generation to invalidate old callbacks, then settles and hides to a known initial state. jumpToEnd can invoke completion, so do not settle before invalidation.

This avoids leftover queue and effect state on replay. clearQueue alone does not stop an active effect. Separate native setTimeout timers would require separate cancellation; this example uses delay in the default fx queue.

The logging .queue() records a start and calls next() so fadeIn can proceed. Omitting next() stalls that queue. Serial mode schedules the next index only from completion instead of starting every item upfront.

Preferences and actual layout

With prefers-reduced-motion set to reduce, both waiting and fading are bypassed and the final state is established immediately. This changes only the example, not global jQuery settings. Decorative sequencing should not delay access to important information.

Revealing display:none items introduces layout space and can move later content. Reserve container space or choose another display technique if positions must remain stable. Focusable controls also require a deliberate keyboard and focus design when hidden.

For dynamic additions, define the current collection and completion count anew. $items here stores the initial three and does not automatically include later nodes. Decide whether to replay everything or animate only new items, then align reset and queue ownership.