JavaScript >> Javascript 文檔 >  >> Tags >> CSS

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的引用,這裡的監聽器就會繼續被調用 對象。

更多關於聽眾

由於我的誤解,我在上一篇文章中對媒體查詢偵聽器的初始描述不完整。監聽器實際上是在兩種情況下觸發的:

  1. 媒體查詢最初生效時。因此,在前面的示例中,當屏幕寬度變為 600 像素或更小時。
  2. 當媒體查詢最初變得無效時。例如,當屏幕寬度超過 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 中。

參考

  1. JavaScript 中的 CSS 媒體查詢,我的第 1 部分
  2. Rob Flaherty 的推文
  3. matchMedia() MediaQueryList 未更新
  4. matchMedia() 監聽器丟失
  5. Paul Irish 的 matchMedia polyfill
  6. 由 Paul Hayes 編寫的 matchMedia polyfill
  7. 我的 YUI 3 Gallery 媒體模塊

Tutorial JavaScript 教程
  1. 你好!

  2. Javascript 中的錯誤處理

  3. 學習使用 Dogs 應用構建 React 項目 [⚛ + 🐶]

  4. 你為什麼使用 React?

  5. 關於 Vue 中的過濾器你應該知道的一切

  6. 如何向 <input> 添加“只讀”屬性?

  7. 將 CLI 項目發佈到 NPM

  1. 面向初學者的無頭 CMS

  2. JavaScript 入門 - 第 3 章 🚀

  3. JS Var vs Let 區別

  4. Firefox 的天氣選項卡

  5. 將您的節點後端連接到 postgresql 數據庫

  6. 使用 Grid.js 創建下一代 HTML 表格。 😎

  7. 如何:mobx-state-tree + react + typescript

  1. 使用 EnjoyHint 創建提示

  2. 為什麼需要應用程序性能監控工具

  3. 如何使用 Node.js 和 Redis 緩存來加速 HTTP 響應

  4. 在 ReactJS 中實現無限滾動