Temukan semua aturan CSS yang berlaku untuk sebuah elemen

87

Banyak alat / API menyediakan cara memilih elemen dari kelas atau ID tertentu. Ada juga kemungkinan untuk memeriksa stylesheet mentah yang dimuat oleh browser.

Namun, agar browser merender elemen, mereka akan mengkompilasi semua aturan CSS (mungkin dari file stylesheet yang berbeda) dan menerapkannya ke elemen tersebut. Inilah yang Anda lihat dengan Firebug atau WebKit Inspector - pohon pewarisan CSS lengkap untuk sebuah elemen.

Bagaimana saya dapat mereproduksi fitur ini dalam JavaScript murni tanpa memerlukan plugin browser tambahan?

Mungkin sebuah contoh dapat memberikan klarifikasi tentang apa yang saya cari:

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

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

Di sini elemen p # description memiliki dua aturan CSS yang diterapkan: warna merah dan ukuran font 20 px.

Saya ingin menemukan sumber dari mana aturan CSS yang dihitung ini berasal (warna berasal dari aturan p dan seterusnya).

cgbystrom
sumber
Lihat di browser dan pengguna alat pengembang browser (mis. Tab Elemen di Chrome)?
Ronnie Royston

Jawaban:

77

Karena pertanyaan ini saat ini tidak memiliki jawaban ringan (non-perpustakaan) yang kompatibel dengan lintas browser, saya akan mencoba memberikannya:

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/

Memanggil css(document.getElementById('elementId'))akan mengembalikan larik dengan elemen untuk setiap aturan CSS yang cocok dengan elemen yang diteruskan. Jika Anda ingin mengetahui informasi yang lebih spesifik tentang setiap aturan, lihat dokumentasi objek CSSRule .

SB
sumber
1
a.matchesdidefinisikan di baris ini: a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector. Artinya, jika sudah ada metode "kecocokan" (standar) untuk Node DOM, ia akan menggunakan yang itu, jika tidak mencoba menggunakan yang spesifik Webkit (webkitMatchesSelector), lalu Mozilla, Microsoft dan Opera. Anda dapat membacanya lebih lanjut di sini: developer.mozilla.org/en/docs/Web/API/Element/matches
SB
3
Sayangnya, menurut saya alternatif ini tidak mendeteksi semua aturan CSS yang diturunkan dari elemen induk pada anak. Fiddle: jsfiddle.net/t554xo2L Dalam hal ini, aturan UL (yang berlaku untuk elemen) tidak dicocokkan dengan if (a.matches(rules[r].selectorText))kondisi penjagaan.
funforum
2
Saya tidak pernah mengklaim bahwa itu mencantumkan / mewarisi / aturan CSS - yang dilakukannya hanyalah mencantumkan aturan CSS yang cocok dengan elemen yang diteruskan. Jika Anda ingin mendapatkan aturan yang diwarisi untuk elemen itu juga, Anda mungkin perlu melintasi DOM ke atas dan memanggil css()setiap elemen induk.
SB
2
Saya tahu :-) Saya hanya ingin menunjukkan hal ini karena orang yang dapat melihat pertanyaan ini mungkin berasumsi bahwa pertanyaan ini mendapat 'semua aturan css yang berlaku untuk suatu elemen', seperti yang disebutkan pada judul pertanyaan, padahal sebenarnya tidak demikian. .
funforum
3
Jika Anda ingin semua aturan saat ini diterapkan ke elemen termasuk yang diwariskan, Anda harus menggunakan getComputedStyle. Oleh karena itu, menurut saya jawaban ini benar dan benar untuk tidak menyertakan gaya yang diwarisi dari orang tua (warna teks diberikan kepada orang tua, misalnya). Apa yang tidak termasuk, adalah aturan yang diterapkan secara kondisional dengan kueri media.
gemetar
24

EDIT: Jawaban ini sekarang tidak digunakan lagi dan tidak lagi berfungsi di Chrome 64+ . Meninggalkan konteks sejarah. Faktanya, laporan bug menautkan kembali ke pertanyaan ini untuk solusi alternatif untuk menggunakan ini.


Sepertinya saya berhasil menjawab pertanyaan saya sendiri setelah satu jam penelitian.

Sesederhana ini:

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

(Bekerja di WebKit / Chrome, mungkin orang lain juga)

cgbystrom
sumber
4
Nah ini tidak banyak gunanya jika hanya didukung oleh chrome. Ini akan bekerja untuk kurang dari 5% dari semua pengunjung (tergantung pada demografi).
Tomasi
5
@diamandiev: Sejak Juni 2012, pangsa penggunaan Chrome telah meningkat hingga lebih dari 32% (dan sedikit lebih tinggi daripada penggunaan IE!). gs.statcounter.com
Roy Tinker
6
getMatchedCSSRules TIDAK menunjukkan gaya akhir yang diterapkan ke elemen. Ini mengembalikan sebuah array dari semua objek CSSStyleRule yang diterapkan dalam urutan kemunculannya. Jika Anda melakukan desain web responsif melalui kueri media CSS atau memuat lebih dari satu lembar gaya (seperti satu untuk IE), Anda masih perlu mengulang setiap gaya yang dikembalikan dan menghitung kekhususan css untuk setiap aturan. Kemudian hitung aturan terakhir yang berlaku. Anda perlu mereproduksi apa yang dilakukan browser secara alami. Untuk membuktikan hal ini dalam contoh Anda, tambahkan "p {color: blue! Important}" di awal pernyataan gaya Anda.
mrbinky3000
24
Ini sekarang sudah tidak digunakan lagi di Chrome 41. Lihat code.google.com/p/chromium/issues/detail?id=437569#c2 .
Daniel Darabos
5
Ini akhirnya telah dihapus di Chrome 63 (entri blog resmi - yang mengarah kembali ke pertanyaan ini)
brichins
20

Versi pendek 12 April 2017

Challenger muncul.

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

Garis /* 1 */membangun larik datar dari semua aturan.
Garis/* 2 */ membuang aturan yang tidak cocok.

Berdasarkan fungsinyacss(el) @SB di halaman yang sama.

Contoh 1

var div = iframedoc.querySelector("#myelement");
var rules = getMatchedCSSRules(div, iframedoc.styleSheets);
console.log(rules[0].parentStyleSheet.ownerNode, rules[0].cssText);

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

Kekurangan

  • Tidak ada penanganan media, tidak @import,@media .
  • Tidak ada akses ke gaya yang dimuat dari stylesheet lintas domain.
  • Tidak ada pengurutan berdasarkan “kekhususan” pemilih (urutan kepentingan).
  • Tidak ada gaya yang diwarisi dari orang tua.
  • Mungkin tidak berfungsi dengan browser lama atau yang belum sempurna.
  • Tidak yakin bagaimana cara mengatasi pseudo-class dan pseudo-selector tetapi tampaknya berjalan dengan baik.

Mungkin saya akan membahas kekurangan ini suatu hari nanti.

Versi panjang 12 Agustus 2018

Berikut implementasi yang jauh lebih komprehensif yang diambil dari halaman GitHub seseorang (bercabang dari kode asli ini , melalui Bugzilla ). Ditulis untuk Gecko dan IE, tetapi dikabarkan bekerja juga dengan Blink.

4 Mei 2017: Kalkulator kekhususan memiliki bug kritis yang sekarang telah saya perbaiki. (Saya tidak dapat memberi tahu penulis karena saya tidak memiliki akun GitHub.)

12 Agustus 2018: Pembaruan Chrome terbaru tampaknya telah memisahkan cakupan objek ( this) dari metode yang ditetapkan ke variabel independen. Oleh karena itu, permintaan matcher(selector)berhenti bekerja. Menggantinya dengan matcher.call(el, selector)telah menyelesaikannya.

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

Memperbaiki bug

  • = match+= match
  • return re ? re.length : 0;return matches ? matches.length : 0;
  • _matchesSelector(element, selector)matchesSelector(element, selector)
  • matcher(selector)matcher.call(el, selector)
7vujy0f0hy
sumber
Di getSheetRules saya harus menambahkan if (stylesheet.cssRules === null) {return []} agar berfungsi untuk saya.
Gwater17
Menguji "versi panjang". Bekerja untuk saya. Terlalu buruk getMatchedCSSRules () tidak pernah distandarisasi oleh browser.
colin moock
Bagaimana ini menangani dua penyeleksi dengan kekhususan yang sama seperti, h1 dan h1, div - di mana yang terakhir dideklarasikan harus digunakan?
Stellan
Mungkin kita bisa mendapatkan ide untuk menangani pseudo di sini? github.com/dvtng/jss/blob/master/jss.js
mr1031011
19

Lihatlah perpustakaan ini, yang melakukan apa yang diminta: http://www.brothercake.com/site/resources/scripts/cssutilities/

Ia bekerja di semua browser modern langsung kembali ke IE6, dapat memberi Anda aturan dan koleksi properti seperti Firebug (sebenarnya lebih akurat daripada Firebug), dan juga dapat menghitung kekhususan relatif atau absolut dari aturan apa pun. Satu-satunya peringatan adalah bahwa, meskipun memahami jenis media statis, ia tidak memahami kueri media.

kue saudara
sumber
Modul ini sangat bagus, semoga mendapat lebih banyak cinta dari penulis.
mr1031011
4

Berikut adalah versi jawaban SB yang juga mengembalikan aturan yang cocok dalam kueri media yang cocok. Saya telah menghapus *.rules || *.cssRulespenggabungan dan.matches pencari implementasi; tambahkan polyfill atau tambahkan kembali baris tersebut jika Anda membutuhkannya.

Versi ini juga mengembalikan CSSStyleRuleobjek daripada teks aturan. Saya rasa ini sedikit lebih berguna, karena spesifikasi aturan dapat lebih mudah diperiksa secara terprogram dengan cara ini.

Kopi:

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;
}
gemetar
sumber
Bagaimana ini bisa diubah untuk digunakan pada anak-anak yang lulus elementjuga?
Kragalon
2
Apa kasus penggunaan Anda? Saya tidak benar-benar melihat di mana itu akan berguna, karena aturan yang berlaku untuk anak-anak tidak selalu berlaku untuk orang tua. Anda baru saja berakhir dengan setumpuk aturan tanpa kesamaan. Jika Anda benar-benar ingin agar Anda bisa mengulang kembali anak-anak dan menjalankan metode ini untuk masing-masing, dan membangun sebuah array dari semua hasil.
gemetar
Saya hanya mencoba membuat cloneNode(true)fungsionalitas tetapi dengan gaya kloning yang dalam juga.
Kragalon
1
kondisi ini: if (window.matchMedia (rule.conditionText) .matches) {...} mencegah kecocokan dalam kasus saya karena "rule.conditionText" tidak ditentukan. Tanpa itu, itu berhasil. Anda dapat mencoba dan mengujinya di news.ycombinator.com . "span.pagetop b" memiliki aturan kueri media yang tidak cocok dengan fungsi Anda sebagaimana adanya.
ayal gelles
1
Chrome tidak mendukung properti conditionText pada instance CSSMediaRule.
Macil
3

Ini adalah versi saya dari getMatchedCSSRulesfungsi yang mendukung @mediakueri.

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
}
pengguna3896501
sumber
1

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>

Thomas
sumber
3
Duplikat tak berguna dari versi lama jawaban saya. Hanya mencemari halaman. Versi lengkap dan terbaru: di sini .
7vujy0f0hy
1

Memastikan IE9 +, saya menulis fungsi yang menghitung CSS untuk elemen yang diminta dan turunannya, dan memberikan kemungkinan untuk menyimpannya ke className baru jika diperlukan dalam cuplikan di bawah.

/**
  * @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;
}

Pemakaian

getElementStyles(document.getElementByClassName('.my-class'), '.dummy-class', 'width:100%;opaity:0.5;transform:scale(1.5);');
Shobhit Sharma
sumber
2
1. Anda dapat mengganti seluruh computeStylessubrutin hanya dengan el => getComputedStyle(el).cssText. Bukti: biola . 2. '.' + element.className merupakan konstruksi yang salah karena mengasumsikan keberadaan satu nama kelas. Konstruksi yang valid adalah element.className.replace(/^| /g, '.'). 3. Fungsi Anda mengabaikan kemungkinan pemilih CSS lain selain kelas. 4. Rekursi Anda secara sewenang-wenang terbatas pada satu tingkat (anak-anak tetapi bukan cucu). 5. Penggunaan: tidak ada getElementByClassName, hanya getElementsByClassName(mengembalikan array).
7vujy0f0hy
1

Saya pikir jawaban dari SB seharusnya yang diterima pada saat ini tetapi tidak tepat. Disebutkan beberapa kali bahwa akan ada beberapa aturan yang mungkin terlewat. Menghadapi hal itu, saya memutuskan untuk menggunakan document.querySelectorAll daripada element.matches. Satu-satunya hal adalah Anda memerlukan semacam identifikasi unik dari elemen untuk membandingkannya dengan yang Anda cari. Dalam kebanyakan kasus saya pikir itu dapat dicapai dengan mengatur id-nya agar memiliki nilai yang unik. Begitulah cara Anda mengidentifikasi elemen yang cocok menjadi milik Anda. Jika Anda bisa memikirkan cara umum untuk mencocokkan hasil document.querySelectorAll dengan elemen yang Anda cari, itu pada dasarnya adalah polyfill lengkap getMatchedCSSRules.

Saya memeriksa kinerja untuk document.querySelectorAll karena mungkin lebih lambat dari element.matches tetapi dalam banyak kasus seharusnya tidak menjadi masalah. Saya melihat bahwa dibutuhkan sekitar 0,001 milidetik.

Saya juga menemukan pustaka CSSUtilities yang mengiklankan bahwa ia dapat melakukan ini tetapi saya merasa ini sudah tua dan belum diperbarui dalam beberapa saat. Melihat kode sumbernya, itu membuat saya berpikir mungkin ada kasus yang terlewat.

cagdas_ucar
sumber
CSSUtilities benar-benar tua, tetapi ia mengembalikan aturan untuk status semu juga (misalnya ia dapat mengembalikan aturan hover). Saya belum menemukan jawaban apa pun di sini yang membahas keadaan semu.
mr1031011