如何为 DOM 元素生成唯一的 CSS 选择器?

Posted

技术标签:

【中文标题】如何为 DOM 元素生成唯一的 CSS 选择器?【英文标题】:How to generate unique css selector for DOM element? 【发布时间】:2012-01-25 04:12:23 【问题描述】:

我需要为元素生成唯一的 CSS 选择器。 特别是,我有 onclick 事件处理程序,它应该记住什么目标元素 被点击并将此信息发送到我的服务器。有没有办法在不修改 DOM 的情况下做到这一点?

附:我的 javascript 代码应该在不同的上运行 第三方网站,所以我无法对 html 做出任何假设。

【问题讨论】:

能否提供选择元素所需的html? 其实我没有得到“我的”html,因为我的javascript代码是插入到一些第三方网站中的,所以html代码有一些随意的结构 Getting a jQuery selector for an element 的可能重复项 @Dan Dascalescu:我会小心地将有关标准选择器的问题标记为有关众所周知的非标准实现的问题的副本。大多数标准选择器都与 jQuery 兼容,但反之则不然。 @BoltClock:我确实在对该问题的评论中询问过 OP 是否真的在寻找 jQuery 或 CSS 选择器。 【参考方案1】:

您可能会遍历 DOM 树从节点返回到 body 元素以生成选择器。

Firebug 对此有一个功能,它使用 XPath 和 CSS 选择器。

见this answer

【讨论】:

你能推荐任何提供这种功能的javascript库吗? @tsds:生成 CSS 选择器的库有 10 多个,其中一个的作者已经发布了this comparison。【参考方案2】:

为了简单起见,假设您有一个链接列表:您可以简单地传递所有元素集合中触发元素的索引

<a href="#">...</a>
<a href="#">...</a>    
<a href="#">...</a>

js(jQuery 1.7+,我用.on()否则用bind())函数即可

var triggers = $('a');
triggers.on('click', function(e) 
   e.preventDefault();
   var index = triggers.index($(this));
   /* ajax call passing index value */
);

这样,如果您单击第三个元素,传递的索引值将是 2。(从 0 开始的索引); 当然,只要代码(DOM)不变,这是有效的。稍后您可以使用该索引为该元素创建一个 css 规则,例如使用:nth-child

否则,如果您的每个元素都有不同的属性(如 id),您可以传递该属性

JsFiddle 上的示例:http://jsfiddle.net/t7J8T/

【讨论】:

这是一个相当复杂的问题的简单解决方案——生成独特的 CSS 选择器,理想情况下对页面结构的变化有一定的鲁棒性。生成 CSS 选择器的库有 10 多个,其中一个的作者发布了this comparison。 @Dan Dascalescu:不幸的是,选择器本身并没有提供太多解决此类问题的方法。除了 id 和 class 选择器之外,Selectors 中的几乎所有其他内容都在设计上与文档结构紧密耦合。无论结构如何,您可以构造的唯一标识元素的唯一选择器是单独的 id 选择器,即使它假设文档是一致的。 (我不确定 :root 是否保证符合标准的文档中的唯一元素,但如果确实如此,那将只是另一个选择器,而不是一个特别有用的选择器。)【参考方案3】:

是的,你可以这样做。但有一些警告。为了能够保证选择器是唯一的,您需要使用并非普遍支持的:nth-child()。如果您想将这些 CSS 选择器放入 CSS 文件中,则并非在所有浏览器中都有效。

我会这样做:

function () 
    if (this.id) 
        return sendToServer('#' + this.id);
    
    var parent = this.parentNode;
    var selector = '>' + this.nodeName + ':nth-child(' + getChildNumber(this) ')';
    while (!parent.id && parent.nodeName.toLowerCase() !== 'body') 
        selector = '>' + this.nodeName + ':nth-child(' + getChildNumber(parent) + ')' + selector;
        parent = parent.parentNode;
    
    if (parent.nodeName === 'body') 
        selector = 'body' + selector;
     else 
        selector = '#' + parent.id + selector;
    
    return sendToServer(selector);

然后将它添加到您要建模的每个元素的点击处理程序中。我会让你实现getChildNumber()

编辑:刚刚看到您关于它是第 3 方代码的评论...所以您可以添加一个 event 参数,用 event.target 替换所有 this 实例,然后附加window 的点击事件的函数,如果这更容易的话。

【讨论】:

生成独特的 CSS 选择器在理想情况下对页面结构的变化具有一定的鲁棒性是一个复杂的问题。生成 CSS 选择器的库有 10 多个,其中一个的作者发布了this comparison。【参考方案4】:
"use strict";
function getSelector(_context)
     var index, localName,pathSelector, that = _context, node;
     if(that =='null') throw 'not an  dom reference';
     index =  getIndex(that);

     while(that.tagName)
       pathSelector = that.localName+(pathSelector?'>'+pathSelector :'');
       that = that.parentNode;
     
    pathSelector = pathSelector+':nth-of-type('+index+')';

    return pathSelector;


function getIndex(node)
    var i=1;
    var tagName = node.tagName;

    while(node.previousSibling)
    node = node.previousSibling;
        if(node.nodeType === 1 && (tagName.toLowerCase() == node.tagName.toLowerCase()))
            i++;
        
    
    return i;

document.addEventListener('DOMContentLoaded', function()
   document.body.addEventListener('mouseover', function(e)
     var c = getSelector(e.target);
         var  element = document.querySelector(c);
          //console.log(element);
      console.log(c);
        //element.style.color = "red"

   );
);

你可以试试这个。不使用 jquery。

【讨论】:

只使用 nth-of-type 选择器并忽略 id 会导致选择器对页面结构的更改不太健壮。这个问题可能看起来很简单,但实际上它有点复杂——生成独特的 CSS 选择器,理想情况下对页面结构的变化有一定的鲁棒性。生成 CSS 选择器的库有 10 多个,其中一个的作者发布了this comparison。【参考方案5】:

检查这个 CSS 选择器生成器库@medv/finder

生成最短选择器 每页的唯一选择器 稳定而健壮的选择器 2.9 kB gzip 并缩小大小

生成选择器示例:

.blog > article:nth-child(3) .add-comment

【讨论】:

【参考方案6】:

这个函数创建了一个长而实用的独特选择器,运行速度很快。

const getCssSelector = (el) => 
  let path = [], parent;
  while (parent = el.parentNode) 
    path.unshift(`$el.tagName:nth-child($[].indexOf.call(parent.children, el)+1)`);
    el = parent;
  
  return `$path.join(' > ')`.toLowerCase();
;

示例结果:

html:nth-child(1) > body:nth-child(2) > div:nth-child(1) > div:nth-child(1) > main:nth-child(3) > div:nth-child(2) > p:nth-child(2)

以下代码创建了一个更漂亮更短的选择器

const getCssSelectorShort = (el) => 
  let path = [], parent;
  while (parent = el.parentNode) 
    let tag = el.tagName, siblings;
    path.unshift(
      el.id ? `#$el.id` : (
        siblings = parent.children,
        [].filter.call(siblings, sibling => sibling.tagName === tag).length === 1 ? tag :
        `$tag:nth-child($1+[].indexOf.call(siblings, el))`
      )
    );
    el = parent;
  ;
  return `$path.join(' > ')`.toLowerCase();
;

示例结果:

html > body > div > div > main > div:nth-child(2) > p:nth-child(2)

【讨论】:

您可以在“el.id”第一次出现时取消移位和中断 getCssSelectorShort。如果您有 id,则无需获取任何底层选择器。 向短版添加类:jsfiddle.net/Sillvva/qof6h0up [].indexOf.call(parent.children, el) + 1[...parent.children].indexOf(el) + 1【参考方案7】:

Firefox 开发者控制台使用以下启发式方法来查找元素的唯一选择器。该算法返回找到的第一个合适的选择器。 (Source)

    如果元素具有唯一的id 属性,则返回id 选择器#&lt;idname&gt;

    如果标签是htmlheadbody,则返回类型选择器&lt;elementname&gt;

    对于元素的每个类:

      如果类对元素是唯一的,则返回类选择器.&lt;classname&gt;

      如果标签/类组合是唯一的,则返回选择器&lt;elementname&gt;.&lt;classname&gt;

      计算元素在其父子列表中的索引。如果选择器 &lt;elementname&gt;.&lt;classname&gt;:nth-child(&lt;index&gt;) 是唯一的,则返回它。

    index 成为元素在其父子列表中的索引。

      如果父节点是根节点,则返回选择器&lt;elementname&gt;:nth-child(&lt;index&gt;)

      否则,递归查找父节点的唯一选择器并返回&lt;parentselector&gt; &gt; &lt;elementname&gt;:nth-child(&lt;index&gt;)

【讨论】:

以上是关于如何为 DOM 元素生成唯一的 CSS 选择器?的主要内容,如果未能解决你的问题,请参考以下文章

css中如何获取dom元素

Sonarqube css:如何为前缀为“pr-”的选择器禁用“意外的未知类型”规则

css选择器详解

如何为数组中的每个创建一个具有唯一图像和选择器的自定义 UIButton?

JQ基本选择器

jQuery选择器