Using Javascript to dynamically add styles to styl

2019-08-15 01:49发布

问题:

I am currently trying to make my own polyfill that will allow older browsers such as Internet Explorer the ability to read and execute media queries using traditional css selectors. The process flow goes something like this:

  1. Iterate through each stylesheet found within document
  2. Run through each style rule in stylesheet searching for media queries
  3. Define what device size we are targeting then apply appropriate "id"
  4. Dynamically add css line with selector to existing stylesheet

The idea is that the polyfill will search for media queries then apply a parent "id" to the body corresponding to the device size, for instance:

#tablet .wrap { ... }
#mobile .wrap { ... }
#desktop .wrap { ... }

Here is what the javascript looks like so far:

var styleSheets = document.styleSheets;
var debug = false;
var ruleParts = mediaPart = rules = compiledSel = className = '';
var dimS = dimB = 0;
var idList = Array('smallMobile','mobile','tablet','smallDesktop','desktop');

// run through each stylesheet
for( i = 0; i < styleSheets.length; i++ ) {

    // If uncommented will show each stylesheets rules contained therein
    if( debug ) {
        console.log( styleSheets[i].cssRules );
    }

    // run through each rule declaration
    for( a = 0; a < styleSheets[i].rules.length; a++ ) {

        if( styleSheets[i].rules[a].type == 4 ) {

            mediaPart = styleSheets[i].rules[a].media[0].split(' and ');

            dimS = parseInt( mediaPart[0].replace(/[():A-Za-z$-]/g, "") );
            dimB = parseInt( mediaPart[1].replace(/[():A-Za-z$-]/g, "") );

            if( dimS > 0 && dimB < 418 ) {
                className = idList[0];
            } else if( dimS > 419 && dimB < 767 ) {
                className = idList[1];
            } else if( dimS > 768 && dimB < 1024 ) {
                className = idList[2];
            } else if( dimS > 1025 && dimB < 1201 ) {
                className = idList[3];
            } else {
                className = idList[4];
            }


            if( styleSheets[i].rules[a].cssRules.length > 1 ) {

                for( b = 0; b < styleSheets[i].rules[a].cssRules.length; b++ ) {

                    ruleParts = styleSheets[i].rules[a].cssRules[b].cssText.split('{');
                    rules = ruleParts[1].split('}');

                    addCSSRule( styleSheets[i], '#'+ className +' '+ ruleParts[0], rules[0], 1 );

                    /*
                     *      Investigate why the .insertRule() and addRule() are failing specifically what is causing them to break
                     *      
                     */

                }

            } else {

            }

        }

    }
}

function addCSSRule(sheet, selector, rules, index) {
    if(sheet.insertRule) {
        sheet.insertRule(selector + "{" + rules + "}", index);
    } else {
        sheet.addRule(selector, rules, index);
    }
}

Right now everything is working the way I want, even adding the css rule to the style sheet however I get this error and I'm not totally sure at what point:

Uncaught TypeError: Cannot read property 'length' of undefined 

If I remove the function call to this line, I don't get the error:

addCSSRule( styleSheets[i], '#'+ className +' '+ ruleParts[0], rules[0], 1 );

So I'm not sure what is happening during the function call and the reading of the .length

My html looks something like this:

<!DOCTYPE html>
<head>
<title>Style Manipulation VIA Javascript</title>

<link rel="stylesheet" href="css/test.css" />
<link rel="stylesheet" href="css/typo.css" />

</head>
<body>

<div class="wrap">
    <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>
    <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>
</div>

Overall I need to figure out why the property length can't be read and what in the addCSSRule function call is causing the error.

回答1:

There are significant differences between the IE and W3C implementations of the styleSheets object, hopefully the following is explanatory enough to help:

function getStyleRules() {
    var rule, rules;
    var sheet, sheets = document.styleSheets;
    var ruleCssText = ['Rules:','',]

    for (var i=0, iLen=sheets.length; i<iLen; i++) {
      sheet = sheets[i];

      // Get the rules using:
      //
      //        W3C model        IE model
      rules = sheet.cssRules || sheet.rules;

      for (var j=0, jLen=rules.length; j<jLen; j++) {
        rule = rules[j];

        // The selector is available here in both models,
        // but uppercase in IE so set to lower case
        alert('Selector: ' + rule.selectorText.toLowerCase());

        // Getting the actual rule text
        // W3C model - selector and rule
        if (rule.cssText) {
          ruleCssText.push(rule.cssText);

        // IE model - rule only, doesn't include selector
        } else if (rule.style) {
          ruleCssText.push(rule.style.cssText.toLowerCase());
        }
      }
      alert(ruleCssText.join('\n'));
    }
}

There is documentation on MSDN of the IE model, but I'm not sure if it still covers older IE or only the newer versions which may well use both models: styleSheet object, rule object

There is some Opera documentation here: Dynamic style - manipulating CSS with JavaScript

and also some information here: The styleSheet object, The CSS Rule object.

As with all web resources, take it with a serious amount of skepticism, look for other sources and test, test, test in as many browsers (especially older ones) as you can.

Running your code:

> TypeError: styleSheets[i].rules is undefined
> 
> for( a = 0; a < styleSheets[i].rules.length; a++ ) {

Here you are mixing the IE (rules) and W3C (cssRules) models. Better to just get the rules object once: -

  var sheetRules = styleSheets[i].rules || styleSheets[i].cssRules;
  for( a = 0; a < sheetRules.length; a++ ) {

Then you have:

  if ( sheetRules[a].type == 4 ) {

but the IE model doesn't implement a type property for the rules object (even though the MSDN documentation says it does, that's for the W3C model implemented by IE 9 and later).

Then:

>     mediaPart = sheetRules[a].media[0].split(' and ');

Note that media is a property of the sheet, not of a rule, so:

      // Perhaps mediaParts (plural)?
      mediaPart = styleSheets[i].media;

And just to be nasty, the IE model has a media property with a type of string, but it's empty. You can get the text of it using:

styleSheets[0].cssText.replace(/\s+/g,' ').match(/@media.+}[^{]+}/)

.

>     // TypeError: mediaPart[1] is undefined
>     dimB = parseInt( mediaPart[1].replace(/[():A-Za-z$-]/g, "") );

Here you seem to be expecting "and" to be in the rule. You haven't provided an example of your rules, but if they are like:

@media screen, print { ... }

then media is a list of the media rules: media[0] is screen and media[1] is print, so you want to iterate over the media rules.

Anyhow, that's enough for now.