Как и jQuery.closest(), но пересекая потомков?
Есть ли функция, похожая на jQuery
.closest()
, но для перемещения потомков и возвращения только ближайших?
Я знаю, что есть функция .find()
, но она возвращает все возможные совпадения, а не самые близкие.
Edit:
Вот определение ближайшего (по крайней мере, для меня):
Во-первых, все дети пересекаются, затем проходят все дети каждого ребенка.
В приведенном ниже примере id='2'
является ближайшим .closest
потомками id="find-my-closest-descendant"
<div id="find-my-closest-descendant">
<div>
<div class="closest" Id='1'></div>
</div>
<div class="closest" Id='2'></div>
</div>
См. ссылка JSfiddle.
Ответы
Ответ 1
Согласно вашему определению closest
, я написал следующий плагин:
(function($) {
$.fn.closest_descendent = function(filter) {
var $found = $(),
$currentSet = this; // Current place
while ($currentSet.length) {
$found = $currentSet.filter(filter);
if ($found.length) break; // At least one match: break loop
// Get all children of the current set
$currentSet = $currentSet.children();
}
return $found.first(); // Return first match of the collection
}
})(jQuery);
Ответ 2
Если под "ближайшим" потомком вы подразумеваете первого ребенка, то вы можете сделать:
$('#foo').find(':first');
Или:
$('#foo').children().first();
Или, чтобы найти первое вхождение определенного элемента, вы можете сделать:
$('#foo').find('.whatever').first();
Или:
$('#foo').find('.whatever:first');
Действительно, нам нужно твердое определение того, что означает "ближайший потомок".
например.
<div id="foo">
<p>
<span></span>
</p>
<span></span>
</div>
Какой <span>
будет $('#foo').closestDescendent('span')
возвращен?
Ответ 3
Вы можете использовать find
с помощью селектора :first
:
$('#parent').find('p:first');
В приведенной выше строке будет найден первый элемент <p>
у потомков #parent
.
Ответ 4
Как насчет этого подхода?
$('find-my-closest-descendant').find('> div');
Этот "прямой детский" селектор работает для меня.
Ответ 5
Чистое решение 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>
Ответ 6
Если кто-то ищет чистое решение JS (использующее ES6 вместо jQuery), вот то, которое я использую:
Element.prototype.QuerySelector_BreadthFirst = function(selector) {
let currentLayerElements = [...this.childNodes];
while (currentLayerElements.length) {
let firstMatchInLayer = currentLayerElements.find(a=>a.matches && a.matches(selector));
if (firstMatchInLayer) return firstMatchInLayer;
currentLayerElements = currentLayerElements.reduce((acc, item)=>acc.concat([...item.childNodes]), []);
}
return null;
};
Ответ 7
Я думаю, что сначала вы должны определить, что означает "ближайший". Если вы имеете в виду потомок node, соответствующий вашим критериям, который является самым коротким расстоянием в терминах родительских ссылок, то использование ": first" или ".eq(0)" не обязательно будет работать:
<div id='start'>
<div>
<div>
<span class='target'></span>
</div>
</div>
<div>
<span class='target'></span>
</div>
</div>
В этом примере второй элемент ".target" <span>
"ближе" к "start" <div>
, потому что он только один родительский хоп прочь. Если это то, что вы подразумеваете под "ближайшим", вам нужно найти минимальное расстояние в функции фильтра. Список результатов из селектора jQuery всегда находится в порядке DOM.
Может быть:
$.fn.closestDescendant = function(sel) {
var rv = $();
this.each(function() {
var base = this, $base = $(base), $found = $base.find(sel);
var dist = null, closest = null;
$found.each(function() {
var $parents = $(this).parents();
for (var i = 0; i < $parents.length; ++i)
if ($parents.get(i) === base) break;
if (dist === null || i < dist) {
dist = i;
closest = this;
}
});
rv.add(closest);
});
return rv;
};
Этот тип взломанного плагина из-за того, как он создает объект результата, но идея в том, что вам нужно найти самый короткий родительский путь из всех подходящих элементов, которые вы найдете. Этот код смещается к элементам влево в дереве DOM из-за проверки <
; <=
будет смещаться вправо.
Ответ 8
Я подготовил это, а не для позиционных селекторов (им нужно больше, чем просто matchesSelector
):
Демо: http://jsfiddle.net/TL4Bq/3/
(function ($) {
var matchesSelector = jQuery.find.matchesSelector;
$.fn.closestDescendant = function (selector) {
var queue, open, cur, ret = [];
this.each(function () {
queue = [this];
open = [];
while (queue.length) {
cur = queue.shift();
if (!cur || cur.nodeType !== 1) {
continue;
}
if (matchesSelector(cur, selector)) {
ret.push(cur);
return;
}
open.unshift.apply(open, $(cur).children().toArray());
if (!queue.length) {
queue.unshift.apply(queue, open);
open = [];
}
}
});
ret = ret.length > 1 ? jQuery.unique(ret) : ret;
return this.pushStack(ret, "closestDescendant", selector);
};
})(jQuery);
Вероятно, есть некоторые ошибки, они не очень сильно тестировали.
Ответ 9
Ответ Роб W не совсем сработал у меня. Я приспособил его к тому, что действительно работало.
//closest_descendent plugin
$.fn.closest_descendent = function(filter) {
var found = [];
//go through every matched element that is a child of the target element
$(this).find(filter).each(function(){
//when a match is found, add it to the list
found.push($(this));
});
return found[0]; // Return first match in the list
}
Ответ 10
Я бы использовал следующее, чтобы включить сам объект, если он соответствует селектору:
var jTarget = $("#doo");
var sel = '.pou';
var jDom = jTarget.find(sel).addBack(sel).first();
Разметка:
<div id="doo" class="pou">
poo
<div class="foo">foo</div>
<div class="pou">pooo</div>
</div>
Ответ 11
Несмотря на то, что это старая тема, я не мог удержаться от реализации моей ближайшей оболочки.
Поставляет первого найденного потомка с наименьшим количеством путешествий (сначала дыхание).
Один из них - рекурсивный (личный фаворит), другой - список томов без рекурсии в качестве расширений jQquery.
Надеюсь, что кто-то выиграет.
Примечание. Рекурсивное переполнение стека, и я улучшил другое, теперь симулятивное к предыдущему ответу.
jQuery.fn.extend( {
closestChild_err : function( selector ) { // recursive, Qaru when not found
var found = this.children( selector ).first();
if ( found.length == 0 ) {
found = this.children().closestChild( selector ).first(); // check all children
}
return found;
},
closestChild : function( selector ) {
var todo = this.children(); // start whith children, excluding this
while ( todo.length > 0 ) {
var found = todo.filter( selector );
if ( found.length > 0 ) { // found closest: happy
return found.first();
} else {
todo = todo.children();
}
}
return $();
},
});
Ответ 12
Следующий плагин возвращает n-ых ближайших потомков.
$.fn.getNthClosestDescendants = function(n, type) {
var closestMatches = [];
var children = this.children();
recursiveMatch(children);
function recursiveMatch(children) {
var matches = children.filter(type);
if (
matches.length &&
closestMatches.length < n
) {
var neededMatches = n - closestMatches.length;
var matchesToAdd = matches.slice(0, neededMatches);
matchesToAdd.each(function() {
closestMatches.push(this);
});
}
if (closestMatches.length < n) {
var newChildren = children.children();
recursiveMatch(newChildren);
}
}
return closestMatches;
};
Ответ 13
Я искал аналогичное решение (мне нужны все ближайшие потомки, т.е. ширина сначала + все совпадения, независимо от того, какой уровень он существует), вот что я в итоге сделал:
var item = $('#find-my-closest-descendant');
item.find(".matching-descendant").filter(function () {
var $this = $(this);
return $this.parent().closest("#find-my-closest-descendant").is(item);
}).each(function () {
// Do what you want here
});
Надеюсь, это поможет.
Ответ 14
Вы можете просто положить,
$("#find-my-closest-descendant").siblings('.closest:first');
Ответ 15
у вас есть много вариантов, однако $("#parent").children(".child");
- это fastet.
проверьте эту статью для получения более подробной информации и теста