用户在HTML页面中选择两个或多个元素。我想要完成的是找到那些元素的共同祖先(如果以前没有找到,那么身体节点将是共同的祖先)?
P.S:可以用XPath实现,但对我来说这不是一个更好的选择。也可以通过css选择器解析找到它,但我认为它是一个脏方法(?)谢谢。
答案 0 :(得分:65)
这是一个更高效的纯JavaScript版本。
function parents(node) {
var nodes = [node]
for (; node; node = node.parentNode) {
nodes.unshift(node)
}
return nodes
}
function commonAncestor(node1, node2) {
var parents1 = parents(node1)
var parents2 = parents(node2)
if (parents1[0] != parents2[0]) throw "No common ancestor!"
for (var i = 0; i < parents1.length; i++) {
if (parents1[i] != parents2[i]) return parents1[i - 1]
}
}
答案 1 :(得分:43)
涉及手动遍历祖先元素的解决方案远比必要复杂得多。您不需要手动执行循环。使用parents()
获取一个元素的所有祖先元素,将其缩小为包含has()
的第二个元素的元素,然后使用first()
获取第一个元素。
var a = $('#a'),
b = $('#b'),
closestCommonAncestor = a.parents().has(b).first();
答案 2 :(得分:8)
试试这个:
function get_common_ancestor(a, b)
{
$parentsa = $(a).parents();
$parentsb = $(b).parents();
var found = null;
$parentsa.each(function() {
var thisa = this;
$parentsb.each(function() {
if (thisa == this)
{
found = this;
return false;
}
});
if (found) return false;
});
return found;
}
像这样使用:
var el = get_common_ancestor("#id_of_one_element", "#id_of_another_element");
这很快就被吓坏了,但它应该有效。如果你想要稍微不同的东西(例如jQuery对象返回而不是DOM元素,DOM元素作为参数而不是ID等),应该很容易修改。
答案 3 :(得分:8)
这是另一个使用element.compareDocumentPosition()
和element.contains()
的纯方法,前者是标准方法,后者是大多数主流浏览器支持的方法,不包括Firefox:
function getCommonAncestor(node1, node2) {
var method = "contains" in node1 ? "contains" : "compareDocumentPosition",
test = method === "contains" ? 1 : 0x10;
while (node1 = node1.parentNode) {
if ((node1[method](node2) & test) === test)
return node1;
}
return null;
}
工作演示:http://jsfiddle.net/3FaRr/ (使用lonesomeday的测试用例)
这应该或多或少地尽可能高效,因为它是纯DOM并且只有一个循环。
再看看这个问题,我注意到答案忽略了“两个或多个”要求的“或更多”部分。所以我决定稍微调整一下以允许指定任意数量的节点:
function getCommonAncestor(node1 /*, node2, node3, ... nodeN */) {
if (arguments.length < 2)
throw new Error("getCommonAncestor: not enough parameters");
var i,
method = "contains" in node1 ? "contains" : "compareDocumentPosition",
test = method === "contains" ? 1 : 0x0010,
nodes = [].slice.call(arguments, 1);
rocking:
while (node1 = node1.parentNode) {
i = nodes.length;
while (i--) {
if ((node1[method](nodes[i]) & test) !== test)
continue rocking;
}
return node1;
}
return null;
}
答案 4 :(得分:5)
上面提到的Range API的commonAncestorContainer属性,以及它的selectNode,使得这很简单。
在Firefox的Scratchpad或类似的编辑器中运行(“显示”)此代码:
var range = document.createRange();
var nodes = [document.head, document.body]; // or any other set of nodes
nodes.forEach(range.selectNode, range);
range.commonAncestorContainer;
请注意,IE 8或更低版本不支持这两种API。
答案 5 :(得分:3)
您应该能够使用jQuery .parents()
函数,然后浏览结果以查找第一个匹配项。 (或者我猜你可以从最后开始然后向后看,直到你看到第一个区别;这可能更好。)
答案 6 :(得分:2)
您还可以使用DOM Range
(当然,当浏览器支持时)。如果您创建Range
startContainer
,并将endContainer
设置为文档中的早期节点,并将Range
设置为文档中的后一个节点,那么commonAncestorContainer
attribute就是这样的function getCommonAncestor(node1, node2) {
var dp = node1.compareDocumentPosition(node2);
// If the bitmask includes the DOCUMENT_POSITION_DISCONNECTED bit, 0x1, or the
// DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC bit, 0x20, then the order is implementation
// specific.
if (dp & (0x1 | 0x20)) {
if (node1 === node2) return node1;
var node1AndAncestors = [node1];
while ((node1 = node1.parentNode) != null) {
node1AndAncestors.push(node1);
}
var node2AndAncestors = [node2];
while ((node2 = node2.parentNode) != null) {
node2AndAncestors.push(node2);
}
var len1 = node1AndAncestors.length;
var len2 = node2AndAncestors.length;
// If the last element of the two arrays is not the same, then `node1' and `node2' do
// not share a common ancestor.
if (node1AndAncestors[len1 - 1] !== node2AndAncestors[len2 - 1]) {
return null;
}
var i = 1;
for (;;) {
if (node1AndAncestors[len1 - 1 - i] !== node2AndAncestors[len2 - 1 - i]) {
// assert node1AndAncestors[len1 - 1 - i - 1] === node2AndAncestors[len2 - 1 - i - 1];
return node1AndAncestors[len1 - 1 - i - 1];
}
++i;
}
// assert false;
throw "Shouldn't reach here!";
}
// "If the two nodes being compared are the same node, then no flags are set on the return."
// http://www.w3.org/TR/DOM-Level-3-Core/core.html#DocumentPosition
if (dp == 0) {
// assert node1 === node2;
return node1;
} else if (dp & 0x8) {
// assert node2.contains(node1);
return node2;
} else if (dp & 0x10) {
// assert node1.contains(node2);
return node1;
}
// In this case, `node2' precedes `node1'. Swap `node1' and `node2' so that `node1' precedes
// `node2'.
if (dp & 0x2) {
var tmp = node1;
node1 = node2;
node2 = tmp;
} else {
// assert dp & 0x4;
}
var range = node1.ownerDocument.createRange();
range.setStart(node1, 0);
range.setEnd(node2, 0);
return range.commonAncestorContainer;
}
是最深的共同祖先节点。
以下是实现此想法的一些代码:
{{1}}
答案 7 :(得分:1)
这是对寂寞回答的概括。它不仅需要两个元素,而且需要一个完整的JQuery对象。
function CommonAncestor(jq) {
var prnt = $(jq[0]);
jq.each(function () {
prnt = prnt.parents().add(prnt).has(this).last();
});
return prnt;
}
答案 8 :(得分:1)
派对有点晚了,但这是一个优雅的jQuery解决方案(因为这个问题被标记为jQuery) -
/**
* Get all parents of an element including itself
* @returns {jQuerySelector}
*/
$.fn.family = function() {
var i, el, nodes = $();
for (i = 0; i < this.length; i++) {
for (el = this[i]; el !== document; el = el.parentNode) {
nodes.push(el);
}
}
return nodes;
};
/**
* Find the common ancestors in or against a selector
* @param selector {?(String|jQuerySelector|Element)}
* @returns {jQuerySelector}
*/
$.fn.common = function(selector) {
if (selector && this.is(selector)) {
return this;
}
var i,
$parents = (selector ? this : this.eq(0)).family(),
$targets = selector ? $(selector) : this.slice(1);
for (i = 0; i < $targets.length; i++) {
$parents = $parents.has($targets.eq(i).family());
}
return $parents;
};
/**
* Find the first common ancestor in or against a selector
* @param selector {?(String|jQuerySelector|Element)}
* @returns {jQuerySelector}
*/
$.fn.commonFirst = function(selector) {
return this.common(selector).first();
};
答案 9 :(得分:1)
这不再需要太多代码来解决:
步骤进行:
代码:
function getLowestCommonParent(node_a, node_b) {
while (node_a = node_a.parentElement) {
if (node_a.contains(node_b)) {
return node_a;
}
}
return null;
}
答案 10 :(得分:1)
晚了一点,这是一个JavaScript ES6版本,它使用Array.prototype.reduce()和Node.contains(),并且可以将任意数量的元素用作参数:
function closestCommonAncestor(...elements) {
const reducer = (prev, current) => current.parentElement.contains(prev) ? current.parentElement : prev;
return elements.reduce(reducer, elements[0]);
}
const element1 = document.getElementById('element1');
const element2 = document.getElementById('element2');
const commonAncestor = closestCommonAncestor(element1, element2);
答案 11 :(得分:1)
基于Andy E
和AntonB
的回答
处理边缘情况:node1 == node2
和 node1.contains(node2)
function getCommonParentNode(node1, node2) {
if (node1 == node2) return node1;
var parent = node1;
do if (parent.contains(node2)) return parent
while (parent = parent.parentNode);
return null;
}
答案 12 :(得分:0)
这是一种更脏的方式。它更容易理解,但需要修改dom:
function commonAncestor(node1,node2){
var tmp1 = node1,tmp2 = node2;
// find node1's first parent whose nodeType == 1
while(tmp1.nodeType != 1){
tmp1 = tmp1.parentNode;
}
// insert an invisible span contains a strange character that no one
// would use
// if you need to use this function many times,create the span outside
// so you can use it without creating every time
var span = document.createElement('span')
, strange_char = '\uee99';
span.style.display='none';
span.innerHTML = strange_char;
tmp1.appendChild(span);
// find node2's first parent which contains that odd character, that
// would be the node we are looking for
while(tmp2.innerHTML.indexOf(strange_char) == -1){
tmp2 = tmp2.parentNode;
}
// remove that dirty span
tmp1.removeChild(span);
return tmp2;
}
答案 13 :(得分:0)
PureJS
function getFirstCommonAncestor(nodeA, nodeB) {
const parentsOfA = this.getParents(nodeA);
const parentsOfB = this.getParents(nodeB);
return parentsOfA.find((item) => parentsOfB.indexOf(item) !== -1);
}
function getParents(node) {
const result = [];
while (node = node.parentElement) {
result.push(node);
}
return result;
}
答案 14 :(得分:0)
不喜欢以上任何答案(想要纯JavaScript和一个功能)。 对我来说非常有效,高效且易于理解:
const findCommonAncestor = (elem, elem2) => {
let parent1 = elem.parentElement,parent2 = elem2.parentElement;
let childrensOfParent1 = [],childrensOfParent2 = [];
while (parent1 !== null && parent2 !== null) {
if (parent1 !== !null) {
childrensOfParent2.push(parent2);
if (childrensOfParent2.includes(parent1)) return parent1;
}
if (parent2 !== !null) {
childrensOfParent1.push(parent1);
if (childrensOfParent1.includes(parent2)) return parent2;
}
parent1 = parent1.parentElement;
parent2 = parent1.parentElement;
}
return null;
};