簡體   English   中英

如何從元素中獲取應用的樣式,不包括默認的用戶代理樣式

[英]How to get the applied style from an element, excluding the default user agent styles

您如何在 JavaScript 中檢索已應用於元素的樣式,不包括默認的用戶代理樣式(因此僅限內聯 + 樣式表樣式)。

基本上,您可以在您最喜歡的開發人員工具的 Computed 選項卡中看到所有用戶樣式:

Edge 的開發者工具中顯示的用戶樣式

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

我期望答案是getComputedStyle減去getDefaultComputedStyle的結果,但是以跨瀏覽器的方式。 看到所有的開發者工具都可以做到,一定有解決辦法:)

文檔有一個名為“styleSheets”的只讀屬性。

var styleSheetList = document.styleSheets;

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

通過使用它,您可以訪問作者應用的所有樣式。

有一個類似的問題,但不是重復的,在這里:

是否可以使用 Javascript 檢查是否在樣式標簽內定義了某些 CSS 屬性?

您可以使用我剛剛提到的那個問題的公認答案從元素中獲取應用的樣式,不包括默認的用戶代理樣式。

該答案沒有提供元素自己的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>

所有開發人員工具都可以作弊,因為它們可以訪問它們內置的瀏覽器應用的默認規則。

我認為以下方法可能有效。

  1. 構造一個與我們感興趣的元素完全相同類型的元素(例如divp )。
  2. 將此元素附加到頁面上的某處,以便僅應用默認瀏覽器規則。 我們可以將其放入iframe中。
    例如,如果您確定沒有針對任何p元素的規則,那么附加到正文可能會更有效。
  3. 檢查樣式的差異,只報告不同的值。
  4. 清理臨時元素。

它在實踐中似乎工作得相當好。 我只在 Firefox 和 Chrome 中對此進行了測試,但我認為它也應該在其他瀏覽器中工作 - 可能除了我使用了 for...infor...of的事實,但人們可以輕松地重寫它。 請注意,不僅會報告您指定的屬性,還會報告一些受您指定的屬性影響的屬性。 例如,邊框顏色與設計上的文本顏色相匹配,因此即使您僅設置color: white也會報告為不同的顏色。

總而言之,我采用了您在其中一個評論中發布的示例,並向其中添加了一個getNonDefaultStyles函數,我認為它可以滿足您的需求。 它當然可以修改為緩存默認樣式,例如div元素,因此在重復調用中效率更高(因為修改 DOM很昂貴),但它顯示了要點。

下面的代碼片段顯示了如何實現將元素附加到正文的版本。 由於 StackOverflow 的限制,無法在片段中顯示iframe版本。 JSFiddle上是可能的。 下面的代碼片段也可以在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>

這是一個函數,它從頁面上的內聯樣式(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選擇器不包括在內,但可以輕松添加。

您可以通過將它們與具有相同標簽名稱的“默認”HTML 元素進行比較來計算用戶應用的(非默認)樣式,該元素在隔離的<iframe>中呈現,因此文檔中的樣式不會“泄漏”到默認值元素。

此解決方案與@Just a student 相同,但增加了以下改進:

  1. <iframe>是一個隱藏的 HTML 元素,因此用戶不會看到它
  2. 為了性能,默認樣式被緩存,我們等待清理<iframe>直到我們調用removeSandbox結束
  3. 它考慮了繼承(即,如果您提供parentElement ,即使父級設置它並且元素將其覆蓋回默認值,它也會列出樣式)
  4. 它考慮了默認樣式的初始值和計算值不匹配的情況(有關更多信息,請參閱此 PR中的注釋 [1])
// usage:
element = document.querySelector('div');
styles = getUserComputedStyles(element);
styles = getUserComputedStyles(element, parentElement);
// call this method when done to cleanup:
removeSandbox();

function getUserComputedStyles(element, parentElement = null) {
    var defaultStyle = getDefaultStyle(element.tagName);
    var computedStyle = window.getComputedStyle(element);
    var parentStyle =
      parentElement ? window.getComputedStyle(parentElement) : null;
    var styles = {};
    [...computedStyle].forEach(function(name) {
        // If the style does not match the default, or it does not match the
        // parent's, set it. We don't know which styles are inherited from the
        // parent and which aren't, so we have to always check both.
        // This results in some extra default styles being returned, so if you
        // want to avoid this and aren't concerned about omitting styles that
        // the parent set but the `element` overrides back to the default,
        // call `getUserComputedStyles` without a `parentElement`.
        const computedStyleValue = computedStyle[name];
        if (computedStyleValue !== defaultStyle[name] ||
          (parentStyle && computedStyleValue !== parentStyle[name])) {
            styles[name] = computedStyleValue;
        }
    });
    return styles;
}

var removeDefaultStylesTimeoutId = null;
var sandbox = null;
var tagNameDefaultStyles = {};

function getDefaultStyle(tagName) {
    if (tagNameDefaultStyles[tagName]) {
        return tagNameDefaultStyles[tagName];
    }
    if (!sandbox) {
        // Create a hidden sandbox <iframe> element within we can create
        // default HTML elements and query their computed styles. Elements
        // must be rendered in order to query their computed styles. The
        // <iframe> won't render at all with `display: none`, so we have to
        // use `visibility: hidden` with `position: fixed`.
        sandbox = document.createElement('iframe');
        sandbox.style.visibility = 'hidden';
        sandbox.style.position = 'fixed';
        document.body.appendChild(sandbox);
        // Ensure that the iframe is rendered in standard mode
        sandbox.contentWindow.document.write(
          '<!DOCTYPE html><meta charset="UTF-8"><title>sandbox</title><body>');
    }
    var defaultElement = document.createElement(tagName);
    sandbox.contentWindow.document.body.appendChild(defaultElement);
    // Ensure that there is some content, so properties like margin are applied
    defaultElement.textContent = '.';
    var defaultComputedStyle =
      sandbox.contentWindow.getComputedStyle(defaultElement);
    var defaultStyle = {};
    // Copy styles to an object, making sure that 'width' and 'height' are
    // given the default value of 'auto', since their initial value is always
    // 'auto' despite that the default computed value is sometimes an absolute
    // length.
    [...defaultComputedStyle].forEach(function(name) {
        defaultStyle[name] = (name === 'width' || name === 'height')
          ? 'auto' : defaultComputedStyle.getPropertyValue(name);
    });
    sandbox.contentWindow.document.body.removeChild(defaultElement);
    tagNameDefaultStyles[tagName] = defaultStyle;
    return defaultStyle;
}

function removeSandbox() {
    if (!sandbox) {
        return;
    }
    document.body.removeChild(sandbox);
    sandbox = null;
    if (removeDefaultStylesTimeoutId) {
        clearTimeout(removeDefaultStylesTimeoutId);
    }
    removeDefaultStylesTimeoutId = setTimeout(() => {
        removeDefaultStylesTimeoutId = null;
        tagNameDefaultStyles = {};
    }, 20 * 1000);
}

即使有了這些改進,對於塊元素,仍然列出了一些默認樣式,因為它們的初始值和計算值不匹配,即widthheightblock-sizeinset-blocktransform-originperspective-origin (見注釋在#4)。 dom-to-image-moregetUserComputedStyle函數)中的這種解決方案能夠修剪掉更多的這些,盡管計算速度較慢。

我以前用過這個功能...

 function get_style(obj,nam) { //obj = HTML element, nam = style property var val = ""; if(document.defaultView && document.defaultView.getComputedStyle) { nam = nam.replace(/[AZ]/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; }

它允許您將樣式屬性作為 hypenated ( background-color ) 或駝峰大小寫 ( backgroundColor ) 傳遞,並根據它使用的方法替換它。

這也涵蓋了較舊的瀏覽器,甚至是舊的 IE!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM