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

前端 未结 4 1544
后悔当初
后悔当初 2020-12-10 04:55

How in JavaScript do you retrieve the styles that have been applied to an element, excluding the default user agent styles (so inline + stylesheet styles on

4条回答
  •  情深已故
    2020-12-10 05:36

    Here's a function that gets all the CSS rules that have been applied to an element from either inline styles (HTML style attribute) or stylesheets on the page. It also grabs relevant keyframes for CSS animations and the :active, :hover, ::before, and ::after selectors.

    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;
    }
    

    The :focus, :focus-within and :visited selectors are not included, but could be easily added.

提交回复
热议问题