容器查询是一种提议,它允许web开发人员根据包含元素的大小而不是浏览器视口的大小来设置dom元素的样式。
如果您是web开发人员,您可能以前听说过容器查询。只要我们有响应式网页设计,我们就有开发人员要求他们(最初元素查询,然后更改为容器查询)。事实上,容器查询可能是迄今为止我们浏览器中没有的css?请求中最需要的。
现在已经有很多,很多,很多帖子正好解释了为什么容器查询是很难在css做,为什么浏览器制造商一直在犹豫要实现它们。我不想在这里重新讨论这个讨论。
我没有把注意力集中在我们称之为“容器查询”的特定css特性提案上,而是专注于构建响应其环境的组件的更广泛概念。如果你接受这个更大的框架,实际上有新的web api已经让你实现这一点。
没错,我们不需要等待容器查询开始构建响应式组件。我们现在可以开始构建它们了!
我将在本文中提出的策略可以在今天使用,并且它被设计为增强功能,因此不支持更新的api或不运行javascript的浏览器将按照他们目前的工作方式运行。它也很容易实现(复制/粘贴),高性能,并且不需要任何特殊的构建工具,库或框架。
为了看到这个策略的一些实例,我构建了一个响应组件演示站点。每个演示链接到它的css源代码,所以你可以看到它的工作原理。
响应式网站制作
但在深入演示之前,您应该阅读本文的其余部分,了解策略如何工作。
策略根据这两个核心原则,最具响应性的设计策略或方法(这一点将不会有所不同):
对于每个组件,首先定义一组通用的基本样式,无论组件位于何种环境中,该样式都适用。然后为那些将在特定环境条件下应用的基础样式定义增加或覆盖。即使浏览器不支持满足或启用特定环境条件所需的功能,这些原则的力量也是可行的。这包括功能要求javascript的情况 – 禁用javascript的用户将获得基本样式,而且这些都可以正常工作。
在大多数情况下,上面#1中定义的基本样式是在最小可能的屏幕尺寸上工作的样式(因为小屏幕往往比大屏幕更具限制性),并且它们不包含在任何排序媒体查询中(所以它们应用到处)。
下面是一个例子,它定义.mycomponent了两个任意断点的基本样式,然后覆盖样式,36em并且48em:
.mycomponent {
/* base styles that work for any screen size */
}
@media (min-width: 36em) {
.mycomponent {
/* overrides the above styles on screens larger than 36em */
}
}
@media (min-width: 48em) {
.mycomponent {
/* overrides the above styles on screens larger than 48em */
}
}
当然,这些断点使用媒体查询,所以它们适用于浏览器视口的大小。什么容器查询倡导者想要的是能够做这样的事情(注意,这是建议的语法,而不是官方语法):
.container:media(min-width: 36em) > .mycomponent {
/* overrides that only apply for medium container sizes */
}
不幸的是,上述语法在今天的任何浏览器中都不起作用,并且可能不会很快。
但是,今天的工作是这样的:
.mycomponent {
/* base styles that work on any screen size */
}
.md > .mycomponent {
/* overrides that apply for medium container sizes */
}
.lg > .mycomponent {
/* overrides that apply for large container sizes */
}
当然,这个代码假定组分容器具有添加到它们正确的类(在本例中,.md和.lg)。但是暂时忽略这些细节,如果你是一个想要构建响应组件的css开发人员,那么第二种语法对你来说可能还是有意义的。
无论您是将容器查询作为显式长度比较查询(第一种语法)还是使用命名断点类(第二种语法)编写,您的样式仍然是声明性的,功能上是相同的。只要你可以定义你想要的命名断点,我不会看到其中一个明显的好处。
为了澄清本文的其余部分,让我使用下面的映射(min-width适用于容器而不是视口)定义我正在使用的命名断点类:
命名断点容器宽度
sm min-width: 24em
md min-width: 36em
lg min-width: 48em
xl min-width: 60em
现在我们要做的就是确保我们的容器元素总是拥有正确的断点类,所以正确的组件选择器将会匹配。
观察容器大小对于大多数web开发历史,可以观察对窗口的更改,但是观察对单个dom元素的大小更改是困难的或不可能的(至少以高性能的方式)。当chrome 64发布resizeobserver时,这一点发生了变化。
resizeobserver,继类似mutationobserver和intersectionobserver之类的api之后,web开发人员能够以高性能的方式观察dom元素的大小变化。
以下是您在上一节中使用css所需的代码resizeobserver:
// only run if resizeobserver is supported.
if ('resizeobserver' in self) {
// create a single resizeobserver instance to handle all
// container elements. the instance is created with a callback,
// which is invoked as soon as an element is observed as well
// as any time that element's size changes.
var ro = new resizeobserver(function(entries) {
// default breakpoints that should apply to all observed
// elements that don't define their own custom breakpoints.
var defaultbreakpoints = {sm: 384, md: 576, lg: 768, xl: 960};
entries.foreach(function(entry) {
// if breakpoints are defined on the observed element,
// use them. otherwise use the defaults.
var breakpoints = entry.target.dataset.breakpoints ?
json.parse(entry.target.dataset.breakpoints) :
defaultbreakpoints;
// update the matching breakpoints on the observed element.
object.keys(breakpoints).foreach(function(breakpoint) {
var minwidth = breakpoints[breakpoint];
if (entry.contentrect.width >= minwidth) {
entry.target.classlist.add(breakpoint);
} else {
entry.target.classlist.remove(breakpoint);
}
});
});
});
// find all elements with the `data-observe-resizes` attribute
// and start observing them.
var elements = document.queryselectorall('[data-observe-resizes]');
for (var element, i = 0; element = elements[i]; i++) {
ro.observe(element);
}
}
注意:这个例子使用es5语法,因为(正如我后面解释的),我建议直接在html中嵌入这些代码,而不是将其包含在外部javascript文件中。较老的语法用于更广泛的浏览器支持。此代码resizeobserver使用回调函数创建单个实