如何从元素中列出所有 css 变量 names/values 对

How to list all css variables names/values pairs from element

我有 JS 库,但遇到这个问题:我正在创建临时元素以使用等宽字体计算字符大小。现在我正在复制 inlie 样式,但我需要原始样式的所有样式,包括 css 变量。我不想克隆该元素,因为里面有一些我不需要的元素。元素也可能有用户设置的 id,不确定当有两个具有相同 id 的元素时它会如何表现,所以(我认为)最好将每个样式复制到新的临时元素。

我有基于这些的代码:

我的代码是这样的:

function is_valid_style_property(key, value) {
    //checking that the property is not int index ( happens on some browser
    return typeof value === 'string' && value.length && value !== parseInt(value);
}

function copy_computed_style(from, to) {
    var computed_style_object = false;
    computed_style_object = from.currentStyle || document.defaultView.getComputedStyle(from, null);

    if (!computed_style_object) {
        return;
    }
    Object.keys(computed_style_object).forEach(function(key) {
        var value = computed_style_object.getPropertyValue(key);
        if (key.match(/^--/)) {
            console.log({key, value}); // this is never executed
        }
        if (is_valid_style_property(key, value)) {
            to.style.setProperty(key, value);
        }
    });
}

问题是 getComputedStyle,不要 return css 变量。是否有任何其他解决方案来获取应用于元素的 css 变量列表?

我需要 CSS 变量,因为我有 css 应用于我的临时项目内的元素,它基于 css 变量。克隆节点是将 CSS 变量从一个元素复制到另一个元素的唯一方法吗?

编辑:

这不是重复的,因为 css 变量不仅可以按照 class 的样式 sheet 设置为内联。我的元素可以通过非常不同的 css 选择器添加样式,我可能不知道。

基于这个答案 我创建了一个依赖 getMatchedCSSRules 的代码以检索所有 CSS 然后提取 CSS 自定义属性。由于自定义属性是继承的,我们需要收集在元素内定义的属性和在任何父元素上定义的属性。

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

var element = document.querySelector(".box");

/*Get element style*/
var obj = window.getMatchedCSSRules(element)[0];
var all_css = obj.parentStyleSheet.cssRules;
for(var i=0;i < all_css.length;i++) {
 var rules = all_css[i].cssText.substring(all_css[i].cssText.indexOf("{")+1,all_css[i].cssText.indexOf("}"));
 rules = rules.split(";");
 for(var j=0;j<rules.length;j++) {
  if(rules[j].trim().startsWith("--")) {
   console.log(rules[j]);
  }
 }
}
/*get inline style*/
var rules = element.getAttribute("style").trim().split(";");
for(var j=0;j<rules.length;j++) {
 if(rules[j].trim().startsWith("--")) {
  console.log(rules[j]);
 }
}
:root {
  --b: 20px;
}

.box {
  background: red;
  height: 100px;
  --c: blue;
  border: 1px solid var(--c);
}
.element {
  --e:30px;
  padding:var(--e);
}
<div class="box element" style="color:blue;--d:10ch;border-radius:20px;">
</div>

这里是代码的相关部分1:

var element = document.querySelector(".box");

/*Get external styles*/
var obj = window.getMatchedCSSRules(element)[0];
var all_css = obj.parentStyleSheet.cssRules;
for(var i=0;i < all_css.length;i++) {
    var rules = all_css[i].cssText.substring(all_css[i].cssText.indexOf("{")+1,all_css[i].cssText.indexOf("}"));
    rules = rules.split(";");
    for(var j=0;j<rules.length;j++) {
        if(rules[j].trim().startsWith("--")) {
            console.log(rules[j]);
        }
    }
}
/*Get inline styles*/
var rules = element.getAttribute("style").trim().split(";");
for(var j=0;j<rules.length;j++) {
    if(rules[j].trim().startsWith("--")) {
        console.log(rules[j]);
    }
}

如您所见,这将打印所需的值。您可以轻松调整代码以将值存储在数组或对象中。

1:此代码未优化,因为在某些情况下它可能会收集不需要的 CSS。会继续编辑的。