最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to list all css variables namesvalues pairs from element - Stack Overflow

programmeradmin2浏览0评论

I have JS library and I have this issue: I'm creating temporary element for calculating size of character using monospace font. Right now I'm copying inlie style, but I need all styles from original including css variables. I don't want to clone the element, because there are elements, that are inside, that I don't need. Also element may have id set by the user, not sure how this will behave when there will be two elements with same id, so it would be better (I think) to just copy each style to new temporary element.

I have code based on these:

  • Accessing a CSS custom property (aka CSS variable) through JavaScript
  • Set javascript puted style from one element to another

My code look like this:

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_puted_style(from, to) {
    var puted_style_object = false;
    puted_style_object = from.currentStyle || document.defaultView.getComputedStyle(from, null);

    if (!puted_style_object) {
        return;
    }
    Object.keys(puted_style_object).forEach(function(key) {
        var value = puted_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);
        }
    });
}

the problem is that getComputedStyle, don't return css variables. Is there any other solution to get list of css variables applied to element?

I need CSS variables because I have css that is applied to element that are inside of my temporary item, that is based on css variables. Is clone node the only way to copy CSS variables from one element to other?

EDIT:

this is not duplicate because css variable can also be set inline not only in style sheet per class. And my element can have style added by very different css selectors that I can't possibly know.

I have JS library and I have this issue: I'm creating temporary element for calculating size of character using monospace font. Right now I'm copying inlie style, but I need all styles from original including css variables. I don't want to clone the element, because there are elements, that are inside, that I don't need. Also element may have id set by the user, not sure how this will behave when there will be two elements with same id, so it would be better (I think) to just copy each style to new temporary element.

I have code based on these:

  • Accessing a CSS custom property (aka CSS variable) through JavaScript
  • Set javascript puted style from one element to another

My code look like this:

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_puted_style(from, to) {
    var puted_style_object = false;
    puted_style_object = from.currentStyle || document.defaultView.getComputedStyle(from, null);

    if (!puted_style_object) {
        return;
    }
    Object.keys(puted_style_object).forEach(function(key) {
        var value = puted_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);
        }
    });
}

the problem is that getComputedStyle, don't return css variables. Is there any other solution to get list of css variables applied to element?

I need CSS variables because I have css that is applied to element that are inside of my temporary item, that is based on css variables. Is clone node the only way to copy CSS variables from one element to other?

EDIT:

this is not duplicate because css variable can also be set inline not only in style sheet per class. And my element can have style added by very different css selectors that I can't possibly know.

Share Improve this question edited Jan 3, 2019 at 14:50 jcubic asked Jan 2, 2019 at 10:24 jcubicjcubic 66.7k58 gold badges249 silver badges453 bronze badges 9
  • Possible duplicate of Get all CSS properties for a class or id with Javascript/JQuery – user6656728 Commented Jan 2, 2019 at 10:26
  • @MumbaiWadala does this consider CSS Custom properties? – Temani Afif Commented Jan 2, 2019 at 10:27
  • 1 @connexo they are the same. They're called custom properties by the spec, but they're monly called css variables. – jcubic Commented Jan 2, 2019 at 10:46
  • 2 @MumbaiWadala this is not a duplicate because I don't want to find css for single selector but for single element that can be accessed in very different selectors, that I can't possibly know. I also need to include inline styles. – jcubic Commented Jan 2, 2019 at 10:53
  • 1 Related: stackoverflow./a/49419028/104380 – vsync Commented Dec 19, 2019 at 10:55
 |  Show 4 more ments

3 Answers 3

Reset to default 2

I found that this works in Firefox and Chrome (I've not tested in other browsers).

  const res: Record<string, string> = {};

  if ("putedStyleMap" in elem) {
    // Chrome
    const styles = elem.putedStyleMap();
    Array.from(styles).forEach(([prop, val]) => {
      if (prop.startsWith("--")) {
        res[prop] = val.toString();
      }
    });
  } else {
    // Firefox
    const styles = getComputedStyle(elem);
    for (let i = 0; i < styles.length; i++) {
      const propertyName = styles[i];
      if (propertyName.startsWith("--")) {
        const value = styles.getPropertyValue(propertyName);
        res[propertyName] = value;
      }
    }
  }

  console.log({ res });

Based on this answer https://stackoverflow./a/37958301/8620333 I have created a code that rely on getMatchedCSSRules in order to retrieve all the CSS and then extract the CSS custom properties. Since Custom properties are inherited we need to gather the one defined within the element and the one defined on any parent element.

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) {
            // paring function that sorts CSSStyleRules according to specificity of their `selectorText`
            function pareSpecificity (a, b) {
                return getSpecificityScore(element, b.selectorText) - getSpecificityScore(element, a.selectorText);
            }

            return rules.sort(pareSpecificity);
        }

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

Here is the relevant part of the code1:

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

As you can see, this will print the needed values. You can easily adjust the code to store the values in an array or an Object.

1: This code is not optimized as it may gather non needed CSS in some cases. Will keep editing it.

Building off of @Evanss answer, this works for me in both Chrome and Firefox

const list = {};

if ('putedStyleMap' in document.documentElement) {
  // Chrome
  const styles = document.documentElement.putedStyleMap();
  styles.forEach((val, key) => {
    if (key.startsWith('--')) list[key] = val.toString();
  });
} else {
  // Firefox
  const styles = getComputedStyle(document.documentElement);
  for (let i = 0; i < styles.length; i++) {
    const propertyName = styles[i];
    if (propertyName.startsWith('--')) {
      const value = styles.getPropertyValue(propertyName);
      list[propertyName] = value;
    }
  }
}

console.log(list)
:root{
  --foo: #F00;
  --bar: green;
}

TypeScript version:

const list: Record<string, string> = {};

if ('putedStyleMap' in document.documentElement) {
  // Chrome
  const styles = document.documentElement.putedStyleMap();
  styles.forEach((val, key) => {
    if (key.startsWith('--')) list[key] = val.toString();
  });
} else {
  // Firefox
  const styles = getComputedStyle(document.documentElement);
  for (let i = 0; i < styles.length; i++) {
    const propertyName = styles[i]!;
    if (propertyName.startsWith('--')) {
      const value = styles.getPropertyValue(propertyName);
      list[propertyName] = value;
    }
  }
}

console.log(list)
发布评论

评论列表(0)

  1. 暂无评论