jQuery / Selector / :nth-child() - Seleccionar posiciones an+b

En jQuery, :nth-child(an+b) selecciona elementos hermanos en posiciones que cumplen una fórmula, contando desde uno dentro de cada padre. p:nth-child(2) significa un p que es el segundo hijo de tipo elemento, no el segundo p. Usa :nth-of-type(2) para el segundo hermano p, o .eq(1) para el segundo elemento de un conjunto ya seleccionado.

Contar desde uno e incluir otras etiquetas

La documentación de :nth-child() sigue las reglas CSS de posición entre hermanos. El recuento empieza de nuevo para cada padre e incluye otras etiquetas. Los espacios de texto y comentarios no son elementos. Los elementos ocultos conservan su posición mientras permanezcan en el documento, así que el orden visible no basta.

Si un padre contiene h2, p, p, p:nth-child(1) queda vacío y p:nth-child(2) elige el primer p. No reúne primero los p para numerarlos después. Limita el ámbito a hijos directos con >, como en el ejemplo.

Interpretar an+b y fórmulas habituales

En la definición CSS de an+b, a y b son enteros y n toma enteros no negativos como 0, 1 y 2. Solo coinciden las posiciones positivas resultantes. Cero y las posiciones negativas no seleccionan nada. -n+3 produce 3, 2, 1, las primeras tres posiciones; no cuenta hacia atrás desde el final.

Condición Posiciones Significado
2 2 Segundo elemento de cada padre
2n o even 2, 4, 6, … Posiciones pares
2n+1 u odd 1, 3, 5, … Posiciones impares
3n+1 1, 4, 7, … Cada tercera posición desde uno
-n+3 1, 2, 3 Primeras tres posiciones

Añadir p delante de la condición conserva solo los p en esas posiciones. Si el cuarto hijo es span, p:nth-child(2n) lo excluye, pero no lo salta para renumerar como cuarto un p posterior.

Ejemplo completo con etiquetas mezcladas

Guarda todo el código en UTF-8 como nth-child-demo.html y ábrelo en un navegador. No necesitas instalar nada, pero sí internet para cargar el archivo oficial de jQuery desde su red de distribución de contenidos, o CDN. El código usa 4.0.0; las fórmulas, inserción, restablecimiento y comparación se ejecutaron con 3.7.1 y 4.0.0.

A contiene h2, p, p, span, p; B contiene h2, p, p, p. El resultado muestra los identificadores de los p para compararlos con las posiciones de las etiquetas.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Sibling positions and formulas</title>
  <style>
    body { font: 16px/1.6 sans-serif; margin: 24px; }
    .group { border: 1px solid #aaa; padding: 12px; margin: 12px 0; }
    .group > * { display: block; margin: 6px 0; padding: 6px; }
    .group > .marked { background: #fff0b3; outline: 2px solid #b56b00; }
    button, select { padding: 8px; margin: 4px; }
  </style>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
</head>
<body>
  <h1>Sibling positions and formulas</h1>
  <div id="groups">
    <section class="group" id="group-a">
      <h2>A heading</h2>
      <p id="a1">A paragraph 1</p>
      <p id="a2">A paragraph 2</p>
      <span>A note</span>
      <p id="a3">A paragraph 3</p>
    </section>
    <section class="group" id="group-b">
      <h2>B heading</h2>
      <p id="b1">B paragraph 1</p>
      <p id="b2">B paragraph 2</p>
      <p id="b3">B paragraph 3</p>
    </section>
  </div>
  <label for="rule">Formula:</label>
  <select id="rule">
    <option value="2">2</option>
    <option value="2n">2n (even)</option>
    <option value="2n+1">2n+1 (odd)</option>
    <option value="3n+1">3n+1</option>
    <option value="-n+3">-n+3</option>
  </select>
  <button type="button" id="prepend">Prepend notice to A</button>
  <button type="button" id="reset">Reset</button>
  <output id="result" aria-live="polite"></output>
  <script>
    $(function () {
      function refresh() {
        $("#groups > .group > p").removeClass("marked");
        const rule = $("#rule").val();
        const $chosen = $("#groups > .group > p:nth-child(" + rule + ")");
        $chosen.addClass("marked");
        const ids = $chosen.map((_, item) => item.id).get().join(", ");
        $("#result").text(`${ids || "none"} | ${$chosen.length}`);
      }
      $("#rule").on("change", refresh);
      $("#prepend").on("click", function () {
        if (!$("#extra").length) {
          $("#group-a").prepend('<div id="extra">Notice</div>');
        }
        refresh();
      });
      $("#reset").on("click", function () {
        $("#extra").remove();
        $("#rule").val("2");
        refresh();
      });
      refresh();
    });
  </script>
</body>
</html>

La fórmula inicial es 2. El segundo elemento de cada grupo es su primer p: se resaltan a1 y b1.

a1, b1 | 2
  1. 2n produce a1, b1, b3 | 3. El cuarto elemento de A es span y no cumple p.
  2. 2n+1 produce a2, a3, b2 | 3. El h2 inicial queda excluido.
  3. 3n+1 produce b3 | 1. Las posiciones 1 y 4 de A son h2 y span; solo la cuarta de B es p.
  4. -n+3 produce a1, a2, b1, b2 | 4: los p entre los primeros tres hijos de tipo elemento de cada padre.
  5. Pulsa Reset y después Prepend notice to A. El div insertado al principio convierte h2 en el segundo hijo de A. Con fórmula 2 queda b1 | 1. Las pulsaciones repetidas no crean duplicados.
  6. Reset elimina solo el div añadido, restaura la fórmula 2 y consulta otra vez. Vuelven el contenido original y a1, b1 | 2.

Comparar :nth-of-type() y .eq()

Tras Reset, ejecuta este fragmento en la consola de desarrollo. :nth-of-type() cuenta hermanos de la misma etiqueta; .eq() cuenta desde cero el conjunto ya seleccionado.

const ids = $set => $set.map((_, item) => item.id).get().join(", ");
console.log(ids($("#groups > .group > p:nth-child(2)")));
console.log(ids($("#groups > .group > p:nth-of-type(2)")));
console.log(ids($("#groups > .group > p").eq(1)));
a1, b1
a2, b2
a2

La primera línea devuelve a1 y b1, segundos hijos de tipo elemento que son p. La segunda devuelve a2 y b2, segundos p de cada padre. La última devuelve solo a2, en el índice 1 del conjunto total de p. Un índice negativo de .eq() cuenta desde el final del conjunto y difiere de -n+3.

Documento dinámico y resultados inesperados

Las selecciones no reflejan automáticamente los cambios del documento. Un conjunto con a1 y b1 guardado antes de insertar mantiene esos elementos después. Consulta de nuevo, como refresh, y elimina las marcas anteriores para reflejar posiciones actuales.

Si queda vacío, revisa posiciones cero o inexistentes, la etiqueta real y los títulos, avisos o elementos ocultos intermedios. En tablas, inspecciona también padres intermedios como tbody en el modelo de objetos del documento, DOM. Si no responde, comprueba primero la carga de jQuery en Network y los errores en Console.