css实现六边形及其它灵活布局

2,759 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

这是一个响应式的六边形网格布局,没有用到媒体查询、JavaScript 或大量的 CSS。是不是很酷?现在我们来进行案例讲解一下:

制作六边形网格

首先,创建我们的六边形。这个任务使用 clip-path 实现起来相当容易。另外推荐一个很棒的剪辑路径的在线生成器Clippy裁剪示意图一:

1.png 为每个六边形元素设置为inline-block

    <div class="main">
        <div class="container">
            <div></div>
            <div></div>
            <div></div>
            <!--更多的元素. --> 
        </div>
    </div>

css设置为:

    .main {
      display: flex;
      --s: 100px;  /* size  */
      --m: 4px;   /* margin */
    }

    .container {
      font-size: 0; /* 禁用内联块元素之间的空白 */
    }

    .container div {
      width: var(--s);
      margin: var(--m);
      height: calc(var(--s) * 1.1547);
      display: inline-block;
      font-size: initial;
      clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%);
    }

到目前为止并没有什么复杂的处理逻辑,我们设置了一个容器container来存放inline-block的六边形元素,另外我们需要处理因为inline-block而导致的空白间隙问题(我们使用font-size: 0;进行修复):

2.gif 至此,我们将得到这样的效果:

3.png 每隔一行我们都需要设置一些偏移量,以便使六边形元素重叠而不是直接堆叠在彼此之上。该偏移量将等于25%的元素高度(参考图 1)。我们将该偏移量应用于margin-bottom

    .container div {
      width: var(--s);
      margin: var(--m);
      height: calc(var(--s) * 1.1547);
      display: inline-block;
      font-size: initial;
      clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%);
      margin-bottom: calc(var(--m) - var(--s) * 0.2886); /* 设置底部边距 */
    }

...结果变成: 4.png 现在我们面临的问题是如何移动第二行元素以获得完美的六边形网格。我们已经将元素压缩到行彼此垂直重叠的程度,我们还需要的是将元素每隔一行向右推,以便六边形交错而不是重叠。这时候就是float发挥shape-outside作用的时刻了。

你有没有想过为什么我们要设置一个display: flex.main元素来包裹我们的容器.container?该操作也是 div 布局技巧的一部分:

弹性项目的例子:

    <div style="display:flex">

        <!-- flex item: block child -->
        <div id="item1">block</div>

        <!-- flex item: floated element; 浮动被忽略 -->
        <div id="item2" style="float: left;">float</div>

        <!-- flex item: 行内文本会生成一个匿名的块 -->
        anonymous item 3

        <!-- flex item: inline child -->
        <span>
            item 4
            <!-- flex items 内为普通流布局 -->
            <q style="display: block" id=not-an-item>item 4</q>
            item 4
        </span>
    </div>

上述代码会生成: 5.png

请注意,元素间的空白消失了:它不会成为自己的弹性项目,即使元素间文本确实被包裹在匿名弹性项目中。

还要注意匿名项的框是不可设置样式的,因为没有要为其分配样式规则的元素。然而,它的内容将从 flex 容器继承样式(例如字体设置)。

这里我们使用.container::before伪类元素创建一个浮动元素,它占据了网格左侧的所有高度,并且宽度等于半个六边形(加上边距):

    .container::before { content: ""; width: calc(var(--s)/2 + var(--m)); float: left; height: 100%; }

我们得到以下结果:

6.png 现在我们快速回顾一下shape-outsideMDN的描述:

shape-outside 的 CSS 属性定义了一个可以是非矩形的形状,相邻的内联内容应围绕该形状进行包装。默认情况下,内联内容包围其边距框; shape-outside提供了一种自定义此包装的方法,可以将文本包装在复杂对象周围而不是简单的框中。

注意定义中的“内联内容”。这恰恰解释了为什么六边形需要的是inline-block元素。但要了解我们需要什么样的形状,让我们放大图案:

7.png shape-outside最有意思的是它可以使用渐变,但是什么样的渐变才适用于我们的六边形布局呢?假如我们有 10 行六边形,我们只需要在每偶数行移动一个均值。从不同的角度来看,我们需要每隔一行移动一次,所以我们需要一重复渐变。shape-outside大概设置为:

    shape-outside: repeating-linear-gradient(#0000 0 A, #000 0 B); /* #0000 是 transparent 的一种写法 */

现在我们需要定义A、B的值。因为六边形网格布局,我们需要重复每两行,因此B的值两行的高度,两行的高度等于两个六边形的高度(包括它们的边距)减去重叠的两倍(2*Height + 4*M - 2*Height*25% = 1.5*Height + 4*M ),使用csscalc()并将该数值存在css变量f中:

    .main {
      display:flex;
      --s: 100px;  /* size  */
      --m: 4px;    /* margin */
      --f: calc(1.732 * var(--s) + 4 * var(--m)  - 1px);
    }

A(上图中蓝色箭头定义的)的值需要至少等于一个六边形的大小,或更大一点。为了将第二行推到右边,我们需要一些不透明颜色的像素,所以A可以简单地等于B - Xpx,其中X是一个小值,我们最终得到shape-outside的值如下:

    shape-outside: repeating-linear-gradient(#0000 0 calc(var(--f) - 3px),#000 0 var(--f));

我们将得到:

8.png

我们重复的线性渐变的形状是将每隔一行向右推一个六边形宽度的一半,以抵消图案,最终代码如下:

    .main {
      display:flex;
      --s: 100px;  /* size  */
      --m: 4px;    /* margin */
      --f: calc(var(--s) * 1.732 + 4 * var(--m) - 1px); 
    }

    .container {
      font-size: 0; /* 清除行内块级元素的边距 */
    }

    .container div {
      width: var(--s);
      margin: var(--m);
      height: calc(var(--s) * 1.1547);
      display: inline-block;
      font-size:initial;
      clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%);
      margin-bottom: calc(var(--m) - var(--s) * 0.2885);
    }

    .container::before {
      content: "";
      width: calc(var(--s) / 2 + var(--m));
      float: left;
      height: 120%; 
      shape-outside: repeating-linear-gradient(#0000 0 calc(var(--f) - 3px), #000 0 var(--f));
    }

至此,六边形网格布局基本完成。您可能已经注意到我在变量f中添加了-1px。由于我们正在处理涉及小数的计算,四舍五入可能会给我们带来不好的结果。为了避免这种情况,我们添加或删除了几个像素。出于类似的原因,我们也使用120%而不是100%浮动元素的高度。这些值没有特定的逻辑;我们只是简单地调整它们以确保覆盖大多数情况而不会错位我们的形状。

想要更多的形状?

参照上面的案例,我们只需要变更shape-outside的值即可就行各种内联环绕效果: 本文只是讲解一个思路,更多网格布局可参考类似思路进行开发😘