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

发布于 2024-12-22 10:10:03 字数 177 浏览 1 评论 0原文

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

PS 我的 javascript 代码应该在不同的平台上运行
第三方网站,所以我不能对 html 做出任何假设。

I need to generate unique css selector for elements.
Particularly, I have onclick event handler that should remember what target element
was clicked and send this info to my server. Is there a way to do it without doing DOM modifications?

P.S. my javascript code supposed to be run on different
3-rd party websites so I can't make any assumptions about html.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

深居我梦 2024-12-29 10:10:03

该函数创建了一个很长但非常实用的独特选择器,工作速度很快。

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)

This function creates a long, but quite practical unique selector, works quickly.

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();
};

Example result:

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)

The following code creates a slightly more beautiful and short selector

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();
};

Example result:

html > body > div > div > main > div:nth-child(2) > p:nth-child(2)
清醇 2024-12-29 10:10:03

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

Build Status

  • 生成最短选择器
  • 每页独特的选择器
  • 稳定且强大的选择器
  • 2.9 kB gzip 和缩小大小

生成的选择器示例:

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

Check this CSS selector generator library @medv/finder

Build Status

  • Generates shortest selectors
  • Unique selectors per page
  • Stable and robust selectors
  • 2.9 kB gzip and minify size

Example of generated selector:

.blog > article:nth-child(3) .add-comment
捶死心动 2024-12-29 10:10:03

是的,你可以这样做。但有一些注意事项。为了能够保证选择器是唯一的,您需要使用并未得到普遍支持的 :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 参数,将 this 的所有实例替换为event.target 然后将该函数附加到 window 的点击事件(如果这样更简单)。

Yes, you could do this. But with a few caveats. In order to be able to guarantee that selectors are unique, you'd need to use :nth-child() which isn't universally supported. If you're then wanting to put these CSS selectors into CSS files, it won't work in all browsers.

I'd do it with something like this:

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);
}

Then add that to your click handler for each element you want to model. I'll leave you to implement getChildNumber().

Edit: Just seen your comment about it being 3rd party code... so you could add an event argument, replace all instances of this with event.target and then just attach the function to window's click event if that's easier.

萌吟 2024-12-29 10:10:03

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

  1. 如果元素有唯一的id 属性,返回 id 选择器#

  2. 如果标签是 htmlheadbody,则返回类型选择器 .

  3. 对于元素的每个类:

    1. 如果该类对于该元素是唯一的,则返回类选择器.

    2. 如果标签/类组合是唯一的,则返回选择器.

    3. 计算元素在其父元素的子列表中的索引。如果选择器 .:nth-child() 是唯一的,则返回该选择器。

  4. index 为其父元素的子列表中的索引。

    1. 如果父节点是根节点,则返回选择器:nth-child()

    2. 否则,递归查找父节点的唯一选择器并返回> <元素名称>:nth-child(<索引>)

The Firefox developer console uses the following heuristics to find a unique selector for an element. The algorithm returns the first suitable selector found. (Source)

  1. If the element has a unique id attribute, return the id selector #<idname>.

  2. If the tag is html, head, or body, return the type selector <elementname>.

  3. For each class of the element:

    1. If the class is unique to the element, return the class selector .<classname>.

    2. If the tag/class combination is unique, return the selector <elementname>.<classname>.

    3. Compute the element's index in its parent's child list. If the selector <elementname>.<classname>:nth-child(<index>) is unique, return that.

  4. Let index be the element's index in its parent's child list.

    1. If the parent node is the root node, return the selector <elementname>:nth-child(<index>).

    2. Otherwise, recursively find a unique selector for the parent node and return <parentselector> > <elementname>:nth-child(<index>).

枕头说它不想醒 2024-12-29 10:10:03

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

Firebug 有一个功能,可以使用 XPath 和 CSS 选择器。

请参阅此答案

You could probably traverse the DOM tree from the node back to the body element to generate a selector.

Firebug has a feature for this, both using XPath and CSS selectors.

See this answer

甜`诱少女 2024-12-29 10:10:03

假设您有一个链接列表,为了简单起见:您可以简单地传递

<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/

let say you have a list of links for the sake of simplicity: you can simply pass the index of the triggering element in the collection of all elements

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

the js (jQuery 1.7+, I used .on()otherwise use bind()) function can be

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

so that if you click on third element index value passed will be 2. (0-based index);
of course this is valid as long as the code (the DOM) doesn't change. Later you can use that index to create a css rule to that element e.g. using :nth-child

Otherwise if each one of your elements have a different attribute (like an id) you can pass that attribute

example on JsFiddle : http://jsfiddle.net/t7J8T/

喜你已久 2024-12-29 10:10:03
"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。

"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"

   });
});

you can try this one. without using jquery.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文