Accessing CSS media query rules via JavaScript/DOM - javascript

I've been using a number of libraries (including my own) to dynamically load assets based upon media queries I've outlined in CSS files. For example:
In CSS:
#media screen and (max-width: 480px) {
.foo {
display: none;
}
}
And using an asset loader; require.js, modernizr.js etc or using window.matchMedia and associated addListener() functions:
if (function("screen and (max-width: 480px)")){
// Load several files
load(['mobile.js','mobile.css']);
}
Declaring them twice is awkward/silly and as far as I can find, all JS helper libraries and asset loaders require you to repeat the media queries rather than locating them programmatically from JS/DOM.
So, I've been exploring the ability to access the values programmatically via document.stylesheets, but I'm not sure if they're accessible and there seems very little documentation to suggest they are.
The furthest I've got is looking for CSSMediaRule and using console.dir(document.stylesheets) amongst others to explore the stylesheet object.
But no references are made (within document.stylesheets) to the actual media query rules used in CSS - only the classes to be applied as a result of the media queries... What I'm trying to locate, programmatically, is:
"screen and (max-width: 480px)"
Is there any way of accessing such CSS Media query rules via JavaScript/DOM?

For get rules use cross browser variant:
var styleSheet = document.styleSheets[0];
var rules = styleSheet.cssRules || styleSheet.rules; // IE <= 8 use "rules" property
For detect CSSMediaRule object in rules list use (not work in IE <= 8, because "CSSMediaRule" class available only in IE >= 9):
var i = 0;
if (rules[i].type == 4)
{
// Do something
}
Some functions for get styles from current DOM (not works in IE <= 8):
function getCssRulesFromDocumentStyleSheets(media)
{
var resultCssRules = '';
for (var i = 0; i < document.styleSheets.length; i++)
{
var styleSheet = document.styleSheets[i];
if (isRuleFromMedia(styleSheet, media))
resultCssRules += getCssRulesFromRuleList(styleSheet.cssRules || styleSheet.rules, media);
}
return resultCssRules;
}
function getCssRulesFromRuleList(rules, media)
{
var resultCssRules = '';
for (var i = 0; i < rules.length; i++)
{
var rule = rules[i];
if (rule.type == 1) // CSSStyleRule
{
resultCssRules += rule.cssText + "\r\n";
}
else if (rule.type == 3) // CSSImportRule
{
if (isRuleFromMedia(rule, media))
resultCssRules += getCssRulesFromRuleList(rule.styleSheet.cssRules || rule.styleSheet.rules, media);
}
else if (rule.type == 4) // CSSMediaRule
{
if (isRuleFromMedia(rule, media))
resultCssRules += getCssRulesFromRuleList(rule.cssRules || rule.rules, media);
}
}
return resultCssRules;
}
function isRuleFromMedia(ruleOrStyleSheet, media)
{
while (ruleOrStyleSheet)
{
var mediaList = ruleOrStyleSheet.media;
if (mediaList)
{
if (!isMediaListContainsValue(mediaList, media) && !isMediaListContainsValue(mediaList, 'all') && mediaList.length > 0)
return false;
}
ruleOrStyleSheet = ruleOrStyleSheet.ownerRule || ruleOrStyleSheet.parentRule || ruleOrStyleSheet.parentStyleSheet;
}
return true;
}
function isMediaListContainsValue(mediaList, media)
{
media = String(media).toLowerCase();
for (var i = 0; i < mediaList.length; i++)
{
// Access to mediaList by "[index]" notation now work in IE (tested in versions 7, 8, 9)
if (String(mediaList.item(i)).toLowerCase() == media)
return true;
}
return false;
}
Functions usage example:
<style type="text/css">
#media screen and (max-width: 480px) {
p { margin: 10px; }
}
#media screen and (max-width: 500px) {
p { margin: 15px; }
}
#media print {
p { margin: 20px; }
}
</style>
<!-- ... -->
<script type="text/javascript">
alert(getCssRulesFromDocumentStyleSheets('print'));
alert(getCssRulesFromDocumentStyleSheets('screen and (max-width: 480px)'));
// For IE (no space after colon), you can add fix to "isMediaListContainsValue" function
alert(getCssRulesFromDocumentStyleSheets('screen and (max-width:480px)'));
</script>
Here is a JS Fiddle for it: https://jsfiddle.net/luisperezphd/hyentcqc/

This is how I do it:
In css create classes to expose or hide content at various breakpoints.
This is a handy utility anyway. These are already available in Twitter Bootstrap for example.
<style type="text/css">
.visible-sm, .visible-md, .visible-lg{
display:none;
}
#media (max-width: 480px) {
.visible-sm{
display: block;
}
}
#media (min-width: 481px) and (max-width: 960px) {
.visible-md{
display: block;
}
}
#media (min-width: 961px) {
.visible-lg{
display: block;
}
}
</style>
In all your documents add empty spans with these classes.
They won't show up on the page if you keep the spans inline.
<span id="media_test">
<span class="visible-sm"></span>
<span class="visible-md"></span>
<span class="visible-lg"></span>
</span>
Add this short jquery extension to your script file.
This sets a new class in the body tag that matches the current media query.
(function ($) {
$.fn.media_size = function () {
//the default port size
var size = 'lg';
//the sizes used in the css
var sizes = ['sm','md','lg'];
//loop over to find which is not hidden
for (var i = sizes.length - 1; i >= 0; i--) {
if($('#media_test .visible-'+sizes[i]).css("display").indexOf('none') == -1){
size = sizes[i];
break;
};
};
//add a new class to the body tag
$('body').removeClass(sizes.join(' ')).addClass(size);
}
}(jQuery));
$(document).media_size();
Now you have an automatic integration with your css media queries Modernizr style.
You can write javascript (jquery) that is conditional on based on your media queries:
how big is this viewport?
<script type="text/javascript">
$('.sm a').click(function(e){ alert('Media queries say I\'m a small viewport');});
$('.lg a').click(function(e){ alert('Media queries say I\'m a large viewport');});
</script>

Related

JavaScript if statement alternative syntax

I want to check the size of the screen and toggle a class depending on the size of the screen.
Html
<div id="item" class="test"></div>
Script
window.addEventListener("load", toggleClass);
window.addEventListener("resize", toggleClass);
function toggleClass() {
var w = window.innerWidth;
item = document.getElementById("item");
if ( w > 700 ) {
item.classList.remove("test");
}else {
if ( item.classList.contains("test")) {
}else {
item.classList.add("test");
}
}
}
You don't need to test for whether test is included in the classList first - you can just add it unconditionally. Also, avoid implicitly creating global variables - always declare a new variable name with var (or, preferably, const, or let):
function toggleClass() {
var w = window.innerWidth;
var item = document.getElementById("item");
if ( w > 700 ) {
item.classList.remove("test");
}else {
item.classList.add("test");
}
}
You can also use the Conditional (ternary) operator
function toggleClass() {
var item = document.getElementById("item");
(window.innerWidth > 700) ? item.classList.remove("test") : item.classList.add("test");
}
A different approach - rather than adding a class on the smaller size - use a media query to apply the styling that you want - obviously if you are using the class for a purpose other than styling - this approach may not work - but if all you are doing is styling an element based on the width of the screen - then a media query is your friend.
The benefit of this approach (if its purely styling changes you are doing) is that there is no js required - the browser will automatically use whatever styling the media query matches. This is better for performance because the re is no js to run.
#media screen and (max-width: 699px) {
p {
font-size: 14px
}
}
#media screen and (min-width: 700px) {
p {
font-size: 16px
}
}

How can I force a matching window.matchMedia to execute on page load?

I noticed a difference between css media query definition and the javascript window.matchMedia media query definition:
The css rules are apllied initially to a loaded page.
The rules defined in javascript are not executed after the page load, but only after a new condition is entered.
An example:
I have two different pages with equivalent media query definitions, the first one defined in css and the second one defined in javascript:
the css version (defined in a style element):
#media (min-width: 401px) and (max-width: 600px) { body {background-color: red; } }
#media (min-width: 601px) and (max-width: 800px) { body {background-color: blue; } }
the javascript version (defined either globally or in a function called after body onload):
window.matchMedia("(min-width: 401px) and (max-width: 600px)")
.addListener(function(e) {
if (e.matches) {
document.body.style.background = "red";
}
});
window.matchMedia("(min-width: 601px) and (max-width: 800px)")
.addListener(function(e) {
if (e.matches) {
document.body.style.background = "blue";
}
});
When I load a page and the window is 700 px wide
the css version page is blue
the javascript version is white and changes its state only after a new condition is met, i.e. the window is sized below 601 px.
How can I force a matching window.matchMedia to execute on page load?
To fire a matchMedia on load, you could do like this instead (with a somewhat cleaner code base).
Stack snippet
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>
document.addEventListener("DOMContentLoaded", function(e) {
// medias (as an array to make it a little easier to manage)
var mqls = [
window.matchMedia("(max-width: 400px)"),
window.matchMedia("(min-width: 401px) and (max-width: 600px)"),
window.matchMedia("(min-width: 601px) and (max-width: 800px)"),
window.matchMedia("(min-width: 801px)")
]
// event listeners
for (var i=0; i<mqls.length; i++){
mqls[i].addListener(mqh)
}
// matches methods
function mqh(){
if (mqls[0].matches) {
console.log("CALLBACK (max-width: 400px)");
document.body.style.background = "green";
} else if (mqls[1].matches) {
console.log("CALLBACK (max-width: 600px)");
document.body.style.background = "red";
} else if (mqls[2].matches) {
console.log("CALLBACK (max-width: 800px)");
document.body.style.background = "blue";
} else if (mqls[3].matches) {
console.log("CALLBACK (min-width: 801px)");
document.body.style.background = "gray";
}
console.log("window.innerWidth: " + window.innerWidth);
}
// call once on load
mqh();
});
</script>
</head>
<body>
</body>
</html>
Org. src: http://www.javascriptkit.com/javatutors/matchmediamultiple.shtml
A callback function bound to window.matchMedia is not called on page load.
A solution to the problem would be:
to define a function in which the media queries are explicitly checked via if(window.matchMedia("...").matches){
to call that function on page load via <body onload
to call that function on resize via window.onresize
I was facing the same problem today, and I've used the following solution inspired by Nathan:
const gate = 800
function listener(
matches,
) {
document.getElementById('tag').innerHTML = matches ? 'wider' : 'narrower'
}
window.onload=() => {
const match = window.matchMedia(`(min-width: ${gate}px)`)
match.addListener(e => listener(e.matches))
listener(match.matches)
}
<h1>
This window is
<span id='tag'></span>
than 800px
</h1>
The core concept is to run your listener function once with MediaQueryList.matches passed as a parameter.
And if someone is trying to achieve this with a framework, do remember to register and trigger the listener during the component mount event.

Javascript: Removing media query from external css file

How could I remove media query loaded from external css file (<link rel="stylesheet" type="text/css" href="XXXX.css" media="screen">)? Note that I cant disable the entire link tag because other important styles are included out of that media query:
body{...}
.container{...}
...
#media(min-width:XXXpx) {
....
}
...
Thank you!
I strongly recommend pure CSS solution for this problem, such as defining a new stylesheet overwritting rules you don't want.
#selector {
color: #000;
}
.some-classs-you-want-to-reserve {/*..*/}
#media only screen and (min-width: 600px) {
/* unwanted */
#selector {
display: none;
}
}
For example, if you want to mute rules inside #media only screen and (min-width: 600px), simply add a new stylesheet overwritting them to default values:
#media only screen and (min-width: 600px) {
/* unwanted */
#selector {
display: block;
}
}
If you insist on using Javascript, you can access and modify css rules by iterating document.styleSheets.
https://developer.mozilla.org/en-US/docs/Web/API/StyleSheetList
https://developer.mozilla.org/en-US/docs/Web/API/document.styleSheets
This is an instance of StyleSheetList, an array-like object. Each item of it represents an external or inline css stylesheet. All rules including media queries can be found inside it.
A brief tree structure:
- document.styleSheets
|- CSSStyleSheet
|- cssRules
|- media
So here's an example showing how to remove all rules defined inside #media only screen and (min-width: 600px) block:
function removeRule() {
if(typeof window.CSSMediaRule !== "function")
return false; //Your browser doesn't support media query feature
var s = document.styleSheets, r,
i, j, k;
if(!s) return false; //no style sheets found
// walk throuth css sheets
for(i=0; i<s.length; i++) {
// get all rules
r = s[i].cssRules;
if(!r) continue;
for(j=0; j<r.length; j++) {
//If there's a rule for media query
if(r[j] instanceof CSSMediaRule &&
r[j].media.mediaText == "only screen and (min-width: 600px)") {
for(k=0; k<r[j].cssRules.length; k++) {
// remove all rules of it
r[j].deleteRule(r[j].cssRules[k]);
}
return true;
}
}
}
}
removeRule();
Or live fiddle: http://jsfiddle.net/e4h41qm2/
The fact is, if you delete one media query, another one get's activated. So you have to do a loop till no media query is found:
function removeRule() {
if (typeof window.CSSMediaRule !== 'function') {
return false;
}
var styleSheets = document.styleSheets;
var number = 0;
if (!styleSheets) {
return false;
}
for (i = 0; i < styleSheets.length; i++) {
var styleSheet = styleSheets[i];
var rules = styleSheet.cssRules;
if (!rules) {
continue;
}
for (var j = 0; j < rules.length; j++) {
var cssText = rules[j].cssText;
if (cssText.indexOf('#media') === 0) {
number++;
styleSheet.deleteRule(j);
}
}
}
if (number) {
return number;
}
return 0;
}
function removeMediaQueries() {
var num = removeRule();
var total = num;
while (num) {
num = removeRule();
total += num;
}
if (total) {
console.info(total + ' media quer' + (total == 1 ? 'y' : 'ies' + ' removed'));
} else {
console.info('No media queries removed');
}
}
removeMediaQueries();
If you put all this in one line you can generate a bookmark in your browser and have a fast way to test the design without media queries. Very useful for newsletter.
javascript:!function(){function e(){if("function"!=typeof window.CSSMediaRule)return!1;var e=document.styleSheets,n=0;if(!e)return!1;for(i=0;i<e.length;i++){var o=e[i],r=o.cssRules;if(r)for(var t=0;t<r.length;t++){var f=r[t].cssText;0===f.indexOf("#media")&&(n++,o.deleteRule(t))}}return n?n:0}function n(){for(var i=e(),n=i;i;)i=e(),n+=i;n?console.info(n+" media quer"+(1==n?"y":"ies removed")):console.info("No media queries removed")}n()}();
You're probably better off overriding it instead of trying to delete it, but if you must:
style = document.styleSheets[0]; // or whatever stylesheet you want
[].forEach.call(style.cssRules || [], function(rule, i) {
if (!rule.cssText.indexOf('#media(min-width:XXXpx') {
style.deleteRule(i);
}
});
Untested.

How to change an image from the DOM in a particular browser width without using media-queries

I have an img tag in the DOM.
<div class="some-div">
<img src="https://cdn0.iconfinder.com/data/icons/social-networks-and-media-flat-icons/133/Social_Media_Socialmedia_network_share_socialnetwork_network-09-128.png">
</div>
Now when the browser width become 768px (actually for the tab,smartphone) I need to change the src of the img tag. That means simply the image will be changed to a another one. Example:-
<div class="some-div">
<img src="https://cdn0.iconfinder.com/data/icons/social-networks-and-media-flat-icons/133/Social_Media_Socialmedia_network_share_socialnetwork_network-09-128.png">
</div>
Remember I cant use the background-image property in the css for some reason here so would not be able to write media-queries like this #media only screen and (max-width: 768px)
I need to change it via JS or anyhow. Can you help? Thanks a ton.
Remember I cant use the background-image property in the css for some reason here so would not be able to write media-queries like this #media only screen and (max-width: 768px)
If it's specifically that you can't use background-image, but you can use media queries, then:
#media (min-width: 769px) {
img.small {
display: none;
}
}
#media (max-width: 768px) {
img.big {
display: none;
}
}
...and in your markup:
<img class="big" src="/path/to/big/image.png">
<img class="small" src="/path/to/small/image.png">
If you can't use media queries at all, then:
<img data-big="/path/to/big/image.png" data-small="/path/to/small/image.png">
and
(function() {
var lastSizeAttr = null;
var list = document.getElementsByTagName("img"); // the list is live
var resizeTimer = 0;
window.addEventListener("resize", maybeResize);
function maybeResize() {
if (resizeTimer) {
clearTimeout(resizeTimer);
}
resizeTimer = setTimeout(imageResize, 100); // Wait 100ms
}
function imageResize() {
var attr = favorite_width_prop_here < 769 ? "data-small" : "data-big";
var img;
var n;
resizeTimer = 0;
if (attr !== lastSizeAttr) {
lastSizeAttr = attr;
for (n = 0; n < list.length; ++n) {
img = list[n];
img.src = img.getAttribute(attr);
}
}
}
})();
...or similar at the end of your HTML (so the elements are there to be found by getElementsByTagName). Note the favorite_width_prop_here, I don't immediately recall which property to use if you're not using jQuery. :-)
You need to calculate the width of window size.
function calculateWidth(){
if(window.outerWidth <= 768){
var imgTag = document.getElementElementById('someImage');
imgTag.src = "imageForTab.jpg"; //here your small image would be for tab
}
}
//attaching the function on window resize
window.addEventListener('resize', calculateWidth);
<body onload="calculateWidth()"> //calling the function on body onload
Pleae put id on your image tag.
<img id="someImage" src="https.....
This is handeling only the image which is given with this id ('someImage').

Get CSS style sheet property using Javascript (and media query)

Generally, when I'm not using JQuery, I use the following utility function to get properties from a stylesheet using Javascript. This works nicely to get values from CSS style sheets:
<!DOCTYPE html>
<html>
<head>
<style>
.foo {
color: red;
}
</style>
<script type = "text/javascript">
function getcss(selector, property)
{
var len = document.styleSheets.length;
for (var idx = 0; idx < len; ++idx)
{
var sheet = document.styleSheets && document.styleSheets[idx];
if (sheet)
{
var r = sheet.rules ? sheet.rules : sheet.cssRules;
if (r)
{
var i = r.length;
while (i--)
{
if (r[i].selectorText && r[i].selectorText.toLowerCase() === selector.toLowerCase())
{
return (r[i].style[property]);
}
}
}
}
}
return null;
}
function exec()
{
alert(getcss(".foo", "color"));
}
</script>
</head>
<body onload = "exec()">
</body>
</html>
The problem is that it doesn't take into account media queries. If I replace the <style> section of the above code with:
.foo {
color: red;
}
#media screen and (max-width: 600px) {
.foo {
color: green;
}
}
...it still outputs red if I shrink the window to smaller than 600px.
Is this correct WC3 behavior? Or is this a browser bug? (I'm testing with Firefox 12 on Ubuntu).
Is there anyway to correct this, so that Javascript "sees" the correct style sheet as required by the media query?
Your code is simply looking through the rules of a stylesheet, and only the first list of rules at that. The rules are a object representation of the stylesheet -- they don't change just because you've resized your browser.
In your code you're never going to see the green rul because you're only iterating over the items in the first CSSRuleList. The trick here is you need to recursively loop over any additional rules that are CSSMediaRule and contain their own CSSRuleList. For you, the sheet.rules contains both a single CSSSyleRule (which is your .foo { color:red; }) and a CSSMediaRule (which is your media query). That second rule then its own CSSRuleList, which you can traverse to find your green color.
In this case, here's where your data lies:
// Assuming sheet 0 is your stylesheet above
var sheet = document.styleSheets[0];
// First rule is ".foo { color: red; }"
console.log(sheet.cssRules[0].cssText);
// Second Rule is your "#media" and its first rule is ".foo { color: green; }"
console.log(sheet.cssRules[1].cssRules[0].cssText);

Categories