[CSS] Flex/Grid Layout Modules, part 16

Media Query 我觉得已经讲到快烂掉了,搭配 Grid 说实在话也没有很不好做的地方。不过,由於 Grid 是「方格系统」,所以你必须要撇开之前使用 Float Position, Flexbox 的那种流向的思维,这是比较令人苦恼的。

像是 Bootstrap 5.1.x 可以打开 Grid(预设关闭),揪竟多少人会打开呢?让我们继续看下去...


Grid 在 RWD 的使用盲点

最困难的点在於数(三声)数(四声)。

我就不复习 RWD 的事情了。一开始我们先复习一下 Grid 的定义方式,

.grid-layout {
    display: grid;
    
    grid-template-columns: repeat(4, 1fr);
    grid-template-rows: repeat(4, 1fr);
    
    gap: 10px;
}

由於我们上面使用了弹性单位,所以这个 Grid 在任何尺寸底下,都会弹性的保持在 4x4 的网格系统内。这样是不是很棒?

你很棒,你全家都很棒!

换句话说,网格系统在使用弹性尺寸的设定下,无论任何尺寸都会保有最小可使用空间(min-content)的设计。这样对於排版来说未必是一件好事。举例来说,如果我今天想要在 [2, 3] 这个位置上,让当中的元件强制换行,那麽要怎麽做?

假设我们在 4x4 的位置上,都恰恰好的放一个 1x1 的方块,

// 因为我不想占版面,所以用 SCSS 写一点回圈

@for $i from 1 through 4 {
    @for $j from 1 through 4 {
        .box-#{(($i - 1) * 4 + $j)} {
            grid-column: #{$j} / #{($j + 1)};
            grid-row: #{$i} / #{($i + 1)};
        }
    }
}

然後我们来找一下 [2, 3] 这个位置的方块是谁,

https://ithelp.ithome.com.tw/upload/images/20220517/20001433f9wyOWnTwL.png

现在我们找到 [2, 3] 的元件之後,那麽我要怎麽让他 断行 呢?你可以开始找找 Google 或是 StackOverflow 看看有没有关於 grid layout break row 之类的结果。

你现在看到的这篇应该就是最佳解,不用找了。

首先必须澄清一件事情,网格系统里面没有所谓的 断行 这件事情。你所看到或是查询到跟断行操作很类似的作法,其实是利用 Auto-placement 的特性去做的。

Auto-placement in CSS Grid Layout, MDN

所以,我们重新来看一次真正需要的事情什麽?

  1. 我想要在 [2, 3] 的位置发生断行的动作。
  2. 所以 [3, 3] 之後的元件应该都继续往下放。

好的,首先先提醒会产生的 副作用

  • 由於网格系统可摆放空间不够,势必会产生隐性网格。
  • 如果没有定义自动填满栏或列的尺寸,请留意他会使用 min-content 来填充。
  • 如果你原本有使用 负数 的轨道,请注意会被改变。

实际上该怎麽操作?

// 还记得 max-width 是数值以上?还是数值以下吗?

@media screen and (max-width: 768px) {

    // 对了,[3, 3] 位置是 11 号的盒子
    
    .box-11 {
        grid-column: 1 / 2;
        grid-row: 4 / 5;
    }
    
    // 然後後面的 12, 13, 14, 15, 16 全部都要改
    // 然後後面的 12, 13, 14, 15, 16 全部都要改
    // 然後後面的 12, 13, 14, 15, 16 全部都要改
}

有没有觉得很崩溃?

现在来解释为何用 Auto-replacement 可以做到 类似 的事情,先决条件是,你的网格系统不能强迫定义每个元件的网格位置,也就是说,你必须让他自然排列或使用相对位置排列,

.box-1 {
    grid-column: 1 / 1;
    grid-row: 1 / 1;
}

// 只设定第一个盒子,後面都不设定。
// 以上述的例子,甚至全部都不设定也可以。

如果我们要将 [2, 3] 这个位置後面的元件都换到下一行去,

@media screen and (max-width: 768px) {

    // 对了,[2, 3] 位置是 10 号的盒子
    
    .box-11 {
        grid-column: span 3;
    }
}

这样你就会得到这样的结果,

https://ithelp.ithome.com.tw/upload/images/20220517/200014338OaZBKklXb.png

这就是普遍你能找到的所谓 断行 的解法,利用 Auto-placement 的规则,加上 span 关键字来让该位置的元件产生 跨栏 的动作,这样,自然的後面的元件就会因为栏位不够用的关系,被挤到下一行去。

说真的不叫做断行,而是 被断行。以现行的格线系统来看,如果不想要很麻烦的定义每个元件的位置,那麽你也只能这样做。或是使用命名方式的 grid-template-areas 来做也不是不行。

所以我才会说这是一个数数的工作,而且很要命的是很容易数错。

那麽,有没有比较好的作法?有的,但我有个前提,这是我自己觉得比较好的作法,如果觉得不对或是不好的,就不要用就好了,请不要留言骂我~

如果没问题请继续往下看。


对於 RWD 来说,一种比较合适的 Grid 结构方式

一般情况来说,我们不太可能将整个页面设计都使用弹性尺寸来做,除非你的客户弹性很大(像是那个什麽石墨烯的裤子),不然通常还是会有需要固定尺寸,固定位置的需求。

以上述的例子来说,我们以样使用一个 4x4 的网格容器,并且定义了几个区块,

.grid-layout {
    display: grid;
    
    grid-template-columns: 200px repeat(3, 1fr);
    grid-template-rows: 80px repeat(2, 1fr) 80px;
    
    gap: 10px;
}

.header {
    grid-column: 1 / 5;
    grid-row: 1 / 2;
}

.sidebar {
    grid-column: 1 / 2;
    grid-row: 1 / 4;
}

.main {
    grid-column: 2 / 5;
    grid-row: 1 / 4;
}

.footer {
    grid-column: 1 / 5;
    grid-row: 3 / 4;
}

这样我们就会得到像是这样的结构,

https://ithelp.ithome.com.tw/upload/images/20220517/20001433oirfbAkiVD.png

接着,我们来思考一下 Media Query 该怎麽做?如果我们需要在小装置上,把 .sidebar 放到 .main 的下方,实际上的操作方式会是什麽?

首先,请记得网格所设定的固定尺寸是不会因为你的装置变小而有所变化,也就是说,上述的设定被套用到手机上,例如 iPhone 13 的尺寸下(装置 Viewport 宽度为 390px)的情况,那麽我们可以知道整个容器的弹性空间会被压缩。

也就是说,扣掉 .sidebar 本身固定的 200px,再扣掉 gap10px 之後,你现在的 .main 就只剩下 180px 可用,这是弹性空间所计算出来的剩余尺寸。然後在手机上就会出现侧边栏位比主要栏位还要大的情况。

回到 Media Query,到底该怎麽做会比较好?

// 首先,我们以 Mobile First 为出发点(个人喜好)

.header,
.sidebar,
.main,
.footer {
  grid-column: span 4;
}

// 接着做一个大尺寸的 Mediq Query

@media screen and (min-width: 768px) {
    .header {
        grid-column: span 4;
    }

    .sidebar {
        grid-column: unset;
        grid-row: span 2;
    }

    .main {
        grid-column: span 3;
        grid-row: span 2;
    }

    .footer {
        grid-column: span 4;
    }
}

由上面的例子,你应该可以理解到格线系统里面没有 断行 的概念,全部都是一个矩型的矩阵在排位置,你可以把他想像成一个围棋的棋盘,想像你要摆放的东西,在什麽位置(座标)上面,涵盖了多大的范围。

https://ithelp.ithome.com.tw/upload/images/20220517/2000143337qDaOTM7R.png

https://ithelp.ithome.com.tw/upload/images/20220517/200014333IeDireUHo.png

那麽,我们来看看一个比较复杂的例子,倘若我们的元件排列没那麽单纯,又有顺序问题的情况下,怎麽样制作 Grid 呢(或者你直接用 Flexbox 搞不好更快)?

延续上述的例子,我们想要在 .main 的上下加上广告,但是在手机上,这个广告得出现在 .sidebar 的上下方,不想将 .main 夹在广告中间。

我们先来看手机的呈现方式,

https://ithelp.ithome.com.tw/upload/images/20220517/20001433eK5CifAXLV.png

然後给大家看一下 HTML 的内容,

<div class="grid-layout">
    <div class="header">Header</div>
    <div class="sidebar">Sidebar</div>
    <div class="main">Main</div>
    <div class="ads-1">ADs 1</div>
    <div class="ads-2">ADs 2</div>
    <div class="footer">Footer</div>
</div>

接着我们来看一下大尺寸的呈现结果,

https://ithelp.ithome.com.tw/upload/images/20220517/200014337ExiDMPkC3.png

看起来好像很完美,但实务上的操作其实挺累人的,我再强调一次,在网格系统中没有 断行 的概念,他只有 定位排序 可以做。

在小装置上,我的 CSS 以最少的设定大概是这样,

.grid-layout {
    display: grid;
    
    grid-template-columns: 200px repeat(4, 1fr);
    grid-template-rows: 80px repeat(2, 1fr 20px) 80px;
    
    gap: 10px;
}

.header,
.sidebar,
.main,
.ad-1,
.ad-2,
.footer {
    grid-column: span 5;
    order: 1;
}

.main {
    order: 2;
}

.ads-1 {
    order: 3;
}

.sidebar {
    order: 4;
}

.footer,
.ads-2 {
    order: 5;
}

接着换到比较大的装置,你应该有发现连网格系统的尺寸都有略微调整了,

@media screen and (min-width: 768px) {
    .grid-layout {
        grid-template-rows: 80px 60px repeat(3, 1fr) 80px;
    }

    .sidebar {
        grid-column: unset;
        grid-row: span 4;
        order: 2;
    }
    
    .main {
        grid-column: span 4;
        grid-row: span 3;

        order: 4;
    }
    
    .ads-1,
    .ads-2 {
        grid-column: span 2;
        
        order: 3;
    }
    
    .footer {
        order: 5;
    }
}

Media Query 到底做了什麽?

没有。都是在玩拼图。

在格线系统中,其实并不像 Flexbox 会有那麽多关於文件流要考虑的点,不能说没有,只能说是相对的少,而且要考量的点也不一样。扣除掉使用绝对定位(position: absolute)所带来的状况以外,在先前的 Flexbox 并不需要考虑定位问题,这是相对的差异。

与其说是 Mediq Query 来做 RWD,倒不如说是利用 Media Query 然後把 整个画面换掉(重做) 的感觉。不相信的话,你可以去搜寻 css grid with media queries 之类的,然後就一堆人告诉你怎麽样用 Media Query 去换整个 Grid Layout 等等的操作。

等一下,先不要。

一般来说,把 整个画面换掉 的事情应该是属於 AWD(Adaptive Web Design)的范畴。最一开始的初衷应该是 规划出一个不会整个换掉的 Grid Layout 才是重点。

总结来说,如果要在网格系统规划 Media Query 的话,以下诚心建议:

  1. 找个方格纸笔记本。
  2. 把元件排上去,然後决定各种尺寸的顺序、大小及位置。
  3. 接着决定 HTML 语意结构,你要放到 2. 之前也可,顺序就要另外计算。
  4. 把最小的装置先摆出来(通常最好做)。
  5. 慢慢把装置放大,并在每个 breakpoint 决定元件顺序、大小及位置。
  6. 根据每一种 breakpoint 规划适当的 Grid 容器。
  7. 适当的利用弹性尺寸弥补无法预测的边界尺寸。
  8. 真的不行就回去用 Flexbox。

既然都要用 Flexbox,为何不一开始就用?


小结

虽然说 Grid 好像用起来很潮,不过要考虑的地方其实蛮多的。目前现行的诸多套件,基本上还是把他当作 类 Flexbox 来操作,但,其实没人在乎吧。

谁在乎谁痛苦。


目录与小节:
[CSS] Flex/Grid Layout Modules, part 1


部落格同步放送:
[CSS] Flex/Grid Layout Modules, part 16


<<:  ISO 27001 资讯安全管理系统 【解析】(二十四)

>>:  一个人在一年写一套ERP程序

[ JS个人笔记 ] Event Loop事件循环—DAY11

理解js单执行绪&非同步运行机制 由於js为单执行绪,也就是一次只处理一件事情并依序执行,但...

[Day23] JavaScript 函式库 - React

终於要到了铁人赛的尾声了,笔者一直想在最後几篇来了解一下React,所以趁现在JavaScript应...

Day21 Create React App

之前我们在范例练习React时,都是引用CDN的方式来建立开发环境,今天要介绍的是Create Re...

[Day12] CH08:积沙成塔——Array & ArrayList(中)

还记得我们前两天学的方法吗?结合昨天学的阵列,阵列也可以用在方法里传递吗? 当然可以罗!我们就先来看...

Ruby解题分享--Length of Last Word && Plus One && Add Binary

周日一堆韩国综艺要看....好忙! 上一篇Maximum Subarray解题,最後答案有稍作修改。...