Выполнение обратного поиска в селекторе CSS - сложная вещь.Обычно я сталкивался с двумя типами решений:
Перейдите вверх по дереву DOM, чтобы собрать строку селектора из комбинации имен элементов, классов и id
или name
атрибут.Проблема этого метода заключается в том, что он может привести к тому, что селекторы возвращают несколько элементов, которые не обрежут его, если мы требуем, чтобы они выбрали только один уникальный элемент.
Соберите строку селектораиспользуя nth-child()
или nth-of-type()
, что может привести к очень длинным селекторам.В большинстве случаев, чем длиннее селектор, тем выше специфичность, и чем выше специфичность, тем больше вероятность, что он сломается при изменении структуры DOM.
Приведенное ниже решение является попыткойрешая обе эти проблемы.Это гибридный подход, который выводит уникальный селектор CSS (т. Е. document.querySelectorAll(getUniqueSelector(el))
всегда должен возвращать массив из одного элемента).Хотя возвращаемая строка селектора не обязательно самая короткая, она выводится с учетом эффективности селектора CSS при одновременном выравнивании специфичности путем определения приоритетов nth-of-type()
и nth-child()
last.
Вы можете указать, какие атрибуты включить вселектор путем обновления массива aAttr
.Минимальное требование к браузеру - IE 9.
function getUniqueSelector(elSrc) {
if (!(elSrc instanceof Element)) return;
var sSel,
aAttr = ['name', 'value', 'title', 'placeholder', 'data-*'], // Common attributes
aSel = [],
// Derive selector from element
getSelector = function(el) {
// 1. Check ID first
// NOTE: ID must be unique amongst all IDs in an HTML5 document.
// https://www.w3.org/TR/html5/dom.html#the-id-attribute
if (el.id) {
aSel.unshift('#' + el.id);
return true;
}
aSel.unshift(sSel = el.nodeName.toLowerCase());
// 2. Try to select by classes
if (el.className) {
aSel[0] = sSel += '.' + el.className.trim().replace(/ +/g, '.');
if (uniqueQuery()) return true;
}
// 3. Try to select by classes + attributes
for (var i=0; i<aAttr.length; ++i) {
if (aAttr[i]==='data-*') {
// Build array of data attributes
var aDataAttr = [].filter.call(el.attributes, function(attr) {
return attr.name.indexOf('data-')===0;
});
for (var j=0; j<aDataAttr.length; ++j) {
aSel[0] = sSel += '[' + aDataAttr[j].name + '="' + aDataAttr[j].value + '"]';
if (uniqueQuery()) return true;
}
} else if (el[aAttr[i]]) {
aSel[0] = sSel += '[' + aAttr[i] + '="' + el[aAttr[i]] + '"]';
if (uniqueQuery()) return true;
}
}
// 4. Try to select by nth-of-type() as a fallback for generic elements
var elChild = el,
sChild,
n = 1;
while (elChild = elChild.previousElementSibling) {
if (elChild.nodeName===el.nodeName) ++n;
}
aSel[0] = sSel += ':nth-of-type(' + n + ')';
if (uniqueQuery()) return true;
// 5. Try to select by nth-child() as a last resort
elChild = el;
n = 1;
while (elChild = elChild.previousElementSibling) ++n;
aSel[0] = sSel = sSel.replace(/:nth-of-type\(\d+\)/, n>1 ? ':nth-child(' + n + ')' : ':first-child');
if (uniqueQuery()) return true;
return false;
},
// Test query to see if it returns one element
uniqueQuery = function() {
return document.querySelectorAll(aSel.join('>')||null).length===1;
};
// Walk up the DOM tree to compile a unique selector
while (elSrc.parentNode) {
if (getSelector(elSrc)) return aSel.join(' > ');
elSrc = elSrc.parentNode;
}
}