JavaScript 中的 CSS 媒體查詢,第 2 部分
在我之前的帖子中
1
,我通過自定義實現和使用 CSSOM 視圖 matchMedia()
介紹了在 JavaScript 中使用 CSS 媒體查詢 方法。媒體查詢在 CSS 和 JavaScript 中都非常有用,因此我繼續研究以了解如何最好地利用此功能。事實證明,matchMedia()
方法有一些有趣的怪癖,我在寫本系列的第一部分時沒有意識到。
matchMedia()
及其怪癖
回想一下 matchMedia()
返回一個 MediaQueryList
允許您確定給定媒體類型是否與瀏覽器的當前狀態匹配的對象。這是使用 matches
完成的 屬性,它返回一個布爾值。事實證明,matches
是一個getter,每次調用都會查詢瀏覽器的狀態:
var mql = window.matchMedia("screen and (max-width:600px)");
console.log(mql.matches);
//resize the browser
console.log(mql.matches); //requeries
這實際上非常有用,因為它允許您保留對 MediaQueryList
的引用 對象並針對頁面反複檢查查詢的狀態。
不過,Chrome 和 Safari 有一個奇怪的行為。 matches
的初始值 總是正確的,但默認情況下不會更新,除非頁面具有使用相同查詢和至少一個規則定義的媒體塊(帽子提示:Rob Flaherty
2
.例如,為了獲得 MediaQueryList
表示“屏幕和(最大寬度:600px)”要適當地更新(包括觸發事件),你的 CSS 中必須有這樣的東西:
@media screen and (max-width:600px) {
.foo { }
}
媒體塊中至少需要有一個規則,但該規則是否為空並不重要。只要這存在於頁面上,那麼 MediaQueryList
將適當更新並通過 addListener()
添加任何偵聽器 會在適當的時候觸發。如果頁面上沒有此媒體塊,MediaQueryList
就像頁面創建時的狀態快照一樣。
3
您可以通過使用 JavaScript 添加新規則來解決此問題:
var style = document.createElement("style");
style.appendChild(document.createTextNode("@media screen and (max-width:600px) { .foo {} }"));
document.head.appendChild(style); //WebKit supports document.head
當然,您需要為使用 matchMedia()
訪問的每個媒體查詢執行此操作 ,這有點痛苦。
Firefox 的實現還有一個奇怪的怪癖。理論上,您應該能夠為查詢狀態更改時分配一個處理程序,而不是保留對 MediaQueryList
的引用 對象,如:
//doesn't quite work in Firefox
window.matchMedia("screen and (max-width:600px)").addListener(function(mql) {
console.log("Changed!");
});
當在 Firefox 中使用此模式時,即使媒體查詢已變得有效,也可能永遠不會真正調用偵聽器。在我的測試中,它會觸發 0 到 3 次,然後再也不會觸發。 Firefox 團隊已經承認這是一個錯誤
4
並有望盡快修復。同時,您需要保留 MediaQueryList
參考周圍以確保您的聽眾觸發:
//fix for Firefox
var mql = window.matchMedia("screen and (max-width:600px)");
mql.addListener(function(mql) {
console.log("Changed!");
});
只要有對mql
的引用,這裡的監聽器就會繼續被調用 對象。
更多關於聽眾
由於我的誤解,我在上一篇文章中對媒體查詢偵聽器的初始描述不完整。監聽器實際上是在兩種情況下觸發的:
- 媒體查詢最初生效時。因此,在前面的示例中,當屏幕寬度變為 600 像素或更小時。
- 當媒體查詢最初變得無效時。例如,當屏幕寬度超過 600 像素時。
這種行為是 MediaQueryList
的原因 對像被傳入監聽器,所以你可以檢查 matches
確定媒體查詢是否剛剛生效。例如:
mql.addListener(function(mql) {
if (mql.matches) {
console.log("Matches now!");
} else {
console.log("Doesn't match now!");
}
});
使用這樣的代碼,您可以監控 Web 應用程序何時進入和退出某些狀態,從而允許您相應地更改行為。
是否要polyfill?
當我第一次看到 matchMedia()
,我這樣做是為了創建一個 polyfill。保羅愛爾蘭人
5
使用與我在上一篇文章中描述的技術相似的技術實現了一個 polyfill(並為此歸功於我,感謝 Paul!)。 Paul Hayes 然後分叉
6
他的工作是基於非常巧妙地使用 CSS 轉換來檢測變化來創建具有基本偵聽器支持的 polyfill。但是,由於它依賴於 CSS 過渡,因此偵聽器支持僅限於支持 CSS 過渡的瀏覽器。再加上調用 matches
不會重新查詢瀏覽器狀態,而且 Firefox 和 WebKit 中的錯誤讓我相信構建 polyfill 不是正確的方法。畢竟,當實際實現中存在如此明顯的錯誤需要修復時,如何適當地填充呢?
我的方法是創建一個外觀來將這種行為包裝在一個 API 中,以便我可以解決這些問題。當然,我選擇將 API 實現為 YUI Gallery 模塊
7
稱為 gallery-media
. API 非常簡單,由兩種方法組成。第一個是Y.Media.matches()
,它接受一個媒體查詢字符串,如果媒體匹配則返回 true,否則返回 false。無需跟踪任何對象,只需獲取信息:
var matches = Y.Media.matches("screen and (max-width:600px)");
第二種方法是Y.Media.on()
,它允許您指定媒體查詢和在媒體查詢變為有效或無效時調用的偵聽器。監聽器被傳遞一個帶有 matches
的對象 和 media
屬性為您提供有關媒體查詢的信息。例如:
var handle = Y.Media.on("screen and (max-width:600px)", function(mq) {
console.log(mq.media + ":" + mq.matches);
});
//detach later
handle.detach();
我沒有使用 CSS 過渡來監視更改,而是使用了一個簡單的 onresize
事件處理程序。在桌面上,瀏覽器窗口的大小是會改變的主要因素(與移動設備相反,移動設備的方向也可能改變),所以我對舊版瀏覽器做了這個簡化的假設。 API 使用原生 matchMedia()
可用的功能並修補 WebKit 和 Chrome 中的差異,以便您獲得一致的行為。
結論
JavaScript 中的 CSS 媒體查詢比我最初預期的要復雜一些,但仍然非常有用。我認為 polyfill matchMedia()
不合適 給出仍然比比皆是的奇怪錯誤,有效地阻止您甚至以相同的方式跨瀏覽器使用本機代碼。另一方面,立面將您與未來可能發生的錯誤和變化隔離開來。現在繼續使用 CSS 媒體查詢來發揮其潛力……在 JavaScript 中。
參考
- JavaScript 中的 CSS 媒體查詢,我的第 1 部分
- Rob Flaherty 的推文
- matchMedia() MediaQueryList 未更新
- matchMedia() 監聽器丟失
- Paul Irish 的 matchMedia polyfill
- 由 Paul Hayes 編寫的 matchMedia polyfill
- 我的 YUI 3 Gallery 媒體模塊