Чистое решение JS (с использованием ES6).
export function closestDescendant(root, selector) {
const elements = [root];
let e;
do { e = elements.shift(); } while (!e.matches(selector) && elements.push(...e.children));
return e.matches(selector) ? e : null;
}
Пример
Учитывая следующую структуру:
div == $0
├── div == $1
│ ├── div
│ ├── div.findme == $4
│ ├── div
│ └── div
├── div.findme == $2
│ ├── div
│ └── div
└── div == $3
├── div
├── div
└── div
closestDescendant($0, '.findme') === $2;
closestDescendant($1, '.findme') === $4;
closestDescendant($2, '.findme') === $2;
closestDescendant($3, '.findme') === null;
function closestDescendant(root, selector) {
const elements = [root];
let e;
do { e = elements.shift(); } while (!e.matches(selector) && elements.push(...e.children));
return e.matches(selector) ? e : null;
}
const [$0, $1, $2, $3, $4] = [0, 1, 2, 3, 4].map(x => document.querySelector(`#e${x}`));
console.log(closestDescendant($0, '.findme')); // $2
console.log(closestDescendant($1, '.findme')); // $4
console.log(closestDescendant($2, '.findme')); // $2
console.log(closestDescendant($3, '.findme')); // null
<div id="e0">
<div id="e1">
<div></div>
<div id="e4" class="findme"></div>
<div></div>
<div></div>
</div>
<div id="e2" class="findme">
<div></div>
<div></div>
</div>
<div id="e3">
<div></div>
<div></div>
<div></div>
</div>
</div>