轻质砌体解决方案
五月,我了解到Firefox在CSS Grid中添加了砌体布局功能。砌体布局是我一直想从头开始实现的东西,但一直不知道从哪里入手。因此,我自然地查看了演示,然后当我理解这个新的CSS特性是如何工作时,我灵光一闪。
目前,支持仅限于Firefox(而且,即使在那里,也只有在启用特定标志的情况下才支持),但这仍然为我提供了一个JavaScript实现的起点,该实现可以涵盖当前缺乏支持的浏览器。
Firefox在CSS中实现砌体布局的方式是将grid-template-rows
(如示例所示)或grid-template-columns
设置为masonry
值。
我的方法是利用此功能来支持浏览器(再次强调,目前仅指Firefox),并为其余浏览器创建JavaScript回退方案。让我们看看如何使用图像网格的特定案例来实现这一点。
首先,启用标志
为此,我们在Firefox中访问about:config
并搜索“masonry”。这将显示layout.css.grid-template-masonry-value.enabled
标志,我们可以通过双击将其值从false
(默认值)更改为true
来启用它。
让我们从一些标记开始
HTML结构如下所示:
<img src="/static/imghw/default1.png" data-src="https://img.php.cn/upload/article/000/000/000/174364597525146.jpg" class="lazy" alt="A Lightweight Masonry Solution ">
现在,让我们应用一些样式
首先,我们将顶级元素设置为CSS网格容器。接下来,我们为图像定义最大宽度,例如10em
。我们还希望这些图像缩小到网格内容框可用的任何空间,如果视口变得太窄而无法容纳单个10em
列网格,则将实际设置的值为Min(10em, 100%)
。由于响应式设计如今非常重要,我们不使用固定列数,而是自动适应尽可能多的此宽度的列:
$w: Min(10em, 100%); .grid--masonry { display: grid; grid-template-columns: repeat(auto-fit, $w); > * { width: $w; } }
请注意,我们使用了Min()
而不是min()
,以避免Sass冲突。
好吧,这是一个网格!
不过,它不是一个非常漂亮的网格,所以让我们强制其内容水平居中,然后添加网格间隙和填充,两者都等于一个间距值($s
)。我们还设置了一个背景,以便更容易查看。
$s: .5em; /* 砌体网格样式 */ .grid--masonry { /* 与之前的样式相同 */ justify-content: center; grid-gap: $s; padding: $s; } /* 美化样式 */ html { background: #555; }
稍微美化了网格之后,我们转向对网格项目(即图像)进行同样的操作。让我们应用一个滤镜,使它们看起来更统一一些,同时通过稍微圆角和阴影添加一些额外的风格。
img { border-radius: 4px; box-shadow: 2px 2px 5px rgba(#000, .7); filter: sepia(1); }
现在,对于支持砌体布局的浏览器,我们只需要声明它:
.grid--masonry { /* 与之前的样式相同 */ grid-template-rows: masonry; }
虽然这在大多数浏览器中不起作用,但在启用了前面解释的标志的Firefox中,它会产生预期的结果。
但是其他浏览器呢?这就是我们需要…
JavaScript回退方案
为了节省浏览器必须运行的JavaScript代码,我们首先检查页面上是否存在任何.grid--masonry
元素,以及浏览器是否已理解并应用了grid-template-rows
的masonry
值。请注意,这是一种通用方法,假设我们页面上可能有多个这样的网格。
let grids = [...document.querySelectorAll('.grid--masonry')]; if (grids.length && getComputedStyle(grids[0]).gridTemplateRows !== 'masonry') { console.log('糟糕,不支持砌体布局 ?'); } else { console.log('太好了,无需操作!'); }
如果新的砌体功能不受支持,那么我们将获取每个砌体网格的行间隙和网格项目,然后设置列数(每个网格最初为0)。
let grids = [...document.querySelectorAll('.grid--masonry')]; if (grids.length && getComputedStyle(grids[0]).gridTemplateRows !== 'masonry') { grids = grids.map(grid => ({ _el: grid, gap: parseFloat(getComputedStyle(grid).gridRowGap), items: [...grid.childNodes].filter(c => c.nodeType === 1), ncol: 0 })); grids.forEach(grid => console.log(`网格项目:${grid.items.length};网格间隙:${grid.gap}px`)); }
请注意,我们需要确保子节点是元素节点(这意味着它们的nodeType
为1)。否则,我们最终可能会在项目数组中得到由回车符组成的文本节点。
在继续进行之前,我们必须确保页面已加载并且元素仍在移动。一旦我们处理了这个问题,我们就获取每个网格并读取其当前的列数。如果这与我们已经拥有的值不同,那么我们将更新旧值并重新排列网格项目。
if (grids.length && getComputedStyle(grids[0]).gridTemplateRows !== 'masonry') { grids = grids.map(/* 与之前相同 */); function layout() { grids.forEach(grid => { /* 获取调整大小/加载后的列数 */ let ncol = getComputedStyle(grid._el).gridTemplateColumns.split(' ').length; if (grid.ncol !== ncol) { grid.ncol = ncol; console.log('重新排列网格项目'); } }); } addEventListener('load', e => { layout(); /* 初始加载 */ addEventListener('resize', layout, false); }, false); }
请注意,调用layout()
函数是我们需要在初始加载和调整大小时都执行的操作。
要重新排列网格项目,第一步是从所有项目中删除顶部边距(这可能是为了在当前调整大小之前实现砌体效果而设置为非零值)。
如果视口足够窄,我们只有一列,那么我们就完成了!
否则,我们将跳过前ncol
个项目,然后循环遍历其余项目。对于每个考虑的项目,我们计算上面项目的底部边缘位置及其顶部边缘的当前位置。这使我们能够计算需要垂直移动多少,以便其顶部边缘位于上面项目的底部边缘下方一个网格间隙。
/* 如果列数已更改 */ if (grid.ncol !== ncol) { /* 更新列数 */ grid.ncol = ncol; /* 恢复初始定位,无边距 */ grid.items.forEach(c => c.style.removeProperty('margin-top')); /* 如果我们有多于一列 */ if (grid.ncol > 1) { grid.items.slice(ncol).forEach((c, i) => { let prev_fin = grid.items[i].getBoundingClientRect().bottom, /* 上面项目的底部边缘 */ curr_ini = c.getBoundingClientRect().top; /* 当前项目的顶部边缘 */ c.style.marginTop = `${prev_fin grid.gap - curr_ini}px`; }); } }
现在我们有一个可工作的跨浏览器解决方案!
一些小的改进
更真实的结构
在现实世界中,我们更有可能将每个图像包装在一个链接中,链接到其全尺寸图像,以便大图像在灯箱中打开(或者我们将其作为回退导航到它)。
<a href="https://www.php.cn/link/849c1f472f609bb4a3bacafef177f541"> <img src="/static/imghw/default1.png" data-src="https://img.php.cn/upload/article/000/000/000/174364597550777.jpg" class="lazy" alt="A Lightweight Masonry Solution "> </a>
这意味着我们也需要稍微更改一下CSS。虽然我们不再需要显式地设置网格项目的宽度——因为它们现在是链接——但我们需要设置align-self: start
,因为与图像不同,它们默认会拉伸以覆盖整个行高,这会扰乱我们的算法。
.grid--masonry > * { align-self: start; } img { display: block; /* 避免底部出现奇怪的额外空间 */ width: 100%; /* 与之前的样式相同 */ }
使第一个元素跨越网格
我们还可以使第一个项目水平跨越整个网格(这意味着我们可能还应该限制其高度并确保图像不会溢出或变形):
.grid--masonry > :first-child { grid-column: 1 / -1; max-height: 29vh; } img { max-height: inherit; object-fit: cover; /* 与之前的样式相同 */ }
我们还需要在获取网格项目列表时添加另一个筛选条件来排除此拉伸项目:
grids = grids.map(grid => ({ _el: grid, gap: parseFloat(getComputedStyle(grid).gridRowGap), items: [...grid.childNodes].filter(c => c.nodeType === 1 && getComputedStyle(c).gridColumnEnd !== -1 ), ncol: 0 }));
处理具有可变纵横比的网格项目
假设我们想将此解决方案用于博客之类的用途。我们保留完全相同的JS和几乎完全相同的砌体特定CSS——我们只更改列的最大宽度并删除第一个项目的max-height
限制。
从下面的演示可以看出,我们的解决方案在这种情况下也能完美地工作,我们有一个博客文章网格:
您还可以调整视口大小以查看它在这种情况下是如何工作的。
但是,如果我们希望列的宽度有一定的灵活性,例如:
$w: minmax(Min(20em, 100%), 1fr);
那么我们在调整大小时就会遇到问题:
网格项目宽度变化以及每个项目文本内容不同的事实相结合意味着,当超过某个阈值时,我们可能会获得网格项目的不同文本行数(从而改变高度),但其他项目则不会。如果列数没有改变,那么垂直偏移量就不会重新计算,我们最终会得到重叠或更大的间隙。
为了解决这个问题,我们需要在至少一个项目的当前网格高度发生变化时重新计算偏移量。这意味着我们还需要测试当前网格中是否有超过零个项目改变了它们的高度。然后,我们需要在if
块的末尾重置此值,以便我们下次不必不必要地重新排列项目。
if (grid.ncol !== ncol || grid.mod) { /* 与之前相同 */ grid.mod = 0; }
好的,但是我们如何更改这个grid.mod
值呢?我的第一个想法是使用ResizeObserver
:
if (grids.length && getComputedStyle(grids[0]).gridTemplateRows !== 'masonry') { let o = new ResizeObserver(entries => { entries.forEach(entry => { grids.find(grid => grid._el === entry.target.parentElement).mod = 1; }); }); /* 与之前相同 */ addEventListener('load', e => { /* 与之前相同 */ grids.forEach(grid => { grid.items.forEach(c => o.observe(c)); }); }, false); }
这确实可以在必要时重新排列网格项目,即使网格列数没有改变也是如此。但它也使即使有那个if
条件也是没有意义的!
这是因为它在至少一个项目的高度或宽度发生变化时将grid.mod
更改为1。项目的height会因为text reflow而改变,text reflow是由width改变引起的。但是width的改变每次我们调整视口大小时都会发生,并不一定会触发height的改变。
这就是为什么我最终决定存储以前的项目高度并在调整大小时检查它们是否已更改,以确定grid.mod
是否保持为0:
function layout() { grids.forEach(grid => { grid.items.forEach(c => { let new_h = c.getBoundingClientRect().height; if (new_h !== c.dataset.h) { c.dataset.h = new_h; grid.mod ; } }); /* 与之前相同 */ }); }
就是这样!现在我们有一个不错的轻量级解决方案。压缩后的JavaScript不到800字节,而严格的砌体相关样式不到300字节。
但是,但是,但是……
浏览器支持如何?
好吧,@supports
碰巧比这里使用的任何较新的CSS特性都有更好的浏览器支持,因此我们可以将其中的好东西放在里面,并为不支持的浏览器提供一个基本的非砌体网格。此版本一直向后兼容到IE9。
它可能看起来不一样,但它看起来不错,而且功能完善。支持浏览器并不意味着为其复制所有视觉效果。这意味着页面可以工作,并且看起来不损坏或难看。
没有JavaScript的情况如何?
好吧,我们只有在根元素具有我们通过JavaScript添加的js
类时才能应用花哨的样式!否则,我们将获得一个所有项目大小相同的基本网格。
以上是轻质砌体解决方案的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

您是否曾经在项目上需要一个倒计时计时器?对于这样的东西,可以自然访问插件,但实际上更多

我关注的一件事是Lea Verou&#039; s conic-Gradient()Polyfill的功能列表是最后一项:
