搜索
首页web前端css教程轻质砌体解决方案

轻质砌体解决方案

Apr 03, 2025 am 10:06 AM

A Lightweight Masonry Solution

五月,我了解到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/imghwm/default1.png" data-src="https://img.php.cn/upload/article/000/000/000/174364597525146.jpg?x-oss-process=image/resize,p_40" 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-rowsmasonry值。请注意,这是一种通用方法,假设我们页面上可能有多个这样的网格。

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/imghwm/default1.png" data-src="https://img.php.cn/upload/article/000/000/000/174364597550777.jpg?x-oss-process=image/resize,p_40" 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中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
丢失的CSS技巧cohost.org丢失的CSS技巧cohost.orgApr 25, 2025 am 09:51 AM

在这篇文章中,布莱克·莫里(Blackle Mori)向您展示了一些骇客,同时试图推动同位HTML支持的极限。如果您敢于使用这些,以免您也被标记为CSS罪犯。

光标的下一个CSS样式光标的下一个CSS样式Apr 23, 2025 am 11:04 AM

具有CSS的自定义光标很棒,但是我们可以将JavaScript提升到一个新的水平。使用JavaScript,我们可以在光标状态之间过渡,将动态文本放置在光标中,应用复杂的动画并应用过滤器。

世界碰撞:使用样式查询的钥匙帧碰撞检测世界碰撞:使用样式查询的钥匙帧碰撞检测Apr 23, 2025 am 10:42 AM

互动CSS动画和元素相互启动的元素在2025年似乎更合理。虽然不需要在CSS中实施乒乓球,但CSS的灵活性和力量的增加,可以怀疑Lee&Aver Lee&Aver Lee有一天将是一场

使用CSS背景过滤器进行UI效果使用CSS背景过滤器进行UI效果Apr 23, 2025 am 10:20 AM

有关利用CSS背景滤波器属性来样式用户界面的提示和技巧。您将学习如何在多个元素之间进行背景过滤器,并将它们与其他CSS图形效果集成在一起以创建精心设计的设计。

微笑吗?微笑吗?Apr 23, 2025 am 09:57 AM

好吧,事实证明,SVG的内置动画功能从未按计划进行弃用。当然,CSS和JavaScript具有承载负载的能力,但是很高兴知道Smil并没有像以前那样死在水中

'漂亮”在情人眼中'漂亮”在情人眼中Apr 23, 2025 am 09:40 AM

是的,让#039;跳上文字包装:Safari Technology Preview In Pretty Landing!但是请注意,它与在铬浏览器中的工作方式不同。

CSS-tricks编年史XLIIICSS-tricks编年史XLIIIApr 23, 2025 am 09:35 AM

此CSS-tricks更新了,重点介绍了年鉴,最近的播客出现,新的CSS计数器指南以及增加了几位新作者,这些新作者贡献了有价值的内容。

tailwind的@Apply功能比听起来更好tailwind的@Apply功能比听起来更好Apr 23, 2025 am 09:23 AM

在大多数情况下,人们展示了@Apply的@Apply功能,其中包括Tailwind的单个property实用程序之一(会改变单个CSS声明)。当以这种方式展示时,@Apply听起来似乎很有希望。如此明显

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

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

热工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境