Encuentra el elemento ancestro más cercano que tenga una clase específica


225

¿Cómo puedo encontrar el ancestro de un elemento que está más cerca del árbol que tiene una clase particular, en JavaScript puro ? Por ejemplo, en un árbol así:

<div class="far ancestor">
    <div class="near ancestor">
        <p>Where am I?</p>
    </div>
</div>

Entonces quiero div.near.ancestorsi intento esto en el py busco ancestor.


1
tenga en cuenta que el div externo no es un padre, es un antepasado del pelemento. Si en realidad solo desea obtener el nodo primario, puede hacerlo ele.parentNode.
Felix Kling

@FelixKling: No conocía esa terminología; Lo cambiaré.
rvighne

3
En realidad es lo mismo que usamos los humanos :) El padre (padre) de su padre (padre) no es su padre (padre), es su abuelo (abuelo), o más en general, su antepasado.
Felix Kling

Respuestas:


346

Actualización: ahora compatible con la mayoría de los principales navegadores

document.querySelector("p").closest(".near.ancestor")

Tenga en cuenta que esto puede coincidir con selectores, no solo clases

https://developer.mozilla.org/en-US/docs/Web/API/Element.closest


Para los navegadores heredados que no son compatibles closest()pero tienen matches()uno, puede crear una coincidencia de selector similar a la coincidencia de clase de @ rvighne:

function findAncestor (el, sel) {
    while ((el = el.parentElement) && !((el.matches || el.matchesSelector).call(el,sel)));
    return el;
}

1
Todavía no es compatible con las versiones actuales de Internet Explorer, Edge y Opera Mini.
kleinfreund

1
@kleinfreund: todavía no es compatible con IE, Edge u Opera mini. caniuse.com/#search=closest
evolutionxbox

8
Junio ​​de 2016: Parece que todos los navegadores aún no se han puesto al día
Kunal

3
Hay un polyfill DOM Nivel 4 con closestmuchas más características de recorrido DOM más avanzadas. Puede extraer esa implementación por sí solo o incluir todo el paquete para obtener muchas características nuevas en su código.
Garbee

2
Edge 15 tiene soporte, que debería cubrir todos los principales navegadores. A menos que cuente IE11, pero eso no recibe ninguna actualización
8472

195

Esto hace el truco:

function findAncestor (el, cls) {
    while ((el = el.parentElement) && !el.classList.contains(cls));
    return el;
}

El ciclo while espera hasta que eltenga la clase deseada, y se establece elen elel padre de cada iteración para que al final, tenga el antepasado con esa clase o null.

Aquí hay un violín , si alguien quiere mejorarlo. No funcionará en navegadores antiguos (es decir, IE); vea esta tabla de compatibilidad para classList . parentElementse usa aquí porque parentNodeimplicaría más trabajo para asegurarse de que el nodo sea un elemento.


1
Para una alternativa a .classList, consulte stackoverflow.com/q/5898656/218196 .
Felix Kling

1
Arreglé el código, pero aún arrojaría un error si no hay un antepasado con ese nombre de clase.
Felix Kling

2
Uh, pensé que no existía, pero estaba equivocado . De todos modos, parentElementes una propiedad bastante nueva (DOM nivel 4) y parentNodeexiste desde ... para siempre. Por lo tanto, parentNodetambién funciona en navegadores antiguos, mientras que parentElementpuede que no. Por supuesto, podría hacer el mismo argumento a favor / en contra classList, pero no tiene una alternativa simple, como parentElementtiene. De hecho, me pregunto por qué parentElementexiste en absoluto, no parece agregar ningún valor parentNode.
Felix Kling

1
@FelixKling: recién editado, ahora funciona bien para el caso en que no exista tal antepasado. Debo haberme entusiasmado con la versión original corta.
rvighne

3
Si desea verificar el nombre de la clase en el elemento del parámetro en sí, antes de buscar sus antepasados, simplemente puede cambiar el orden de las condiciones en el bucle:while (!el.classList.contains(cls) && (el = el.parentElement));
Nicomak

34

Use element.closest ()

https://developer.mozilla.org/en-US/docs/Web/API/Element/closest

Ver este ejemplo DOM:

<article>
  <div id="div-01">Here is div-01
    <div id="div-02">Here is div-02
      <div id="div-03">Here is div-03</div>
    </div>
  </div>
</article>

Así es como usarías element.closest:

var el = document.getElementById('div-03');

var r1 = el.closest("#div-02");  
// returns the element with the id=div-02

var r2 = el.closest("div div");  
// returns the closest ancestor which is a div in div, here is div-03 itself

var r3 = el.closest("article > div");  
// returns the closest ancestor which is a div and has a parent article, here is div-01

var r4 = el.closest(":not(div)");
// returns the closest ancestor which is not a div, here is the outmost article

14

Basado en la respuesta the8472 y https://developer.mozilla.org/en-US/docs/Web/API/Element/matches aquí está la solución multiplataforma 2017:

if (!Element.prototype.matches) {
    Element.prototype.matches =
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector ||
        function(s) {
            var matches = (this.document || this.ownerDocument).querySelectorAll(s),
                i = matches.length;
            while (--i >= 0 && matches.item(i) !== this) {}
            return i > -1;
        };
}

function findAncestor(el, sel) {
    if (typeof el.closest === 'function') {
        return el.closest(sel) || null;
    }
    while (el) {
        if (el.matches(sel)) {
            return el;
        }
        el = el.parentElement;
    }
    return null;
}

11

La solución @rvighne funciona bien, pero como se identifica en los comentarios ParentElementy ClassListambos tienen problemas de compatibilidad. Para hacerlo más compatible, he usado:

function findAncestor (el, cls) {
    while ((el = el.parentNode) && el.className.indexOf(cls) < 0);
    return el;
}
  • parentNodepropiedad en lugar de la parentElementpropiedad
  • indexOfmétodo en la classNamepropiedad en lugar del containsmétodo en la classListpropiedad.

Por supuesto, indexOf simplemente está buscando la presencia de esa cadena, no le importa si es la cadena completa o no. Entonces, si tuviera otro elemento con la clase 'ancestro-type', aún así devolvería haber encontrado 'ancestor', si esto es un problema para usted, tal vez pueda usar regexp para encontrar una coincidencia exacta.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.