本文旨在提供一种获取元素期望 css 属性的方法,即使这些样式是通过 JavaScript 动态设置的。传统的 `getComputedStyle` 方法返回的是元素最终应用的样式,而本文介绍的方法则能够提取开发者在样式表或内联样式中定义的原始样式,并考虑到 CSS 规则的优先级,帮助开发者更准确地了解元素的设计意图。 在 Web 开发中,我们经常需要获取元素的样式信息。`window.getComputedStyle` 方法可以获取元素最终应用的样式,但有时我们更需要获取开发者在 CSS 样式表中定义的原始样式,特别是当样式是通过 JavaScript 动态修改时。本教程将介绍一种通过遍历样式表并结合 CSS 选择器优先级来获取元素期望样式的方法。 ### 原理 该方法的核心思想是遍历文档中的所有样式表,找到适用于目标元素的 CSS 规则,并提取这些规则中定义的样式属性。由于 CSS 规则存在优先级(specificity),我们需要比较不同规则的优先级,选择优先级最高的规则中定义的样式作为元素的期望样式。 ### 实现步骤 1. **准备样式表:** 解决跨域问题。由于跨域限制,我们可能无法直接访问外部样式表的 `cssRules` 属性。为了解决这个问题,我们需要将外部样式表的内容读取并插入到文档的 `<head>` 中。 “`javascript 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; if(nextElement === NULL){ parentNode.appendChild(style); } else{ parentNode.insertBefore(style, nextElement); } }); } } } for(let i = 0; i < document.styleSheets.Length; i++){ prepareStylesheet(document.styleSheets[i]); }
-
获取元素样式: 遍历样式表,找到适用于目标元素的规则,并提取样式属性。
function getStylesFromStylesheets(elementToInvestigate) { let givenStyles = {}; for (let i = 0; i < document.styleSheets.length; i++) { let rules = document.styleSheets[i].cssRules; for (let j = 0; j < rules.length; j++) { let rule = rules[j]; 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) { let styles = rule.style; for (let k = 0; k < styles.length; k++) { let styleName = styles[k]; let styleValue = styles[styleName]; let newSpecificity = calculateSingle( selector); if (typeof(givenStyles[styleName]) !== "undefined") { 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 { givenStyles[styleName] = { style: styleValue, specificity: newSpecificity } } } } } } 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 } } } } return givenStyles; }
-
计算 CSS 选择器优先级: 使用 calculateSingle 函数计算 CSS 选择器的优先级。该函数基于 CSS 规范,将选择器优先级表示为一个四元组 [a, b, c, d],其中 a 代表 ID 选择器的数量,b 代表类选择器、属性选择器和伪类选择器的数量,c 代表元素选择器和伪元素选择器的数量。
function calculateSingle(input) { var selector = input, findMatch, typeCount = { 'a': 0, 'b': 0, 'c': 0 }, parts = [], attributeRegex = /([[^]]+])/g, idRegex = /(#[^#s+>~.[:)]+)/g, classRegex = /(.[^s+>~.[:)]+)/g, pseudoElementRegex = /(::[^s+>~.[:]+|:first-line|:first-letter|:before|:after)/gi, pseudoClassWithBracketsRegex = /(:(?!not|global|local)[w-]+([^)]*))/gi, pseudoClassRegex = /(:(?!not|global|local)[^s+>~.[:]+)/g, elementRegex = /([^s+>~.[:]+)/g; 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 }); selector = selector.replace(match, Array(length + 1).join(' ')); } } }; (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')); } } }, escapeHexadecimalRegex = /[0-9A-Fa-f]{6}s?/g, escapeHexadecimalRegex2 = /[0-9A-Fa-f]{1,5}s/g, escapeSpecialCharacter = /./g; replaceWithPlainText(escapeHexadecimalRegex); replaceWithPlainText(escapeHexadecimalRegex2); replaceWithPlainText(escapeSpecialCharacter); }()); (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(' ')); } } }()); findMatch(attributeRegex, 'b'); findMatch(idRegex, 'a'); findMatch(classRegex, 'b'); findMatch(pseudoElementRegex, 'c'); findMatch(pseudoClassWithBracketsRegex, 'b'); findMatch(pseudoClassRegex, 'b'); selector = selector.replace(/[*s+>~]/g, ' '); selector = selector.replace(/[#.]/g, ' '); selector = selector.replace(/:not/g, ' '); selector = selector.replace(/:local/g, ' '); selector = selector.replace(/:global/g, ' '); selector = selector.replace(/[()]/g, ' '); findMatch(elementRegex, 'c'); parts.sort(function(a, b) { return a.index - b.index; }); return [0, typeCount.a, typeCount.b, typeCount.c]; };
-
比较优先级: 使用 compareSpecifities 函数比较两个 CSS 选择器的优先级。
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; };
-
获取期望样式: 遍历完成后,givenStyles 对象将包含目标元素的期望样式。
let propertiesWeWant = ['width', 'height', 'background-color', 'color', 'margin', 'font-size']; let ele = document.querySelector(".hello"); let givenStyles = getStylesFromStylesheets(ele); 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] = ""; } } console.log(propertyList);
示例代码
<!DOCTYPE html> <html> <head> <title>获取元素期望样式</title> <link rel="stylesheet" href="https://cdn.JSdelivr.net/npm/<a class="__cf_email__" data-cfemail="96f4f9f9e2e5e2e4f7e6d6a5b8a2b8a7" href="/cdn-cgi/l/email-protection">[email protected]</a>/dist/css/bootstrap.min.css" > <style> 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; } </style> </head> <body> <div class="hello-wrapper"> <blockquote id="hello-id" class="hello" style="width:1px; background-color: pink;"> helloInnerText </blockquote> </div> <script> let propertiesWeWant = ['width', 'height', 'background-color', 'color', 'margin', 'font-size']; let ele = document.querySelector(".hello"); for(let i = 0; i < document.styleSheets.length; i++){ prepareStylesheet(document.styleSheets[i]); } setTimeout(function(){ let givenStyles = getStylesFromStylesheets(ele); 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] = ""; } } console.log(propertyList); },2000); 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; if(nextElement === null){ parentNode.appendChild(style); } else{ parentNode.insertBefore(style, nextElement); } }); } } } function getStylesFromStylesheets(elementToInvestigate) { let givenStyles = {}; for (let i = 0; i < document.styleSheets.length; i++) { let rules = document.styleSheets[i].cssRules; for (let j = 0; j < rules.length; j++) { let rule = rules[j]; 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) { let styles = rule.style; for (let k = 0; k < styles.length; k++) { let styleName = styles[k]; let styleValue = styles[styleName]; let newSpecificity = calculateSingle( selector); if (typeof(givenStyles[styleName]) !== "undefined") { 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 { givenStyles[styleName] = { style: styleValue, specificity: newSpecificity } } } } } } 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 } } } } return givenStyles; } function calculateSingle(input) { var selector = input, findMatch, typeCount = { 'a': 0, 'b': 0, 'c': 0 }, parts = [], attributeRegex = /([[^]]+])/g, idRegex = /(#[^#s+>~.[:)]+)/g, classRegex = /(.[^s+>~.[:)]+)/g, pseudoElementRegex = /(::[^s+>~.[:]+|:first-line|:first-letter|:before|:after)/gi, pseudoClassWithBracketsRegex = /(:(?!not|global|local)[w-]+([^)]*))/gi, pseudoClassRegex = /(:(?!not|global|local)[^s+>~.[:]+)/g, elementRegex = /([^s+>~.[:]+)/g; 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 }); selector = selector.replace(match, Array(length + 1).join(' ')); } } }; (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')); } } }, escapeHexadecimalRegex = /[0-9A-Fa-f]{6}s?/g, escapeHexadecimalRegex2 = /[0-9A-Fa-f]{1,5}s/g, escapeSpecialCharacter = /./g; replaceWithPlainText(escapeHexadecimalRegex); replaceWithPlainText(escapeHexadecimalRegex2); replaceWithPlainText(escapeSpecialCharacter); }()); (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(' ')); } } }()); findMatch(attributeRegex, 'b'); findMatch(idRegex, 'a'); findMatch(classRegex, 'b'); findMatch(pseudoElementRegex, 'c'); findMatch(pseudoClassWithBracketsRegex, 'b'); findMatch(pseudoClassRegex, 'b'); selector = selector.replace(/[*s+>~]/g, ' '); selector = selector.replace(/[#.]/g, ' '); selector = selector.replace(/:not/g, ' '); selector = selector.replace(/:local/g, ' '); selector = selector.replace(/:global/g, ' '); selector = selector.replace(/[()]/g, ' '); findMatch(elementRegex, 'c'); parts.sort(function(a, b) { return a.index - b.index; }); return [0, typeCount.a, typeCount.b, typeCount.c]; }; 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; }; </script> </body> </html>
注意事项
- 性能: 遍历样式表并计算选择器优先级是一个耗时的操作,不建议在生产环境中使用。
- 跨域: 需要处理跨域问题,确保能够访问外部样式表。
- 复杂选择器: 对于非常复杂的 CSS 选择器,优先级计算可能存在误差。
- 动态样式: 此方法主要针对静态 CSS 样式表,对于通过 JavaScript 动态添加的样式,可能无法准确获取。
总结
本教程介绍了一种获取元素期望 CSS 样式的方法,通过遍历样式表并结合 CSS 选择器优先级,可以提取开发者在样式表或内联样式中定义的原始样式。虽然该方法存在性能问题,但在某些特定场景下,例如需要分析元素样式来源或进行样式调试时,仍然具有一定的价值。 在实际应用中,需要根据具体情况权衡性能和准确性,选择合适的方案。
评论(已关闭)
评论已关闭