Многие инструменты / API предоставляют способы выбора элементов определенных классов или идентификаторов. Также можно проверить необработанные таблицы стилей, загруженные браузером.
Однако для того, чтобы браузеры отображали элемент, они компилируют все правила CSS (возможно, из разных файлов таблиц стилей) и применяют их к элементу. Это то, что вы видите в Firebug или WebKit Inspector - полное дерево наследования CSS для элемента.
Как я могу воспроизвести эту функцию на чистом JavaScript, не требуя дополнительных плагинов для браузера?
Возможно, пример может прояснить то, что я ищу:
<style type="text/css">
p { color :red; }
#description { font-size: 20px; }
</style>
<p id="description">Lorem ipsum</p>
Здесь к элементу p # description применяются два правила CSS: красный цвет и размер шрифта 20 пикселей.
Я хотел бы найти источник, из которого происходят эти вычисленные правила CSS (цвет соответствует правилу p и так далее).
источник
Ответы:
Поскольку на этот вопрос в настоящее время нет легкого (небиблиотечного) кросс-браузерного ответа, я постараюсь дать его:
function css(el) { var sheets = document.styleSheets, ret = []; el.matches = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector || el.oMatchesSelector; for (var i in sheets) { var rules = sheets[i].rules || sheets[i].cssRules; for (var r in rules) { if (el.matches(rules[r].selectorText)) { ret.push(rules[r].cssText); } } } return ret; }
JSFiddle: http://jsfiddle.net/HP326/6/
Вызов
css(document.getElementById('elementId'))
вернет массив с элементом для каждого правила CSS, которое соответствует переданному элементу. Если вы хотите получить более конкретную информацию о каждом правиле, ознакомьтесь с документацией по объекту CSSRule .источник
a.matches
определяется в этой строке:a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector
. Это означает, что если уже существует (стандартный) метод «сопоставления» для узлов DOM, он будет использовать его, в противном случае он попытается использовать конкретный для Webkit (webkitMatchesSelector), а затем методы Mozilla, Microsoft и Opera. Подробнее об этом можно прочитать здесь: developer.mozilla.org/en/docs/Web/API/Element/matchesif (a.matches(rules[r].selectorText))
условию защиты.css()
каждый из родительских элементов.РЕДАКТИРОВАТЬ: этот ответ устарел и больше не работает в Chrome 64+ . Уходя в исторический контекст. Фактически, этот отчет об ошибке ссылается на этот вопрос для поиска альтернативных решений для его использования.
Кажется, мне удалось ответить на свой вопрос после еще одного часа исследования.
Это так просто:
window.getMatchedCSSRules(document.getElementById("description"))
(Работает в WebKit / Chrome, возможно, и в других)
источник
Взгляните на эту библиотеку, которая выполняет то, о чем просили: http://www.brothercake.com/site/resources/scripts/cssutilities/
Он работает во всех современных браузерах вплоть до IE6, может предоставлять вам коллекции правил и свойств, такие как Firebug (на самом деле он более точен, чем Firebug), а также может вычислять относительную или абсолютную специфичность любого правила. Единственное предостережение в том, что, хотя он понимает статические типы мультимедиа, он не понимает медиа-запросы.
источник
Краткая версия 12 апреля 2017 г.
Появляется Челленджер.
var getMatchedCSSRules = (el, css = el.ownerDocument.styleSheets) => [].concat(...[...css].map(s => [...s.cssRules||[]])) /* 1 */ .filter(r => el.matches(r.selectorText)); /* 2 */
Line
/* 1 */
строит плоский массив всех правил.Линия
/* 2 */
отбрасывает несовпадающие правила.На основе функции
css(el)
@SB на той же странице.Пример 1
var div = iframedoc.querySelector("#myelement"); var rules = getMatchedCSSRules(div, iframedoc.styleSheets); console.log(rules[0].parentStyleSheet.ownerNode, rules[0].cssText);
Пример 2
var getMatchedCSSRules = (el, css = el.ownerDocument.styleSheets) => [].concat(...[...css].map(s => [...s.cssRules||[]])) .filter(r => el.matches(r.selectorText)); function Go(big,show) { var r = getMatchedCSSRules(big); PrintInfo: var f = (dd,rr,ee="\n") => dd + rr.cssText.slice(0,50) + ee; show.value += "--------------- Rules: ----------------\n"; show.value += f("Rule 1: ", r[0]); show.value += f("Rule 2: ", r[1]); show.value += f("Inline: ", big.style); show.value += f("Computed: ", getComputedStyle(big), "(…)\n"); show.value += "-------- Style element (HTML): --------\n"; show.value += r[0].parentStyleSheet.ownerNode.outerHTML; } Go(...document.querySelectorAll("#big,#show"));
.red {color: red;} #big {font-size: 20px;}
<h3 id="big" class="red" style="margin: 0">Lorem ipsum</h3> <textarea id="show" cols="70" rows="10"></textarea>
Недостатки
@import
,@media
.Возможно, однажды я устраню эти недостатки.
Полная версия 12 августа 2018 г.
Вот гораздо более полная реализация, взятая с чьей-то страницы GitHub (разветвленная из этого исходного кода через Bugzilla ). Написано для Gecko и IE, но, по слухам, работает и с Blink.
4 мая 2017 г .: В калькуляторе специфичности были обнаружены критические ошибки, которые я исправил. (Я не могу уведомить авторов, потому что у меня нет учетной записи GitHub.)
12 августа 2018: в последних обновлениях Chrome, похоже, отделена область видимости объекта (
this
) от методов, назначенных независимым переменным. Поэтому вызовmatcher(selector)
перестал работать. Замена его наmatcher.call(el, selector)
решила это.// polyfill window.getMatchedCSSRules() in FireFox 6+ if (typeof window.getMatchedCSSRules !== 'function') { var ELEMENT_RE = /[\w-]+/g, ID_RE = /#[\w-]+/g, CLASS_RE = /\.[\w-]+/g, ATTR_RE = /\[[^\]]+\]/g, // :not() pseudo-class does not add to specificity, but its content does as if it was outside it PSEUDO_CLASSES_RE = /\:(?!not)[\w-]+(\(.*\))?/g, PSEUDO_ELEMENTS_RE = /\:\:?(after|before|first-letter|first-line|selection)/g; // convert an array-like object to array function toArray(list) { return [].slice.call(list); } // handles extraction of `cssRules` as an `Array` from a stylesheet or something that behaves the same function getSheetRules(stylesheet) { var sheet_media = stylesheet.media && stylesheet.media.mediaText; // if this sheet is disabled skip it if ( stylesheet.disabled ) return []; // if this sheet's media is specified and doesn't match the viewport then skip it if ( sheet_media && sheet_media.length && ! window.matchMedia(sheet_media).matches ) return []; // get the style rules of this sheet return toArray(stylesheet.cssRules); } function _find(string, re) { var matches = string.match(re); return matches ? matches.length : 0; } // calculates the specificity of a given `selector` function calculateScore(selector) { var score = [0,0,0], parts = selector.split(' '), part, match; //TODO: clean the ':not' part since the last ELEMENT_RE will pick it up while (part = parts.shift(), typeof part == 'string') { // find all pseudo-elements match = _find(part, PSEUDO_ELEMENTS_RE); score[2] += match; // and remove them match && (part = part.replace(PSEUDO_ELEMENTS_RE, '')); // find all pseudo-classes match = _find(part, PSEUDO_CLASSES_RE); score[1] += match; // and remove them match && (part = part.replace(PSEUDO_CLASSES_RE, '')); // find all attributes match = _find(part, ATTR_RE); score[1] += match; // and remove them match && (part = part.replace(ATTR_RE, '')); // find all IDs match = _find(part, ID_RE); score[0] += match; // and remove them match && (part = part.replace(ID_RE, '')); // find all classes match = _find(part, CLASS_RE); score[1] += match; // and remove them match && (part = part.replace(CLASS_RE, '')); // find all elements score[2] += _find(part, ELEMENT_RE); } return parseInt(score.join(''), 10); } // returns the heights possible specificity score an element can get from a give rule's selectorText function getSpecificityScore(element, selector_text) { var selectors = selector_text.split(','), selector, score, result = 0; while (selector = selectors.shift()) { if (matchesSelector(element, selector)) { score = calculateScore(selector); result = score > result ? score : result; } } return result; } function sortBySpecificity(element, rules) { // comparing function that sorts CSSStyleRules according to specificity of their `selectorText` function compareSpecificity (a, b) { return getSpecificityScore(element, b.selectorText) - getSpecificityScore(element, a.selectorText); } return rules.sort(compareSpecificity); } // Find correct matchesSelector impl function matchesSelector(el, selector) { var matcher = el.matchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector || el.msMatchesSelector; return matcher.call(el, selector); } //TODO: not supporting 2nd argument for selecting pseudo elements //TODO: not supporting 3rd argument for checking author style sheets only window.getMatchedCSSRules = function (element /*, pseudo, author_only*/) { var style_sheets, sheet, sheet_media, rules, rule, result = []; // get stylesheets and convert to a regular Array style_sheets = toArray(window.document.styleSheets); // assuming the browser hands us stylesheets in order of appearance // we iterate them from the beginning to follow proper cascade order while (sheet = style_sheets.shift()) { // get the style rules of this sheet rules = getSheetRules(sheet); // loop the rules in order of appearance while (rule = rules.shift()) { // if this is an @import rule if (rule.styleSheet) { // insert the imported stylesheet's rules at the beginning of this stylesheet's rules rules = getSheetRules(rule.styleSheet).concat(rules); // and skip this rule continue; } // if there's no stylesheet attribute BUT there IS a media attribute it's a media rule else if (rule.media) { // insert the contained rules of this media rule to the beginning of this stylesheet's rules rules = getSheetRules(rule).concat(rules); // and skip it continue } // check if this element matches this rule's selector if (matchesSelector(element, rule.selectorText)) { // push the rule to the results set result.push(rule); } } } // sort according to specificity return sortBySpecificity(element, result); }; }
Исправлены ошибки
= match
→+= match
return re ? re.length : 0;
→return matches ? matches.length : 0;
_matchesSelector(element, selector)
→matchesSelector(element, selector)
matcher(selector)
→matcher.call(el, selector)
источник
Вот версия ответа SB, который также возвращает правила сопоставления в соответствующих медиа-запросах. Я удалил
*.rules || *.cssRules
объединение и.matches
средство поиска реализации; добавьте полифил или добавьте эти строки обратно, если они вам нужны.Эта версия также возвращает
CSSStyleRule
объекты, а не текст правила. Я думаю, что это немного более полезно, так как таким образом проще программно исследовать особенности правил.Кофе:
getMatchedCSSRules = (element) -> sheets = document.styleSheets matching = [] loopRules = (rules) -> for rule in rules if rule instanceof CSSMediaRule if window.matchMedia(rule.conditionText).matches loopRules rule.cssRules else if rule instanceof CSSStyleRule if element.matches rule.selectorText matching.push rule return loopRules sheet.cssRules for sheet in sheets return matching
JS:
function getMatchedCSSRules(element) { var i, len, matching = [], sheets = document.styleSheets; function loopRules(rules) { var i, len, rule; for (i = 0, len = rules.length; i < len; i++) { rule = rules[i]; if (rule instanceof CSSMediaRule) { if (window.matchMedia(rule.conditionText).matches) { loopRules(rule.cssRules); } } else if (rule instanceof CSSStyleRule) { if (element.matches(rule.selectorText)) { matching.push(rule); } } } }; for (i = 0, len = sheets.length; i < len; i++) { loopRules(sheets[i].cssRules); } return matching; }
источник
element
?cloneNode(true)
функциональность, но с глубоким клонированием стиля.Вот моя версия
getMatchedCSSRules
функции, которая поддерживает@media
запрос.const getMatchedCSSRules = (el) => { let rules = [...document.styleSheets] rules = rules.filter(({ href }) => !href) rules = rules.map((sheet) => [...(sheet.cssRules || sheet.rules || [])].map((rule) => { if (rule instanceof CSSStyleRule) { return [rule] } else if (rule instanceof CSSMediaRule && window.matchMedia(rule.conditionText)) { return [...rule.cssRules] } return [] })) rules = rules.reduce((acc, rules) => acc.concat(...rules), []) rules = rules.filter((rule) => el.matches(rule.selectorText)) rules = rules.map(({ style }) => style) return rules }
источник
var GetMatchedCSSRules = (elem, css = document.styleSheets) => Array.from(css) .map(s => Array.from(s.cssRules).filter(r => elem.matches(r.selectorText))) .reduce((a,b) => a.concat(b)); function Go(paragraph, print) { var rules = GetMatchedCSSRules(paragraph); PrintInfo: print.value += "Rule 1: " + rules[0].cssText + "\n"; print.value += "Rule 2: " + rules[1].cssText + "\n\n"; print.value += rules[0].parentStyleSheet.ownerNode.outerHTML; } Go(document.getElementById("description"), document.getElementById("print"));
p {color: red;} #description {font-size: 20px;}
<p id="description">Lorem ipsum</p> <textarea id="print" cols="50" rows="12"></textarea>
источник
Обеспечивая IE9 +, я написал функцию, которая вычисляет CSS для запрошенного элемента и его дочерних элементов и дает возможность сохранить его в новом className, если это необходимо, во фрагменте ниже.
/** * @function getElementStyles * * Computes all CSS for requested HTMLElement and its child nodes and applies to dummy class * * @param {HTMLElement} element * @param {string} className (optional) * @param {string} extras (optional) * @return {string} CSS Styles */ function getElementStyles(element, className, addOnCSS) { if (element.nodeType !== 1) { return; } var styles = ''; var children = element.getElementsByTagName('*'); className = className || '.' + element.className.replace(/^| /g, '.'); addOnCSS = addOnCSS || ''; styles += className + '{' + (window.getComputedStyle(element, null).cssText + addOnCSS) + '}'; for (var j = 0; j < children.length; j++) { if (children[j].className) { var childClassName = '.' + children[j].className.replace(/^| /g, '.'); styles += ' ' + className + '>' + childClassName + '{' + window.getComputedStyle(children[j], null).cssText + '}'; } } return styles; }
Применение
getElementStyles(document.getElementByClassName('.my-class'), '.dummy-class', 'width:100%;opaity:0.5;transform:scale(1.5);');
источник
computeStyles
подпрограмму простоel => getComputedStyle(el).cssText
. Доказательство: скрипка . 2.'.' + element.className
является ошибочной конструкцией, поскольку предполагает наличие одного имени класса. Действительная конструкция естьelement.className.replace(/^| /g, '.')
. 3. Ваша функция игнорирует возможность использования других селекторов CSS, кроме классов. 4. Ваша рекурсия произвольно ограничена одним уровнем (дети, но не внуки). 5. Использование: нетgetElementByClassName
, толькоgetElementsByClassName
(возвращает массив).Я думаю, что ответ от SB должен быть принят на данный момент, но он не точен. Несколько раз упоминается, что некоторые правила могут быть пропущены. Столкнувшись с этим, я решил использовать document.querySelectorAll вместо element.matches. Единственное, что вам понадобится какая-то уникальная идентификация элементов, чтобы сравнить их с тем, что вы ищете. В большинстве случаев я думаю, что это достижимо, если присвоить его идентификатору уникальное значение. Вот как вы можете определить, что совпавший элемент принадлежит вам. Если вы можете придумать общий способ сопоставления результата document.querySelectorAll с элементом, который вы ищете, это, по сути, будет полным полифилом getMatchedCSSRules.
Я проверил производительность для document.querySelectorAll, поскольку он, вероятно, медленнее, чем element.matches, но в большинстве случаев это не должно быть проблемой. Я вижу, что это занимает около 0,001 миллисекунды.
Я также нашел библиотеку CSSUtilities, которая рекламирует, что она может это делать, но я чувствую, что она старая и давно не обновлялась. Глядя на его исходный код, я думаю, что могут быть случаи, когда он упускается.
источник