Найти все правила CSS, которые применяются к элементу - PullRequest
74 голосов
/ 01 июня 2010

Многие инструменты / API предоставляют способы выбора элементов определенных классов или идентификаторов. Также есть возможность просматривать необработанные таблицы стилей, загруженные браузером.

Однако, чтобы браузеры отображали элемент, они скомпилируют все правила CSS (возможно, из разных файлов стилей) и применят его к элементу. Это то, что вы видите с Firebug или Инспектором WebKit - полное дерево наследования CSS для элемента.

Как воспроизвести эту функцию на чистом JavaScript, не требуя дополнительных плагинов для браузера?

Возможно, пример может дать некоторые пояснения по поводу того, что я ищу:

<style type="text/css">
    p { color :red; }
    #description { font-size: 20px; }
</style>

<p id="description">Lorem ipsum</p>

Здесь к элементу описания p # применяются два правила CSS: красный цвет и размер шрифта 20 пикселей.

Я хотел бы найти источник, из которого происходят эти вычисленные правила CSS (цвет - это правило p и т. Д.).

Ответы [ 8 ]

65 голосов
/ 25 марта 2014

Поскольку этот вопрос в настоящее время не имеет упрощенного (не для библиотеки) кросс-браузерно-совместимого ответа, я постараюсь дать один:

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 .

22 голосов
/ 02 июня 2010

EDIT: этот ответ устарел и больше не работает в Chrome 64 + . Оставляя в историческом контексте. На самом деле этот отчет об ошибке ссылается на этот вопрос для альтернативных решений использования этого.


Кажется, мне удалось ответить на свой вопрос после еще одного часа исследований.

Это так просто:

window.getMatchedCSSRules(document.getElementById("description"))

(работает в WebKit / Chrome, возможно, и в других)

16 голосов
/ 19 августа 2012

Посмотрите на эту библиотеку, которая выполняет то, что было запрошено: http://www.brothercake.com/site/resources/scripts/cssutilities/

Он работает во всех современных браузерах вплоть до IE6, может предоставлять вам правила и коллекции свойств, такие как Firebug (на самом деле это более точно, чем Firebug), а также может вычислять относительную или абсолютную специфичность любого правила. Единственное предостережение в том, что, хотя он понимает статические типы медиа, он не понимает медиа-запросы.

15 голосов
/ 22 июня 2016

Короткая версия 12 апреля 2017

Появляется Челленджер.

var getMatchedCSSRules = (el, css = el.ownerDocument.styleSheets) => 
    [].concat(...[...css].map(s => [...s.cssRules||[]])) /* 1 */
    .filter(r => el.matches(r.selectorText));            /* 2 */

Строка /* 1 */ создает плоский массив всех правил.
Строка /* 2 */ отбрасывает несоответствующие правила.

На основе функции css(el) от @ S.B. на той же странице.

Пример 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>

* 1030 Упущения * Нет обработки носителя, нет @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)
3 голосов
/ 21 марта 2016

Вот вариант ответа С.Б., который также возвращает правила соответствия в соответствующих медиа-запросах. Я удалил объединение *.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;
}
1 голос
/ 08 сентября 2018

Вот моя версия функции 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
}
1 голос
/ 29 сентября 2016

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>
0 голосов
/ 16 марта 2017

Гарантируя 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);');
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...