w3ctech

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

原文链接:http://www.nczonline.net/blog/2012/01/19/css-media-queries-in-javascript-part-2/

本文由小豪翻译

在我的前一篇文章中,我介绍了CSS media query在JavaScript中的应用,包括一个自制的实现函数和CSSOM Views里的matchMedia()方法。在CSS和JavaScript中,Media query都是那么实用,以至于我继续对其进行了一些研究,探索更好的利用方式。最后我发现,matchMedia()方法还有一些我在写第一篇文章时没有意识到的有趣特性。

matchMedia()和它的有趣特性

再次调用matchMedia()会返回一个MediaQueryList对象,这个对象可以让你确定给出的media类型是否和浏览器当前状态匹配。MediaQuery对象的matches属性会使用一个布尔型数据来表明匹配的结果。很明显,在每一次调用marches属性的时候,它都会获取一次浏览器的状态:

var mql = window.matchMedia("screen and (max-width:600px)");
console.log(mql.matches);
//resize the browser
console.log(mql.matches); //requeries

这显然非常实用,因为这样你就可以保持对MediaQueryList对象的引用,从而可以重复检查query与页面之间的状态。

Chrome和safari还有一个怪异的行为。即使matches属性的初始值是正确的,默认情况下还是不会更新matches的值,除非页面含有一个对应相同的query和至少一个规则定义的media块。比如说,为了让一个表现形式为”“screen and (max-width:600px)““的MediaQueryList对象正确显示出来(包括能正确触发事件),你必须在你的CSS中包含一些内容:

@media screen and (max-width:600px) {
 .foo { }
}

media块中必须含有至少一个CSS规则,但是该规则是可以为空的。只要这个规则存在于页面上,那么MediaQueryList对象就可以正确更新,并且所有通过addListener()绑定的事件都可以正确触发。

你可以使用JavaScript来修复这个问题:

var style = document.createElement("style");
style.appendChild(document.createTextNode("@media screen and (max-width:600px) { .foo {} }"));
document.head.appendChild(style); //WebKit支持document.head

当然你或许会需要为每一个使用matchMedia()访问的media query应用这个修复,这可能会显得有些繁杂。

Firefox中的实现也有一些怪异的特性。理论上,你可以注册一个处理器监听query状态的改变,这样你就不需要一直保持对MediaQUeryList对象的引用了,如下:

//在Firefox中无效
window.matchMedia("screen and (max-width:600px)").addListener(function(mql) {
 console.log("Changed!");
});

在Firefox中,即使media query是有效的,监听器或许仍然不会被调用。在我的测试中,它可能会被触发0到3次,然后再也不会被触发。Firefox团队已经了解了这个bug,并且应该很快会修复这个问题。所以,在使用这个方案的同时,你还是需要继续引用MediaQueryList对象来确保监听器被触发了:

//fix for Firefox
var mql = window.matchMedia("screen and (max-width:600px)");
mql.addListener(function(mql) {
 console.log("Changed!");
});

因为mql的存在,监听器就一直可以被触发。

监听器相关的更多内

在我的上一篇文章中,由于对一部分内容的误解,我对media query的描述有一些错误。监听器会在两个情景下被触发:

  1. media query一开始是有效的。就像前一个例子中,屏幕宽度变成600px或者更低。
  2. media query一开始是无效的。例如屏幕宽度大于600px。

这就是为什么需要将MediaQueryList对象传入到监听器中,这样你可以通过检查matches属性来确定media query是否有效。例如:

mql.addListener(function(mql) {
 if (mql.matches) {
 console.log("Matches now!");
 } else {
 console.log("Doesn't match now!");
 }
});

通过这样的代码,你就可以监控一个web应用的状态,从而可以做一些适当的调整。

是否需要对matchMedia()进行模拟(不支持的浏览器中)

在我在了解matchMedia()的时候,我试图创建一个函数模拟matchMedia()(原文称为polyfill,作为一种不支持matchMedia()方法情况下的替代措施)。Paul Irish使用类似我最近一篇文章里描述的技术实现了一个polyfill。Paul Hayes之后开设了一个分支来创建polyfill,使用了一个基于简单CSS transition的监听器来检测改变。然而,由于它依赖于CSS transitions,监听器的兼容性受到CSS transition兼容性的限制。加上调用matches不能再次获取浏览器状态与Firefox和webkit中的bug,让我坚信创建一个polyfill并不是一个正确的方向。毕竟,当真实环境中存在那么多明显的bug时,polyfill又怎么能正确执行。

我选择的方式是创建一个容器来包裹API的行为,从容器处消除这些问题。当然,我选择将这个API作为YUI Gallery的一个模块实现,叫做gallery-media。这个API很简单,并由两个方法组成。第一个是Y.Media.matches(),调用media query字符串并返回匹配结果。不需要记录任何对象,只需要直接这样获取:

var matches = Y.Media.matches("screen and (max-width:600px)");

第二个方法是Y.Media.on(),它允许你来指定一个media query和一个监听器,当media query生效或者失效时执行。监听器的参数是一个含有matches和media属性的对象,提供media query的信息。例如:

var handle = Y.Media.on("screen and (max-width:600px)", function(mq) {
 console.log(mq.media + ":" + mq.matches);
});
//detach later
handle.detach();

取代使用CSS transitions来监控改变,我使用了一个简单的onresize事件处理器。在桌面系统中,浏览器的尺寸是最有可能被改变的(在移动设备中,方向也有可能被该改变),那么我为旧的浏览器做了这么一个假设。API使用了原生的matchMedia()函数,在WebKit和Chrome中都有效并进行了一些修复,从而可以得到稳定的行为。

总结

JavaScript中的CSS media query比我想象的复杂一些,但是非常实用。我不觉得通过polyfill的方式修复marchMedia()是一个好主意,它让你不能在多个浏览器中使用相同的方式编码。好处是,它将你从bug和改变中隔离出来,从而看起来会更加先进一些。现在让我们使用JavaScript和CSS media query创造更多的可能。

参考

  1. CSS media queries in JavaScript, Part 1
  2. Rob Flaherty’s tweet
  3. matchMedia() MediaQueryList not updating
  4. matchMedia() listeners lost
  5. matchMedia polyfill
  6. matchMedia polyfill
  7. YUI 3 Gallery Media module)

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

w3ctech微信

扫码关注w3ctech微信公众号

共收到0条回复