如何从元素中获取应用的样式,不包括默认的用户代理样式

时间:2017-02-03 13:21:34

标签: javascript html css

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

基本上,您可以在自己喜欢的开发者工具的Computed选项卡中看到所有用户样式:

User styles displayed in Edge's developer tool

没有框架,IE8 +,Edge,Chrome和Firefox。

我希望答案是getComputedStyle减去getDefaultComputedStyle的结果,但是以跨浏览器的方式。看到所有开发人员工具都能够做到这一点,必须有一个解决方案:)

4 个答案:

答案 0 :(得分:6)

有一个名为' 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;	

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

div {
text-align:center;
}
&#13;
<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>
&#13;
&#13;
&#13;

答案 1 :(得分:3)

所有开发人员工具都可以作弊,因为他们可以访问构建它们的浏览器的默认规则。

我认为following approach可能有用。

  1. 构建与我们感兴趣的类型完全相同的元素(例如,divp)。
  2. 在页面的某处附加此元素,以便仅应用默认浏览器规则。我们可以将其放在iframe例如,如果您确定没有针对任何p元素的规则,那么附加到正文可能会更有效。
  3. 检查样式的差异,仅报告不同的值。
  4. 清理临时元素。
  5. 它似乎在实践中运作得相当好。我只在Firefox和Chrome中对此进行了测试,但我认为它也可以在其他浏览器中使用 - 除了我使用for...infor...of的事实,但可以轻松地重写它。请注意,不会报告您指定的属性,还会报告受您指定的属性影响的某些属性。例如,边框颜色与文本颜色by design匹配,因此即使您只设置了color: white,也会报告为不同。

    总结一下,我已经在你的一条评论中发布了一个例子,并在其中添加了一个getNonDefaultStyles函数,我认为它可以满足您的需求。它当然可以被修改为缓存say div元素的默认样式,因此在重复调用中更有效(因为修改DOM is expensive),但它显示了要点。

    下面的代码段显示了如何实现将元素附加到正文的版本。由于StackOverflow的限制,无法在代码段中显示iframe版本。这可以在JSFiddle。以下代码段也可以在Fiddle中找到。

    &#13;
    &#13;
    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";
    }
    &#13;
    #paragraph {
      background: red;
    }
    
    textarea {
      width: 300px;
      height: 400px;
    }
    &#13;
    <p id="paragraph" style="color: white">
      I am a DIV
    </p>
    
    <p>
      User styles:
    </p>
    <textarea id="textarea"></textarea>
    &#13;
    &#13;
    &#13;

答案 2 :(得分:1)

这是一个函数,该函数从页面上的内联样式(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选择器不包括在内,但可以轻松添加。

答案 3 :(得分:0)

我过去曾使用过这个功能......

&#13;
&#13;
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;
}
&#13;
&#13;
&#13;

它允许您将样式属性传递为hypenated(background-color)或camel case(backgroundColor)并根据其使用的方法替换它。

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