在JavaScript中使用媒體查詢的教程

在JavaScript中使用媒體查詢的教程

大多數現代網站使用響應性網頁設計技術,以確保其外觀良好、可讀性好,並在任何螢幕大小的裝置上保持可用性,如手機、平板電腦、膝上型電腦、臺式電腦顯示器、電視、投影儀等。

使用這些技術的站點有一個模板,該模板根據螢幕尺寸修改佈局:

較小的螢幕通常顯示線性、單列檢視,其中通過單擊(漢堡)圖示啟用選單等UI控制元件。

更大的螢幕顯示更多資訊,可能帶有水平對齊的側欄。UI控制元件(如選單項)可能始終可見,以便於訪問。

響應式web設計的一個重要部分是實現CSS或JavaScript媒體查詢,以檢測裝置大小並自動提供適合該大小的設計。我們將討論為什麼這些查詢很重要以及如何使用它們,但首先,讓我們討論一般的響應式設計。

  1. 為什麼響應式設計很重要?
  2. 響應式設計如何工作?
  3. 媒體查詢最佳實踐
  4. 媒體查詢備選方案
  5. 您是否需要JavaScript中的媒體查詢?
  6. 選項 1:監視視場維度
  7. 選項 2:定義和監視CSS自定義屬性(可變)
  8. 選項 3: 使用匹配媒體API

為什麼響應式設計很重要?

不可能只提供一個頁面佈局,而期望它在任何地方都能工作。

當手機在21世紀初首次獲得基本的web訪問時,網站所有者通常會建立兩到三個單獨的頁面模板,鬆散地基於手機和桌面檢視。隨著裝置種類呈指數級增長,這種做法變得越來越不切實際。

今天,有許多螢幕尺寸,從微型手錶顯示器到巨大的8K顯示器甚至更大。即使你只考慮手機,最近的裝置可以比許多低端膝上型電腦具有更高的解析度。

移動裝置的使用量也超過了臺式電腦。除非你的網站有一組特定的使用者,否則你可以期望大多數人通過智慧手機訪問它。儘管大多數網頁設計師、開發人員和客戶繼續使用標準PC機,但小螢幕裝置已不再是後知後覺,應該從一開始就加以考慮。

谷歌已經認識到移動裝置的重要性。當網站在智慧手機上可用且表現良好時,在谷歌搜尋中排名會更好。好的內容仍然至關重要,但載入速度慢、無法適應使用者群螢幕尺寸的網站可能會損害您的業務。

最後,考慮可訪問性。一個適合所有人的網站,無論他們使用什麼裝置,都會吸引更多的觀眾。可訪問性是許多國家的法律要求,但即使不是你所在的地方,也要考慮到更多的觀眾會帶來更多的轉換和更高的盈利能力。

響應式設計如何工作?

響應式設計的基礎是媒體查詢:一種CSS技術,可以根據輸出型別(螢幕、印表機甚至語音)、螢幕尺寸、顯示縱橫比、裝置方向、顏色深度和指標精度等指標應用樣式。媒體查詢還可以考慮使用者偏好,包括減少動畫、明暗模式和更高對比度。

我們展示的示例演示了僅使用螢幕寬度的媒體查詢,但是站點可以更加靈活。有關詳細資訊,請參閱MDN上的全套選項

媒體查詢支援非常出色,已經在瀏覽器中使用了十多年。只有IE8及以下版本沒有支援。它們忽略了媒體查詢應用的樣式,但這有時會帶來好處(請參閱下面的最佳實踐部分)。

使用媒體查詢應用樣式有三種標準方法。第一個載入HTML程式碼中的特定樣式表。例如,當裝置的螢幕寬度至少為800畫素時,以下標記將載入wide.css樣式表:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<link rel="stylesheet" media="screen and (min-width: 800px)" href="wide.css" />
<link rel="stylesheet" media="screen and (min-width: 800px)" href="wide.css" />
<link rel="stylesheet" media="screen and (min-width: 800px)" href="wide.css" />

其次,樣式表可以使用@import規則有條件地載入到CSS檔案中:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
/* main.css */
@import url('wide.css') screen and (min-width: 800px);
/* main.css */ @import url('wide.css') screen and (min-width: 800px);
/* main.css */
@import url('wide.css') screen and (min-width: 800px);

請注意,應避免使用@import,因為每個匯入的CSS檔案都是渲染塊。HTML的<link>標記是並行下載的,而@import則是序列下載檔案。

更典型的是,您將使用@media CSS規則塊在樣式表中應用媒體查詢,該規則塊修改特定的樣式。例如:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
/* default styles */
main {
width: 400px;
}
/* styles applied when screen has a width of at least 800px */
@media screen and (min-width: 800px) {
main {
width: 760px;
}
}
/* default styles */ main { width: 400px; } /* styles applied when screen has a width of at least 800px */ @media screen and (min-width: 800px) { main { width: 760px; } }
/* default styles */
main {
  width: 400px;
}

/* styles applied when screen has a width of at least 800px */
@media screen and (min-width: 800px) {
  main {
    width: 760px;
  }
}

開發人員可以應用任何必要的媒體查詢規則來調整站點的佈局。

媒體查詢最佳實踐

最初設計媒體查詢時,許多網站選擇了一套固定的佈局。這在概念上更易於設計和編碼,因為它有效地複製了一組有限的頁面模板。例如:

  1. 小於600px的螢幕寬度使用400px寬的移動式佈局。
  2. 600px和999px之間的螢幕寬度使用600px寬的平板電腦佈局。
  3. 大於1000px的螢幕寬度使用1000px寬的桌面式佈局。

這項技術有缺陷。在非常小和非常大的螢幕上的結果可能看起來很差,並且隨著裝置和螢幕尺寸的變化,可能需要CSS維護。

一個更好的選擇是使用帶有斷點的“移動第一流體”設計,該斷點可以在特定大小下調整佈局。本質上,預設佈局使用最簡單的小螢幕樣式,將元素定位線上性垂直塊中。

例如,<article><aside><main>容器內:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
/* default small-screen device */
main {
width: 100%;
}
article, aside {
width: 100%;
padding: 2em;
}
/* default small-screen device */ main { width: 100%; } article, aside { width: 100%; padding: 2em; }
/* default small-screen device */
main {
  width: 100%;
}

article, aside {
  width: 100%;
  padding: 2em;
}

以下是所有瀏覽器的結果-即使是不支援媒體查詢的非常舊的瀏覽器:

不支援媒體查詢的示例螢幕截圖

不支援媒體查詢的示例螢幕截圖。

當支援媒體查詢且螢幕超過特定寬度(例如500px)時,<article><aside>元素可以水平放置。本例使用CSS網格,其中主要內容使用大約三分之二的寬度,次要內容使用剩餘的三分之一:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
/* larger device */
@media (min-width: 500px) {
main {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2em;
}
article, aside {
width: auto;
padding: 0;
}
}
/* larger device */ @media (min-width: 500px) { main { display: grid; grid-template-columns: 2fr 1fr; gap: 2em; } article, aside { width: auto; padding: 0; } }
/* larger device */
@media (min-width: 500px) {
  main {
    display: grid;
    grid-template-columns: 2fr 1fr;
    gap: 2em;
  }

  article, aside {
    width: auto;
    padding: 0;
  }
}

以下是大螢幕上的結果:

支援媒體查詢的示例螢幕截圖

支援媒體查詢的示例螢幕截圖

媒體查詢替代方案

響應性設計也可以在現代CSS中使用更新的屬性實現,這些屬性本質上適應佈局,而無需檢查視口尺寸。選擇包括:

  • calcmin-widthmax-widthmin-heightmax-height和較新的clamp屬性都可以定義尺寸標註,根據已知限制和可用空間調整圖元的大小。
  • 視口單位vwvhvminvmax 可以根據螢幕尺寸分數調整元素的大小。
  • 文字可以顯示在CSS列中,在空間允許的情況下顯示或消失。
  • 可以使用min-contentfit-content 和 max-content 維度,根據元素的子元素大小調整元素的大小。
  • CSS flexbox可以在元素開始超出可用空間時進行換行或不換行。
  • CSS網格元素可以使用比例分數 fr 單位調整大小。repeat CSS函式可以與minmaxauto-fit 和 auto-fill結合使用,以分配可用空間。
  • 新的和(當前)實驗性的CSS容器查詢可以對佈局中元件可用的部分空間作出反應。

這些選項超出了本文的範圍,但它們通常比只能響應螢幕尺寸的粗糙媒體查詢更實用。如果您可以在沒有媒體查詢的情況下實現佈局,那麼隨著時間的推移,它可能會使用更少的程式碼,更高效,並且需要更少的維護。

也就是說,在某些情況下,媒體查詢仍然是唯一可行的佈局選項。當你需要考慮其他螢幕因素時,它們仍然是必不可少的,比如縱橫比、裝置方向、顏色深度、指標精度,或者使用者偏好,比如動畫和光/暗模式。

您是否需要JavaScript中的媒體查詢?

到目前為止,我們主要討論CSS。這是因為大多數佈局問題都可以而且應該單獨用CSS來解決。

但是,在某些情況下,使用JavaScript媒體查詢而不是CSS是可行的,例如:

  • 諸如選單之類的元件在小螢幕和大螢幕上具有不同的功能。
  • 切換縱向/橫向會影響web應用程式的功能。
  • 基於觸控的遊戲必須更改<canvas>佈局或調整控制按鈕。
  • web應用程式遵循使用者偏好,如暗/光模式、減少動畫、觸控粗糙度等。

以下部分演示了在JavaScript中使用媒體查詢或類似於媒體查詢的選項的三種方法。所有示例都返回一個狀態字串,其中:

  • 小檢視=寬度小於400畫素的螢幕;
  • 中檢視=寬度在400到799畫素之間的螢幕;
  • 大檢視=寬度為800畫素或更多的螢幕。

選項 1:監視視場維度

在媒體查詢實施之前的黑暗日子裡,這是唯一的選擇。JavaScript將偵聽瀏覽器“調整大小”事件,使用window.innerWidthwindow.innerHeight(或舊IE中的document.body.clientWidthdocument.body.clientHeight)分析視口尺寸,並做出相應的反應。

此程式碼將計算出的字串輸出到控制檯:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const
screen = {
small: 0,
medium: 400,
large: 800
};
// observe window resize
window.addEventListener('resize', resizeHandler);
// initial call
resizeHandler();
// calculate size
function resizeHandler() {
// get window width
const iw = window.innerWidth;
// determine named size
let size = null;
for (let s in screen) {
if (iw >= screen[s]) size = s;
}
console.log(size);
}
const screen = { small: 0, medium: 400, large: 800 }; // observe window resize window.addEventListener('resize', resizeHandler); // initial call resizeHandler(); // calculate size function resizeHandler() { // get window width const iw = window.innerWidth; // determine named size let size = null; for (let s in screen) { if (iw >= screen[s]) size = s; } console.log(size); }
const
  screen = {
    small: 0,
    medium: 400,
    large: 800
  };

// observe window resize
window.addEventListener('resize', resizeHandler);

// initial call
resizeHandler();

// calculate size
function resizeHandler() {

  // get window width
  const iw = window.innerWidth;
 
  // determine named size
  let size = null;
  for (let s in screen) {
    if (iw >= screen[s]) size = s;
  }

  console.log(size);
}

您可以在此處檢視工作演示。(如果使用桌面瀏覽器,請在新視窗中開啟此連結,以便於調整大小。移動使用者可以旋轉裝置。)

上面的示例在調整瀏覽器大小時檢查視口大小;確定它是小型、中型還是大型;並將其設定為body元素上的類,從而更改背景顏色。

這種方法的優點包括:

  • 它可以在每一個可以執行JavaScript的瀏覽器中執行——甚至是古老的應用程式。
  • 您正在捕獲準確的尺寸,並可以做出相應的反應。

缺點是:

  • 這是一種需要大量程式碼的老技術。
  • 是不是太精確了?你真的需要知道寬度是966px還是967px嗎?
  • 您可能需要手動將維度匹配到相應的CSS媒體查詢。
  • 使用者可以快速調整瀏覽器大小,使處理程式功能每次都再次執行。這會通過限制事件來過載較舊和較慢的瀏覽器。它只能每500毫秒觸發一次。

總之,除非有非常具體和複雜的大小調整要求,否則不要監視視口尺寸。

選項 2:定義和監視CSS自定義屬性(可變)

這是一種稍微不尋常的技術,它在觸發媒體查詢時更改CSS中自定義屬性字串的值。所有現代瀏覽器(但不是IE)都支援自定義屬性。

在下面的示例中,@media程式碼塊內的--screen custom property設定為“small”、“medium”或“large”:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
body {
--screen: "small";
background-color: #cff;
text-align: center;
}
@media (min-width: 400px) {
body {
--screen: "medium";
background-color: #fcf;
}
}
@media (min-width: 800px) {
body {
--screen: "large";
background-color: #ffc;
}
}
body { --screen: "small"; background-color: #cff; text-align: center; } @media (min-width: 400px) { body { --screen: "medium"; background-color: #fcf; } } @media (min-width: 800px) { body { --screen: "large"; background-color: #ffc; } }
body {
  --screen: "small";
  background-color: #cff;
  text-align: center;
}

@media (min-width: 400px) {
 
  body {
    --screen: "medium";
    background-color: #fcf;
  }
 
}

@media (min-width: 800px) {
 
  body {
    --screen: "large";
    background-color: #ffc;
  }
 
}

可以使用偽元素單獨在CSS中輸出該值(但請注意,它必須包含在單引號或雙引號中):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
p::before {
content: var(--screen);
}
p::before { content: var(--screen); }
p::before {
  content: var(--screen);
}

您可以使用JavaScript獲取自定義屬性值:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const screen = getComputedStyle(window.body)
.getPropertyValue('--screen');
const screen = getComputedStyle(window.body) .getPropertyValue('--screen');
const screen = getComputedStyle(window.body)
                 .getPropertyValue('--screen');

但這並不是全部,因為返回值包含CSS中冒號後定義的所有空格和引號字元。字串將為”large”,因此需要稍微整理一下:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// returns small, medium, or large in a string
const screen = getComputedStyle(window.body)
.getPropertyValue('--screen')
.replace(/\W/g, '');
// returns small, medium, or large in a string const screen = getComputedStyle(window.body) .getPropertyValue('--screen') .replace(/\W/g, '');
// returns small, medium, or large in a string
const screen = getComputedStyle(window.body)
                 .getPropertyValue('--screen')
                 .replace(/\W/g, '');

您可以在此處檢視工作演示。(如果使用桌面瀏覽器,請在新視窗中開啟此連結,以便於調整大小。移動使用者可以旋轉裝置。)

該示例每兩秒鐘檢查一次CSS值。它需要一點JavaScript程式碼,但需要輪詢更改—您無法使用CSS自動檢測到自定義屬性值已更改。

也不可能將值寫入偽元素,並使用DOM變異觀察器檢測更改。偽元素不是DOM的“真實”部分!

優點:

  • 這是一種簡單的技術,主要使用CSS並匹配真實的媒體查詢。可以同時修改任何其他CSS屬性。
  • 無需複製或解析JavaScript媒體查詢字串。

主要缺點是無法自動對瀏覽器視口尺寸的更改作出反應。如果使用者將手機從縱向旋轉到橫向,JavaScript永遠不會知道。您可以經常輪詢更改,但這是低效的,並且會導致您在演示中看到的時間延遲。

監視CSS自定義屬性是一種新穎的技術,但只有在以下情況下才實用:

  • 佈局可以固定在頁面最初呈現的位置。一個資訊亭或銷售點終端是可能的,但它們可能有固定的解析度和單一的佈局,因此JavaScript媒體查詢變得無關緊要。
  • 該網站或應用程式已經頻繁執行基於時間的功能,如遊戲動畫。可以同時檢查自定義屬性,以確定是否需要更改佈局。

選項 3: 使用matchMedia API

matchMedia API有點不同尋常,但它允許您實現JavaScript媒體查詢。IE10以上的大多數瀏覽器都支援它。建構函式返回一個MediaQueryList物件,該物件的matches屬性針對其特定的媒體查詢計算為true或false。

當瀏覽器視口寬度為800px或更大時,以下程式碼輸出true:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const mqLarge = window.matchMedia( '(min-width: 800px)' );
console.log( mqLarge.matches );
const mqLarge = window.matchMedia( '(min-width: 800px)' ); console.log( mqLarge.matches );
const mqLarge  = window.matchMedia( '(min-width: 800px)' );
console.log( mqLarge.matches );

“change”事件可應用於MediaQueryList物件。每次matches屬性的狀態更改時都會觸發此操作:它在先前為false(低於800px)後變為true(高於800px),反之亦然。

將MediaQueryList物件作為第一個引數傳遞給接收處理程式函式:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const mqLarge = window.matchMedia( '(min-width: 800px)' );
mqLarge.addEventListener('change', mqHandler);
// media query handler function
function mqHandler(e) {
console.log(
e.matches ? 'large' : 'not large'
);
}
const mqLarge = window.matchMedia( '(min-width: 800px)' ); mqLarge.addEventListener('change', mqHandler); // media query handler function function mqHandler(e) { console.log( e.matches ? 'large' : 'not large' ); }
const mqLarge  = window.matchMedia( '(min-width: 800px)' );
mqLarge.addEventListener('change', mqHandler);

// media query handler function
function mqHandler(e) {
 
  console.log(
    e.matches ? 'large' : 'not large'
  );
 
}

處理程式僅在matches屬性更改時執行。當頁面最初載入時,它不會執行,因此您可以直接呼叫函式來確定啟動狀態:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// initial state
mqHandler(mqLarge);
// initial state mqHandler(mqLarge);
// initial state
mqHandler(mqLarge);

當您在兩個不同的狀態之間移動時,API工作得很好。要分析三種或三種以上的狀態,如smallmediumlarge,需要更多的程式碼。

首先用關聯的matchMedia物件定義螢幕狀態物件:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const
screen = {
small : null,
medium: window.matchMedia( '(min-width: 400px)' ),
large : window.matchMedia( '(min-width: 800px)' )
};
const screen = { small : null, medium: window.matchMedia( '(min-width: 400px)' ), large : window.matchMedia( '(min-width: 800px)' ) };
const
  screen = {
    small : null,
    medium: window.matchMedia( '(min-width: 400px)' ),
    large : window.matchMedia( '(min-width: 800px)' )
  };

無需在small狀態上定義matchMedia物件,因為在smallmedium之間移動時,medium事件處理程式將觸發

然後可以為mediumlarge事件設定事件偵聽器。它們呼叫相同的mqHandler()處理程式函式:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// media query change events
for (let [scr, mq] of Object.entries(screen)) {
if (mq) mq.addEventListener('change', mqHandler);
}
// media query change events for (let [scr, mq] of Object.entries(screen)) { if (mq) mq.addEventListener('change', mqHandler); }
// media query change events
for (let [scr, mq] of Object.entries(screen)) {
  if (mq) mq.addEventListener('change', mqHandler);
}

處理程式函式必須檢查所有MediaQueryList物件,以確定當前是活動的smallmedium還是large。比賽必須按大小順序進行,因為999px的寬度可以匹配中型和大型-只有最大的才能“獲勝”:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// media query handler function
function mqHandler() {
let size = null;
for (let [scr, mq] of Object.entries(screen)) {
if (!mq || mq.matches) size = scr;
}
console.log(size);
}
// media query handler function function mqHandler() { let size = null; for (let [scr, mq] of Object.entries(screen)) { if (!mq || mq.matches) size = scr; } console.log(size); }
// media query handler function
function mqHandler() {
 
  let size = null;
  for (let [scr, mq] of Object.entries(screen)) {
    if (!mq || mq.matches) size = scr;
  }
 
  console.log(size);
 
}

您可以在此處檢視工作演示。(如果使用桌面瀏覽器,請在新視窗中開啟此連結,以便於調整大小。移動使用者可以旋轉裝置。)

使用的示例有:

  1. CSS中的媒體查詢以設定和顯示自定義屬性(如上面的選項2所示)。
  2. matchMedia物件中的相同媒體查詢,以監視JavaScript中的維度更改。JavaScript輸出將在同一時間發生變化。

使用matchMedia API的主要優點是:

  • 它是事件驅動的,能夠高效地處理媒體查詢更改。
  • 它使用與CSS相同的媒體查詢字串。

缺點是:

  • 處理兩個或多個媒體查詢需要更多的思考和程式碼邏輯。
  • 您可能需要在CSS和JavaScript程式碼中複製媒體查詢字串。如果不保持同步,這可能會導致錯誤。

為了避免媒體查詢不匹配,可以考慮在構建系統中使用設計令牌。媒體查詢字串在JSON(或類似)檔案中定義,值在構建時插入CSS和JavaScript程式碼。

總之,matchMedia API可能是實現JavaScript媒體查詢的最有效、最實用的方法。它有一些怪癖,但在大多數情況下它是最好的選擇。

小結

固有的CSS大小選擇越來越可行,但媒體查詢仍然是大多數網站響應性web設計的基礎。它們在處理更復雜的佈局和使用者偏好(如明暗模式)時總是必需的。

儘可能將媒體查詢單獨儲存到CSS。當您別無選擇,只能進入JavaScript領域時,matchMedia API為JavaScript媒體查詢元件提供了額外的控制,這些元件需要額外的基於維度的功能。

評論留言