【问题标题】:How to get the applied style from an element, excluding the default user agent styles如何从元素中获取应用的样式,不包括默认的用户代理样式
【发布时间】:2020-07-22 22:43:22
【问题描述】:

如何在 JavaScript 中检索已应用于元素的样式,不包括默认的用户代理样式(因此仅限内联 + 样式表样式)。

基本上,您可以在您最喜欢的开发人员工具的“计算”选项卡中看到的所有用户样式:

请不要使用框架,IE8+、Edge、Chrome 和 Firefox。

我希望答案是getComputedStyle 减去getDefaultComputedStyle 的结果,但是是以跨浏览器的方式。看到所有的开发者工具都可以做到,肯定有办法的:)

【问题讨论】:

  • window.getComputedStyle() ?
  • @AlexK.: 不,我不想要默认的用户代理样式:jsfiddle.net/701tg35d
  • @n_palum 该问题或其答案都不排除结果的默认用户代理样式
  • getDefaultComputedStyle 是非标准的,因此不是一个选项。开发人员工具可能经过硬编码以了解浏览器应用的内容,因此这可能无法通过 javascript 实现。

标签: javascript html css


【解决方案1】:

文档有一个名为“styleSheets”的只读属性。

var styleSheetList = document.styleSheets;

https://developer.mozilla.org/en-US/docs/Web/API/Document/styleSheets

通过这个,你可以达到作者应用的所有样式。

这里有一个类似的问题,但不是重复的,在这里:

Is it possible to check if certain CSS properties are defined inside the style tag with Javascript?

您可以使用我刚才提到的那个问题的accepted answer 从元素中获取应用的样式,不包括默认的用户代理样式。

那个答案没有提供元素自己的style 属性内容,所以我对代码做了一些改进:

var proto = Element.prototype;
var slice = Function.call.bind(Array.prototype.slice);
var matches = Function.call.bind(proto.matchesSelector || 
                proto.mozMatchesSelector || proto.webkitMatchesSelector ||
                proto.msMatchesSelector || proto.oMatchesSelector);

// Returns true if a DOM Element matches a cssRule
var elementMatchCSSRule = function(element, cssRule) {
  return matches(element, cssRule.selectorText);
};

// Returns true if a property is defined in a cssRule
var propertyInCSSRule = function(prop, cssRule) {
  return prop in cssRule.style && cssRule.style[prop] !== "";
};

// Here we get the cssRules across all the stylesheets in one array
var cssRules = slice(document.styleSheets).reduce(function(rules, styleSheet) {
  return rules.concat(slice(styleSheet.cssRules));
}, []);




var getAppliedCss = function(elm) {
	// get only the css rules that matches that element
	var elementRules = cssRules.filter(elementMatchCSSRule.bind(null, elm));
	var rules =[];
	if(elementRules.length) {
		for(i = 0; i < elementRules.length; i++) {
			var e = elementRules[i];
			rules.push({
				order:i,
				text:e.cssText
			})
		}		
	}
	
	if(elm.getAttribute('style')) {
		rules.push({
				order:elementRules.length,
				text:elm.getAttribute('style')
			})
	}
	return rules;
}







function showStyle(){
var styleSheetList = document.styleSheets;
// get a reference to an element, then...
var div1 = document.getElementById("div1");

var rules = getAppliedCss(div1);

var str = '';
for(i = 0; i < rules.length; i++) {
			var r = rules[i];
			str += '<br/>Style Order: ' + r.order + ' | Style Text: ' + r.text; 
		}		
		
	document.getElementById("p1").innerHTML = str;	

}
#div1 {
float:left;
width:100px;
}

div {
text-align:center;
}
<div id="div1" style="font-size:14px;">
	Lorem ipsum 
	</div>
<br/>
<br/>
<a href="javascript:;" onclick="showStyle()"> Show me the style. </a>
	<p id="p1"><p>

【讨论】:

  • 我确实喜欢这种方法,但我想说它目前忽略了内联样式。应将element.style 添加到结果中以使其完整。
  • 对,我现在明白了。让我想想我能做什么
  • 我在代码示例中添加了元素自己的样式属性内容
  • @GyumFox 我想知道您是否在多个浏览器上尝试过这种方法?如果是这样,你能分享一下结果吗?
  • 我在这里使用了你的答案代码:stackoverflow.com/questions/50567761/… 也许你会想看看我修改了什么!
【解决方案2】:

所有开发者工具都可以作弊,因为它们可以访问它们所内置的浏览器所应用的默认规则。

我认为following approach 可能会起作用。

  1. 构造一个与我们感兴趣的元素完全相同类型的元素(例如,divp)。
  2. 将此元素附加到页面的某处,以便仅应用默认浏览器规则。我们可以通过将其放在 iframe 中来做到这一点。
    例如,如果您确定没有针对任何 p 元素的规则,那么附加到正文可能会更有效。
  3. 检查样式差异并仅报告不同的值。
  4. 清理临时元素。

它在实践中似乎运作良好。我只在 Firefox 和 Chrome 中对此进行了测试,但我认为它也应该在其他浏览器中工作 - 可能除了我使用了 for...infor...of 的事实,但可以很容易地重写它。请注意,报告的不仅仅是您指定的属性,还有一些受您指定的属性影响的属性。例如,边框颜色与文本颜色by design 匹配,因此即使您仅设置color: white,也会报告为不同。

总而言之,我采用了您在其中一个 cmets 中发布的示例,并向其中添加了一个 getNonDefaultStyles 函数,我认为它可以满足您的需求。它当然可以修改为缓存默认样式,例如 div 元素,因此在重复调用时效率更高(因为修改了 DOM is expensive),但它显示了要点。

下面的 sn-p 显示了如何实现将元素附加到正文的版本。由于 StackOverflow 的限制,无法在 sn-p 中显示 iframe 版本。在JSFiddle 上是可能的。下面的 sn-p 也可以在 Fiddle 中找到。

var textarea = document.getElementById("textarea"),
    paragraph = document.getElementById("paragraph");

/**
 * Computes applied styles, assuming no rules targeting a specific element.
 */
function getNonDefaultStyles(el) {
  var styles = {},
    computed = window.getComputedStyle(el),
    notTargetedContainer = document.createElement('div'),
    elVanilla = document.createElement(el.tagName);
  document.body.appendChild(notTargetedContainer);
  notTargetedContainer.appendChild(elVanilla);
  var vanilla = window.getComputedStyle(elVanilla);
  for (let key of computed) {
    if (vanilla[key] !== computed[key]) {
      styles[key] = computed[key];
    }
  }
  document.body.removeChild(notTargetedContainer);
  return styles;
}

var paragraphStyles = getNonDefaultStyles(paragraph);
for (let style in paragraphStyles) {
  textarea.value += style + ": " + paragraphStyles[style] + "\n";
}
#paragraph {
  background: red;
}

textarea {
  width: 300px;
  height: 400px;
}
<p id="paragraph" style="color: white">
  I am a DIV
</p>

<p>
  User styles:
</p>
<textarea id="textarea"></textarea>

【讨论】:

  • 我确实喜欢这个想法,但第 2 点可能会成为一个阻碍,因为您将无法消除直接应用于给定类型的所有元素的样式(例如:P {margin:0 })。我想调整解决方案来处理这些问题。可能是通过直接从样式表中读取这些元素的样式(灵感来自 er-han 的解决方案),或者通过在 iframe 中加载空白页面来获取给定元素类型的默认样式?
  • 感谢您的更新。我稍微更改了 Fiddle 以使其在 Edge 上工作:jsfiddle.net/9o178w97/2(我用于 ... in,如您所说,它有一些缺点)。尽管您的解决方案与我最初的想法最接近,但我接受了@er-han 的回答,因为它的优点是只返回用户明确设置的样式属性。
【解决方案3】:

这是一个函数,它从页面上的内联样式(HTML style 属性)或样式表中获取已应用于元素的所有 CSS 规则。它还抓取 CSS 动画和 :active:hover::before::after 选择器的相关关键帧。

function getAppliedCssData(el) {
  // we create a unique id so we can generate unique ids for renaming animations
  let uniqueId = "id" + Math.random().toString().slice(2) + Math.random().toString().slice(2);

  let allRules = [...document.styleSheets].map(s => {
    let rules = [];
    try { rules.push(...s.cssRules) } catch(e) {} // we ignore cross-domain stylesheets with restrictive CORs headers
    return rules;
  }).flat();

  let styleRules = allRules.filter(rule => rule.type === CSSRule.STYLE_RULE)
  let fontFaceRules = allRules.filter(rule => rule.type === CSSRule.FONT_FACE_RULE);
  let keyframesRules = allRules.filter(rule => rule.type === CSSRule.KEYFRAMES_RULE);

  let matchingDefaultRules = styleRules.filter(rule => el.matches(rule.selectorText));
  let nonMatchingRules = styleRules.filter(rule => !el.matches(rule.selectorText));
  let matchingHoverRules =  nonMatchingRules.filter(rule => el.matches(rule.selectorText.replace(/ :/g, " *:").replace(/([^(])(:hover)\b/g, "$1")));
  let matchingActiveRules = nonMatchingRules.filter(rule => el.matches(rule.selectorText.replace(/ :/g, " *:").replace(/([^(])(:active)\b/g, "$1")));
  let matchingBeforeRules = nonMatchingRules.filter(rule => el.matches(rule.selectorText.replace(/ :/g, " *:").replace(/::before\b/g, "")));
  let matchingAfterRules =  nonMatchingRules.filter(rule => el.matches(rule.selectorText.replace(/ :/g, " *:").replace(/::after\b/g, "")));
  let allMatchingStyleRules = [...matchingActiveRules, ...matchingDefaultRules, ...matchingHoverRules, ...matchingBeforeRules, ...matchingAfterRules];
  let matchingAnimationNames = allMatchingStyleRules.map(rule => rule.style.animationName).filter(n => n.trim());
  let matchingKeyframeRules = keyframesRules.filter(rule => matchingAnimationNames.includes(rule.name));
  
  // make name changes before actually grabbing the style text of each type
  allMatchingStyleRules.forEach(rule => rule.style.animationName = rule.style.animationName+uniqueId);
  matchingKeyframeRules.forEach(rule => rule.name = rule.name+uniqueId);

  let matchingDefaultStyles = matchingDefaultRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ") + (el.getAttribute('style') || ""); // important to add these last because inline styles are meant to override stylesheet styles (unless !important is used)
  let matchingHoverStyles = matchingHoverRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ");
  let matchingActiveStyles = matchingActiveRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ");
  let matchingBeforeStyles = matchingBeforeRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ");
  let matchingAfterStyles = matchingAfterRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ");
  let matchingKeyframeStyles = matchingKeyframeRules.map(rule => rule.cssText).join(" ");
  
  // undo the rule name changes because this actually affects the whole document:
  matchingKeyframeRules.forEach(rule => rule.name = rule.name.replace(uniqueId, "")); 
  allMatchingStyleRules.forEach(rule => rule.style.animationName = rule.style.animationName.replace(uniqueId, ""));

  let data = {
    uniqueId,
    defaultStyles: matchingDefaultStyles,
    hoverStyles: matchingHoverStyles,
    activeStyles: matchingActiveStyles,
    keyframeStyles: matchingKeyframeStyles,
    beforeStyles: matchingBeforeStyles,
    afterStyles: matchingAfterStyles,
  }
  return data;
}

:focus:focus-within:visited 选择器不包括在内,但可以轻松添加。

【讨论】:

    【解决方案4】:

    我以前用过这个功能...

    function get_style(obj,nam) { //obj = HTML element, nam = style property
      var val = "";
      if(document.defaultView && document.defaultView.getComputedStyle) {
        nam = nam.replace(/[A-Z]/g,function(str) { //convert name into hypenated
          return "-"+str.toLowerCase();
        });
        val = document.defaultView.getComputedStyle(obj,"").getPropertyValue(nam); //get current style
      }
      else if(obj.currentStyle) {
        nam = nam.replace(/\-(\w)/g,function(str,p1) { //convert name into camel case
          return p1.toUpperCase();
        });
        val = obj.currentStyle[nam]; //get current style
      }
      return val;
    }

    它允许您以连字符 (background-color) 或驼峰大小写 (backgroundColor) 的形式传入样式属性,并根据它使用的方法替换它。

    这也涵盖了旧版浏览器,甚至是旧版 IE!

    【讨论】:

    • 嗯...我想要的是一个列出用户应用的所有样式(和值)的函数(仅限内联或样式表)。您的功能似乎是一种“跨浏览器”getComputedStyle
    • 啊,对不起。稍微误读了这个问题,我以为您正在尝试获取特定的属性值。您可以遍历您可能感兴趣的所有样式的预定义列表,然后调用此函数,并仅列出非空白的样式。但我怀疑这会非常低效并且会产生一个相当长的列表。
    猜你喜欢
    • 1970-01-01
    • 2021-12-07
    • 1970-01-01
    • 1970-01-01
    • 2014-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-12
    相关资源
    最近更新 更多