媒体查询(Media Queries)

我们都知道媒体查询在响应式设计中扮演着一个重要的角色,它可以让我们在不同特性的设备上应用不同的 CSS 样式。一种常见的应用如下:

/* 在width不大于640的设备上,aside将被隐藏 */
@media (max-width: 640px) {
  aside {
    display: none;
  }
}

其实在 JavaScript 中通过 MediaQueryList 接口,也可以进行媒体查询。

MediaQueryList 是什么?

MediaQueryList (下面简称 MQL )是 CSSOM View Module 中定义的一个接口,一个 MQL 对象,本质上对应一条媒体查询语句,通过获取它的 matches 属性得到查询结果。 MQL 对象通过调用 window.matchMedia 方法获得,matchMedia 接受一个媒体查询语句作为参数,并返回对应的 MQL 对象。一个简单的例子如下:

const mql = window.matchMedia('(max-width: 640px)')
if (mql.matches) {
  // width <= 640px
} else {
  // width > 640px
}

mql 对象还提供了 change 事件接口,它拥有 addListener(handler)removeListener(handler) 两个方法,用于监听和移除事件,这两个方法可以看作是 addEventListener('change', handler)addEventListener('change', handler) 的简写。handler 是一个回调函数,传回一个 MediaQueryListEvent 作为参数。

const mql = window.matchMedia('(max-width: 640px)')
mql.addListener(mediaChangeHandler) // mql.onchange = mediaChangeHandler

function mediaChangeHandler (mqle) {
  console.log(mqle.matches) // 更新后的boolean值
  console.log(mqle.media) // 对应的查询字符串
}

它能做什么?

  1. 更灵活的响应式设计

    CSS 中的媒体查询可以对相同的内容在不同的场景下作用不同的样式,而 JS 中的媒体查询可以让我们根据设备特性的变化直接修改内容,用一个简单点的例子来说就是 CSS 的媒体查询可以控制元素的显示与隐藏,而 MQL 可以直接控制元素是否存在。

  2. 在一定程度上代替 UA 检测

    window.matchMedia('handheld')
    window.matchMedia('tv')
  3. 更简便的检测方案

需要注意啥?

  1. MQL 对象是动态的

    同 HTMLElement 上的 classList, childNodes 一样(不同的是 classList, childNodes 都是引用类型,而 matches 是基本类型),第一次通过 matchMedia 方法获取得到 mql 之后,后面媒体特性发生改变,mql.matches 拿到的就是最新的值,不需要再调用一次 matchMedia。

  2. 兼容性

    MQL 比 CSS 中的媒体查询出来的晚一些,所以并不代表支持 CSS 媒体查询的浏览器就一定支持 MQL(当然支持 MQL 的浏览器支持 CSS Media Query 是没问题的),IE9 及以下不支持 MQL。对于 IE9, 可以使用 matchMedia polyfill,这个 polyfill 是基于 CSS 的 Media Queries 实现的,所以只能作用于支持 CSS Media Queries 的浏览器。

一个简单的例子

改变下面表格的宽度,查看变化

可以看到当屏幕宽度最大为 500px 时,月份显示的是头三个字母的缩写。这个例子的主要代码如下:

<table>
  <caption>Days of Months</caption>
  <thead>
    <tr>
      <th class="month">July</th>
      <th class="month">August</th>
      <th class="month">September</th>
      <th class="month">October</th>
      <th class="month">November</th>
      <th class="month">December</th>
    </tr>
  </thead>
  ...
</table>
const daysOfMonths = {
  "July": 31,
  "August": 31,
  "September": 30,
  "October": 31,
  "November": 30,
  "December": 31
}

function mqHandler (mqle) {
  const $months = document.querySelectorAll('.month')
  let displayMonths = Object.keys(daysOfMonths)
  if (mqle.matches) { // 最大宽度为 500px ,截取前 3 个字符
    displayMonths = displayMonths.map(s => s.slice(0, 3))
  }
  $months.forEach(($el, i) => $el.textContent = displayMonths[i])
}

const mql = window.matchMedia('(max-width: 500px)')
mql.addListener(mqHandler)
mqHandler(mql) // 页面加载时初始化