通过 CSS 动画在 JavaScript 中检测媒体查询变化

Avatar of Alessandro Vendruscolo
Alessandro Vendruscolo 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 价值 $200 的免费积分!

以下是 Alessandro Vendruscolo 的客座文章。媒体查询与 CSS 和 JS 相关。在同一位置管理它们的需要和愿望是真实的。一些聪明的做法已经出现,比如 Jeremy Keith 的 条件 CSS。但在这种情况下,您有责任在窗口状态改变后进行测试。您可以通过 MediaQueryList 获取真正的监听器,但这意味着您需要再次在两个地方维护媒体查询。好吧,让 Alessandro 来解释一下吧。

媒体查询最早是在 十二年前(是的,第一份草案可追溯到 2001 年 4 月 4 日!)引入的,旨在限制样式表的范围

媒体查询由媒体类型和一个或多个表达式组成,用于限制特定样式表的范围。[…] 通过使用媒体查询,内容呈现可以针对各种设备进行调整,而无需更改内容本身。

通过使用 CSS 中的媒体查询,我们可以根据例如浏览器屏幕的宽度来选择性地使用样式。

@media screen and (min-width: 960px) {
  body {
    padding: 50px;
  }
}

在上例中,如果您的浏览器屏幕宽度大于或等于 960px,则 body 将具有 50px 的 填充

从 CSS 动画触发的 DOM 事件

一个简单的点击事件

document.querySelector('a.button').addEventListener('click', function(event) {
  // do something
});

这里没什么特别的,只是标准的 DOM 编程。click 事件,以及其他许多事件,是在用户与页面交互时生成的:点击、移动光标、滚动页面等等。

所有这些事件都与 DOM 相关,并且作为用户交互的结果而生成。还有一些与 CSS 动画 专门相关的事件:animationStartanimationEnd(及其 过渡 对应事件,transitionStarttransitionEnd)。

如果我们向页面中插入一个新元素,并且它有动画,我们知道 CSS 动画会在插入后立即开始。因此,如果我们监听该动画的 animationstart 事件,我们可以知道它何时被插入。

@keyframes nodeInserted {
  from { clip: rect(1px, auto, auto, auto); }
  to { clip: rect(0px, auto, auto, auto); }
}
.an-element {
  animation-duration: 0.001s;
  animation-name: nodeInserted;
}
document.addEventListener("animationstart", function (event) {
  if (event.animationName == "nodeInserted") {
    // an element with class an-element has been inserted in the DOM
  }
}, false);

感谢 David WalshDaniel BuchnerOmar Ismail 大约一年前关于这方面的博文。

将媒体查询和动画结合在一起

也许您能想到这将走向何方。如果我们在媒体查询发生变化时触发动画(而不是在节点插入时),我们也可以在 JavaScript 中检测到媒体查询变化何时发生。

body {
  animation-duration: 0.001s;
}
@media screen and (min-width: 1000px) {
  body {
    animation-name: min-width-1000px;
  }
}
@media screen and (min-width: 700px) {
  body {
    animation-name: min-width-700px;
  }
}

@keyframes min-width-700px {
  from { clip: rect(1px, auto, auto, auto); }
  to { clip: rect(0px, auto, auto, auto); }
}

@keyframes min-width-1000px {
  from { clip: rect(1px, auto, auto, auto); }
  to { clip: rect(0px, auto, auto, auto); }
}
document.addEventListener(animationEnd, dispatchEvent, false);

// check the animation name and operate accordingly
function dispatchEvent(event) {
  if (event.animationName === 'min-width-700px') {
    document.body.innerHTML = 'Min width is 700px';
  } else if (event.animationName === 'min-width-1000px') {
    document.body.innerHTML = 'Min width is 1000px';
  }
}

编辑备注:明智地选择您的名称。

CodePen 上的演示


我们拥有相同的简单动画,它实际上什么也不做,但这次,动画位于 @media 块内。

如果窗口宽度超过 700px,min-width-700px 动画将触发,我们的 dispatchEvent 将获取此信息。把它想象成一个回调。然后,我们可以通过显示新的部件、销毁其他部件,或执行任何其他操作来适应新的屏幕尺寸。

有趣的是,我们不需要使用窗口大小调整事件,因为该事件可能很慢并且触发次数太多(通常需要节流)。只要媒体查询匹配,animationstart 事件就会触发,即使我们没有调整窗口大小。如果我们随后调整窗口大小,该事件将仅在媒体查询匹配时触发。

正确的方法

现在我们已经讨论了如何在不依赖 resize 事件的情况下获得窗口宽度更改的 JavaScript 回调。

但我建议您不要将此代码用于生产环境。它可以工作,但正确的方法应该是使用 matchMedia 方法,该方法返回一个 MediaQueryListMediaQueryList 对象可以拥有监听器,因此我们可以使用 addListener 方法在媒体查询匹配或不匹配时获得通知。

Enquire.js 最近发布了版本 2,它封装了 matchMedia 方法,为我们提供了简洁而强大的包装器。