w3ctech

【译】CSS media queries在JavaScript中的应用(一)

原文地址:http://www.nczonline.net/blog/2012/01/03/css-media-queries-in-javascript-part-1/

本文由小豪翻译

在2011年初,我参与了一个关于JavaScript特性检测的项目。一些问题的出现让我觉得使用CSS media query或许会更好,所以我花了一些时间编写一个使用JavaScript操作CSS media queries的函数。我的思路很简单:如果我只需要基于media query就可以控制特定的CSS,那么,我也可以基于media query执行特定的JavaScript。

var isMedia = (function(){
 var div;
 return function(query){
 //如果<div>不存在, 则创建一个并让其隐藏
 if (!div){
 div = document.createElement("div");
 div.id = "ncz1";
 div.style.cssText = "position:absolute;top:-1000px";
 document.body.insertBefore(div, document.body.firstChild);
 }
 div.innerHTML = "_<style media=\"" + query + "\"> #ncz1 { width: 1px; }</style>";
 div.removeChild(div.firstChild);
 return div.offsetWidth == 1;
 };
})();

这个函数的思路很简单。我创建了一个节点,在里面设置了media属性来对应我正在测试的样式。这个样式会应用在标签上,我需要做的就是检查这个样式是否已经应用到对应的节点。我觉得应该避免一些浏览器探测,所以我没有使用currentStyle或者getComputedStyle(),而是选择改变一个元素的宽度,然后用offsetWidth来检查这个改变。

很快,我们就创建了一个函数,并且它可以在几乎所有的浏览器中运行。和你猜的一样,在IE6和IE7上会有一些问题。在这两个个浏览器上,元素是一个NoScope元素.aspx)(比如文本节点,注释节点或者script)。NoScope意味着当页面受到innerHTML或者其他类似的操作注入时,会发生一些可怕的事情。如果NoScope元素是作为HTML字符串的最前面部分被添加的,那么所有这些元素都会被丢弃。为了能正常使用NoScope元素,你必须确定这个元素不是HTML字符串的第一部分。因此,我在元素签名增加了一条下划线,然后再将其删除,这样就能保证在IE6和IE7下保持正常。其他浏览地没有这个NoScope问题,但是使用这个技术并不是一个完美的方案(我之前说过我尽量要避免浏览器探测)。

最后,你可以这样使用这个函数:

if (isMedia("screen and (max-width:800px)"){
 //在screen模式下这样使用
}
if (isMedia("all and (orientation:portrait)")){
 //portrait模式下
}

isMedia()函数在所有浏览器里都工作地很好,它可以很好地检测media query在浏览器中的有效性。也就是说传入一个无效的media query会返回一个false。比如在IE6下,传入"screen"会返回true,但是更复杂的参数传入的话会返回false。这个结果是可以接收的,因为其他没有匹配到的media query中的任意CSS样式都不应该在对应的浏览器中应用。

CSSOM View

CSS Object Model(CSSOM)Views规范增加了对JavaScript操作CSS media query的原生支持,它在window对象下增加了matchMedia()方法。你可以传入一个CSS media query然后返回一个MediaQueryList对象。这个对象包括两个属性:matches,布尔值数据,表示CSS media query是否与当前的显示状态匹配;media对应传入的参数字符串。例如:

var match = window.matchMedia("screen and (max-width:800px)");
console.log(match.media); //"screen and (max-width:800px)"
console.log(match.matches); //true or false

到这里,API没有提供的支持和我之前的想法差不多。你或许会考虑为什么matchMedia()会返回一个对象?毕竟,如果没有匹配成功,返回的对象也毫无用处。答案就在addListener()和removeListener()上。

这两个方法允许你基于CSS media query与视图状态进行交互。例如,当模式转换到“portrait”模式时,或许你会想要有一个提示。你可以这么做:

var match = window.matchMedia("(orientation:portrait)");
match.addListener(function(match){
 if (match.media == "(orientation:portrait)") {
 //do something
 }
});

这段代码对media query进行了监听。当query值和当前的视图状态对应时,监听器对应的函数就会执行,而对应的MediaQueryList对象也会传入。用这个方式吗,你可以让你的JavaScript可以很快地响应布局变化,并且不需要用轮询的方式。那么,这里就和我的想法不太一样了,这个API允许你监听视图状态的改变,并响应调整界面的行为。

matchMeida()方法在Chrome,Safari5.1+,FireFox 9+和IOS 5+上的Safari上都已生效。这些是我了解并已核实的浏览器。IE和Opera依然没有在最近的几个版本中实现这个方法。(注:原文编写时为12年,现在IE10+,及Opera 27+已经实现,http://caniuse.com/#search=matchMedia )

注意:WebKit上的实现有一个BUG,当MediaQueryList对象创建后,matches不会更新,query监听器也不会触发。希望这个问题能早点修复。

总结

CSS media query带来了一个在CSS和JavaScript中都适用的特性检测语法。我期望media query可以在未来成为JavaScript代码中的重要部分,在界面变化发生时,开发者能检测得到。实在没有理由说web应用的行为不应该响应布局的变化,而CSS media让我们变得更加强大。

参考

w3ctech微信

扫码关注w3ctech微信公众号

共收到2条回复

  • 干货

    回复此楼
  • 快速复习下

    回复此楼