javascript - 如何获得所需的元素样式?

标签 javascript html css

我有以下代码,我想获得元素的所需 CSS 属性(即使样式是由 js 设置的,即对于由 SPA 创建的文件,如 react),而不是计算/应用的。例如 computedStyle.width应该有一个未定义的值。我怎样才能做到这一点?

let ele = document.querySelector(".hello");
let computedStyle = window.getComputedStyle(ele);

console.log(computedStyle.backgroundColor);
console.log(computedStyle.height);
console.log(computedStyle.width); // <- This should be undefined or ""
.hello {
  height: 10px;
  background-color: red;
}
<div class="hello">
  helloInnerText
</div>

最佳答案

您可以通过检查所有样式表中适用于正在调查的元素的规则来做到这一点。当规则适用时,您有一个 css 属性,它是编写样式表的开发人员对该元素所需的值。
由于更具体的规则优先,因此您必须对照其他适用的规则检查每个规则的特殊性。
请注意,我不建议将其用于生产,因为它相当慢。
请参阅下面代码中的注释:

/* Say, we want to know the width, height, and background-color */
let propertiesWeWant = ['width', 'height', 'background-color', 'color', 'margin', 'font-size'];
let ele = document.querySelector(".hello");
/* First we have to prepare the stylesheets, in order to avoid CORS problems, 
 * since css rules of external stylesheets cannot be accessed. 
 * We loop through all stylesheets and inject external sheets into the head, 
 * so we can read them in the next step.
 */
for(let i = 0; i < document.styleSheets.length; i++){
    prepareStylesheet(document.styleSheets[i]);
}

setTimeout(function(){
    /* We get all the styles for this element, as found in the stylesheets. */
    let givenStyles = getStylesFromStylesheets(ele);
    /* And filter the results, so we only have the properties we want. */
    let propertyList = {};
    for(let i = 0; i < propertiesWeWant.length; i++){
        let property = propertiesWeWant[i];
        if(typeof(givenStyles[property]) !== "undefined"){
            propertyList[property] = givenStyles[property].style;
        }
        else{
            propertyList[property] = "";
        }
    }
    /* prints all given properties. 
    *  Object { width: "1px", height: "4px", "background-color": "pink", color: "turquoise", margin: "", "font-size": "17.5px" }
    *  */
    console.log(propertyList);
},2000);

/* Taken from her: https://discourse.mozilla.org/t/webextensions-porting-access-to-cross-origin-document-stylesheets-cssrules/18359 
 * If cssRules cannot be accessed because of CORS issues:
 * 1. remove the stylesheet
 * 2. fetch it using its url
 * 3. insert the rules in the header
 */
function prepareStylesheet(sheet){
    try {
        sheet.cssRules;
    } catch (e) {
        if (e.name === 'SecurityError') {
            let nextElement = sheet.ownerNode.nextSibling;
            let parentNode = sheet.ownerNode.parentNode;
            sheet.ownerNode.parentNode.removeChild(sheet.ownerNode);
            fetch(sheet.href).then(resp => resp.text()).then(css => {
                let style = document.createElement('style');
                style.innerText = css;
                //document.head.appendChild(style);
                if(nextElement === null){
                    parentNode.appendChild(style);
                }
                else{
                    parentNode.insertBefore(style, nextElement);
                }
                
            });  
            
            
        }
    }
}
/**
 * Search through stylesheets for rules that apply to a given element.
 * Returns object with css properties name and css property value 
 */
function getStylesFromStylesheets(elementToInvestigate){
    let givenStyles = {};
    //alert(document.styleSheets.length);
    for(let i = 0; i < document.styleSheets.length; i++){
        let rules = document.styleSheets[i].cssRules;
        /* Loop through every rule in the stylesheet and see wether it applies to this element */
        for(let j = 0; j < rules.length; j++){
            let rule = rules[j];
            /* Ignore media rules. Can be added, but needs some logic that you have to write. */
            if(typeof(rule.selectorText) === "undefined"){
                continue;
            }
            let split = rule.selectorText.split(",");
            for(let l = 0; l < split.length; l++){
                let selector = split[l];
                let elements = document.querySelectorAll(selector);
                let applies = false;
                for(let k = 0; k < elements.length; k++){
                    if(elements[k] === elementToInvestigate){
                        applies = true;
                        break;
                    }
                }
                if(applies === true){
                    /* If the rule applies to this element, loop through the css properties of this css rule. */
                    let styles = rule.style;
                    for(let k = 0; k < styles.length; k++){
                        /* For each css property, check if it was already added earlier. */ 
                        let styleName = styles[k];
                        let styleValue = styles[styleName];
                        let newSpecificity = calculateSingle( selector);
                        if(typeof(givenStyles[styleName]) !== "undefined"){
                            /* If the property was already added, compare the specificity of the new one to the one added earlier. */
                            let earlierSelector = givenStyles[styleName].selector;
                            let earlierSpecificity = givenStyles[styleName].specificity;
                            let newHasMoreSpecificity = compareSpecifities( newSpecificity, earlierSpecificity);
                            if(newHasMoreSpecificity === true){
                                givenStyles[styleName] = {
                                        style: styleValue,
                                        specificity: newSpecificity
                                }
                            }
                            else{
                                /* Leave givenStyles as it was, since the former tule had higher specificity. */
                            }
                        }
                        else{
                            /* If the property was not yet seen, add it to the givenStyles. */
                            givenStyles[styleName] = {
                                    style: styleValue,
                                    specificity: newSpecificity
                            }
                        }
                    }
                }
            }
        }
        /* If the element has inline styles, overwrite the found properties since inline always takes precedence. */
        if(elementToInvestigate.style.length > 0){
            for(let j = 0; j < elementToInvestigate.style.length; j++){
                let styleName = elementToInvestigate.style[j];
                let styleValue = elementToInvestigate.style[styleName];
                givenStyles[styleName] = {
                        style: styleValue,
                        specificity: [1,1,1,1] //not needed here
                }
            }
        }
        var inlineStyles;
    }
    return givenStyles;
}
/* Taken from here and adjusted the output: https://raw.githubusercontent.com/keeganstreet/specificity/master/specificity.js */
/**
 * Calculates the specificity of CSS selectors
 * http://www.w3.org/TR/css3-selectors/#specificity
 *
 * Returns specificity: e.g. 0,1,0,0
 */
function calculateSingle(input) {
    var selector = input,
        findMatch,
        typeCount = {
            'a': 0,
            'b': 0,
            'c': 0
        },
        parts = [],
        // The following regular expressions assume that selectors matching the preceding regular expressions have been removed
        attributeRegex = /(\[[^\]]+\])/g,
        idRegex = /(#[^\#\s\+>~\.\[:\)]+)/g,
        classRegex = /(\.[^\s\+>~\.\[:\)]+)/g,
        pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi,
        // A regex for pseudo classes with brackets - :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-type(), :lang()
        // The negation psuedo class (:not) is filtered out because specificity is calculated on its argument
        // :global and :local are filtered out - they look like psuedo classes but are an identifier for CSS Modules
        pseudoClassWithBracketsRegex = /(:(?!not|global|local)[\w-]+\([^\)]*\))/gi,
        // A regex for other pseudo classes, which don't have brackets
        pseudoClassRegex = /(:(?!not|global|local)[^\s\+>~\.\[:]+)/g,
        elementRegex = /([^\s\+>~\.\[:]+)/g;

    // Find matches for a regular expression in a string and push their details to parts
    // Type is "a" for IDs, "b" for classes, attributes and pseudo-classes and "c" for elements and pseudo-elements
    findMatch = function(regex, type) {
        var matches, i, len, match, index, length;
        if (regex.test(selector)) {
            matches = selector.match(regex);
            for (i = 0, len = matches.length; i < len; i += 1) {
                typeCount[type] += 1;
                match = matches[i];
                index = selector.indexOf(match);
                length = match.length;
                parts.push({
                    selector: input.substr(index, length),
                    type: type,
                    index: index,
                    length: length
                });
                // Replace this simple selector with whitespace so it won't be counted in further simple selectors
                selector = selector.replace(match, Array(length + 1).join(' '));
            }
        }
    };

    // Replace escaped characters with plain text, using the "A" character
    // https://www.w3.org/TR/CSS21/syndata.html#characters
    (function() {
        var replaceWithPlainText = function(regex) {
                var matches, i, len, match;
                if (regex.test(selector)) {
                    matches = selector.match(regex);
                    for (i = 0, len = matches.length; i < len; i += 1) {
                        match = matches[i];
                        selector = selector.replace(match, Array(match.length + 1).join('A'));
                    }
                }
            },
            // Matches a backslash followed by six hexadecimal digits followed by an optional single whitespace character
            escapeHexadecimalRegex = /\\[0-9A-Fa-f]{6}\s?/g,
            // Matches a backslash followed by fewer than six hexadecimal digits followed by a mandatory single whitespace character
            escapeHexadecimalRegex2 = /\\[0-9A-Fa-f]{1,5}\s/g,
            // Matches a backslash followed by any character
            escapeSpecialCharacter = /\\./g;

        replaceWithPlainText(escapeHexadecimalRegex);
        replaceWithPlainText(escapeHexadecimalRegex2);
        replaceWithPlainText(escapeSpecialCharacter);
    }());

    // Remove anything after a left brace in case a user has pasted in a rule, not just a selector
    (function() {
        var regex = /{[^]*/gm,
            matches, i, len, match;
        if (regex.test(selector)) {
            matches = selector.match(regex);
            for (i = 0, len = matches.length; i < len; i += 1) {
                match = matches[i];
                selector = selector.replace(match, Array(match.length + 1).join(' '));
            }
        }
    }());

    // Add attribute selectors to parts collection (type b)
    findMatch(attributeRegex, 'b');

    // Add ID selectors to parts collection (type a)
    findMatch(idRegex, 'a');

    // Add class selectors to parts collection (type b)
    findMatch(classRegex, 'b');

    // Add pseudo-element selectors to parts collection (type c)
    findMatch(pseudoElementRegex, 'c');

    // Add pseudo-class selectors to parts collection (type b)
    findMatch(pseudoClassWithBracketsRegex, 'b');
    findMatch(pseudoClassRegex, 'b');

    // Remove universal selector and separator characters
    selector = selector.replace(/[\*\s\+>~]/g, ' ');

    // Remove any stray dots or hashes which aren't attached to words
    // These may be present if the user is live-editing this selector
    selector = selector.replace(/[#\.]/g, ' ');

    // Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument
    // Remove non-standard :local and :global CSS Module identifiers because they do not effect the specificity
    selector = selector.replace(/:not/g, '    ');
    selector = selector.replace(/:local/g, '      ');
    selector = selector.replace(/:global/g, '       ');
    selector = selector.replace(/[\(\)]/g, ' ');

    // The only things left should be element selectors (type c)
    findMatch(elementRegex, 'c');

    // Order the parts in the order they appear in the original selector
    // This is neater for external apps to deal with
    parts.sort(function(a, b) {
        return a.index - b.index;
    });

    return [0, typeCount.a, typeCount.b, typeCount.c];
};

/**
 * Compares two specificities
 *
 *  - it returns false if a has a lower specificity than b
 *  - it returns true if a has a higher specificity than b
 *  - it returns true if a has the same specificity than b
 */
function compareSpecifities(aSpecificity, bSpecificity) {

    for (var i = 0; i < 4; i += 1) {
        if (aSpecificity[i] < bSpecificity[i]) {
            return false;
        } else if (aSpecificity[i] > bSpecificity[i]) {
            return true;
        }
    }

    return true;
};
blockquote{
    margin:7px;
    border-left: 10000px;
}
#hello-id.hello{
    height: 1px;
}
#hello-id{
    height: 2px;
}
html #hello-id{
    height: 3px;
    color: green;
}
#hello-id.hello{
    height: 4px;
    color: turquoise;
}
.hello-wrapper .hello {
  height: 5px;
  background-color: blue;
}
.hello {
  height: 5px;
  background-color: red;
}
#bogus.bogus{
    height: 6px;
    background-color: orange;
}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css" >
<div class="hello-wrapper">
    <blockquote id="hello-id" class="hello" style="width:1px; background-color: pink;">
      helloInnerText
    </blockquote>
</div>

关于javascript - 如何获得所需的元素样式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72718967/

相关文章:

javascript - 绝对元素在相对元素悬停和点击问题的顶部

html - 将一个 div 拆分为四个独立的 div

css - 如何为特定的单个元素设置固定的字体大小/防止浏览器缩放?

javascript - jquery 显示隐藏多个 div 最快的方法

javascript - 有什么方法可以在没有查询字符串的情况下获取 URL?

javascript - JS 异步函数停止页面加载

php - 如何在提交时从侧栏中的mysql获取搜索结果而不刷新页面

css - Chrome 中奇怪的 CSS 边框问题

javascript - 当按钮被触发时如何显示不同的表格数据?

javascript - global.eval 无法访问词法范围内的变量。该行为是否符合 ECMAScript 标准?