Web技巧 (04)

发布于 大漠

在Web技巧第三期中,我们了解到了排版中断字连接符、原生的延迟加载、不规则阴影处理、Sass颜色函数和CSS自定义属性的结合以及CSS中对齐等知识点。在这一期中,我们将围绕着CSS中的布局来展开。在这一期中将会向大家介绍CSS Shapes给布局带来的变化(Web艺术)、在Flexbox和Grid布局中如何控制最后一行的剩余项以及如何使用Flexbox和Grid实现响应式布局、CSS怎么实现瀑布流布局、CSS怎么构建对角容器和两个还未得到支持的CSS特性:subgrid带两个属性值的display

Web艺术: CSS Shapes给布局带来的变化

@Andy Clarke在使用CSS给新的设计带来很多边界性的突破。在他的新文章《Art Direction For The Web Using CSS Shapes》中,超越了基本的CSS Shapes,并向大家展示了如何使用它们给Web布局带来的突破。

CSS Shapes相关的文章在互联网上有很多,特别是去年,@Rachel Anddrew写了一篇教程,得新介绍了CSS Shapes。这篇文章向大家介绍了使用CSS Shapes的基本知识。如果你想了解CSS Shapes中的shape-outsideshape-marginshape-image-threshold等属性如何使用,那么这篇文章是篇不错的教程。虽然介绍CSS Shapes的文章很多,甚至使用CSS Shapes创建的示例也很多,可是很多都没有超出CSS的基本形状,即circle()ellipse()inset(),即使使用polygon()的例子也很少超出一些局限性。而CSS Shapes形状所能提供的创造力是非常强大的,如果一直还是用一些基本形状的示例来阐述CSS Shapes魅力的话,难免会令人感到失望。庆幸的是,@Andy Clarke的文章让你能重新感到CSS Shapes魅力所在。因为他以自己的灵感和想象力,为我们展示了使用CSS Shapes五种不同类型的布局(V字型布局Z型布局曲线布局对角线形状布局旋转布局),而这些布局效果更独特,更吸引人。

我们来看一个V字型的布局:

<main>
    <div class="placeholder"></div>
    <h1>V-Shapes</h1>
    <p>After giving the ... </p>
    <!-- 其他的HTML结构 -->
</main>

其中div.placeholder是用来当作CSS Shapes的占位符,比如们要的一个V字型,其实是两个三角形拼出来的:

如此一来,可以借助::before::after来使用两个图片,不过对于类似这样的三角形,还可以借助polygon()来绘制。为了方便,可以基于像**Clippy**这样的在线工具来完成:

.shape-placeholder {
    width: 100%;
    
    &::before,
    &::after {
        content: '';
        width: 35vw;
        height: 100vh;
    }
    
    &::before {
        float: left;
        shape-outside: polygon(0 0, 0% 100%, 70% 100%);
    }
    
    &::after {
        float: right;
        shape-outside: polygon(100% 0, 30% 100%, 100% 100%);
    }
}

得到的效果如下:

利用类似的原理,还可以实现很多其他的效果。

当然,很多时候一些形状是我们需要借助其他工具来帮助我们完成的。这样的工具在Chrome和Firefox浏览器都提供了相应的插件(**CSS Shapes Editor **),这些工具具体的使用可以阅读下面的文章:

相比而言,Firefox下的工具更好用一些。假设你想让文本围绕下图来做布局,你可以先用图片当作背景图,成为CSS Shapes的一个占位图,比如下面这张图:

接着先随便绘制几个点,使用polygon(55px -1px, 9.57% 8.45%),再借助浏览器插件跟着背景图来描边(拖动点,添加点):

然将最终的polygon()的值复制出来给shape-outside:

shape-outside: polygon(55px -1px, 9.57% 8.45%, 15.06% 17.15%, 0.41% 20.5%, -6.92% 29.07%, -22.67% 35.16%, -50.89% 41.37%, -75.8% 43.73%, -79.44% 49.93%, -58.94% 52.06%, -47.95% 47.33%, -3.26% 39.75%, -9.85% 47.7%, -39.52% 51.3%, -64.43% 58.76%, -73.59% 77.77%, -94.47% 84.85%, -93.73% 90.56%, -53.07% 84.1%, -40.26% 67.45%, 0.77% 67.58%, -8.42% 80.59%, -21.22% 90.42%, -20.84% 99.38%, -4.57% 97.33%, 13.13% 101.34%, 33.12% 94.38%, 43.75% 74.89%, 53.42% 62.5%, 67.94% 37.95%, 78.99% 24.94%, 101.86% 17.67%, 96.55% 11.86%, 74.31% 6.78%, 57.63% 1.97%)

最终你能看到的效果如下:

如此一来,你可以发挥你的想象力和创造力,做出任何你觉得很有意思的东西。

第五届CSS Conf会议@勾股@张鑫旭 两位大大分享的主题中都提到CSS Shapes相关的特性。

Flexbox 和 Grid 布局中如何控制剩余的项

今天和同事一起探讨了Flexbox和Grid布局中剩余项的控制。这里将相关过程和涉及到的东西在这里聊聊。比如像下图这样的一个效果:

在Flexbox布局中我们很好的处理最后一行单一的项目(Flex项目),而在Grid布局中最后一行单一的项目(Grid项目)就无法自动跨列:

左侧红色为Flexbox布局,右侧绿色为Grid布局。

就上述而言,最后一行出现单一项目有点类似于印刷领域。在印刷领域,段落末尾不占一行的单词被称为**寡妇**。

在分页媒体查询和多列布局中的这些排版行为可以使用CSS的属性windoworphans来处理。

回到我们的示列当中来,如果我们在Grid布局中,也要能像Flexbox布局一样,很好的处理最后一项。那么这个时候我们就需要借助CSS伪类选择器来处理。即结合:last-child:nth-child。通过这两个伪类选择器我们可以检测一个项目是不是寡妇(独立的一个),并相应的调整其样式。对于该技术,@Heydon Pickering在其博客中有过相关的介绍,而且将该技术称为 数量查询(Quantity Queries) ,也有人将该技术称为取模

利用数量查询特性,我们可以对最后一个网格项做相应的处理:

.item:last-child:nth-child(3n - 2) {
    grid-column: span 3;
}

另外一个场景,Flex项目设置了一个max-width,而且容器设置了justify-content的值为centerspace-aroundspace-betweenspace-evenly时,最后一行的效果如下图所示。

而我们实际想要的效果如Grid布局一样:

对于这样的效果,在Grid中很轻易就能完成:

.grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
    grid-gap: 10px;
}

但在Flexbox中就没有那么容易了。要实现同等的效果,需要借助额外的手段,即在Flexbox容器中添加空的Flex项目。这个空标签的数量我们可以下面的JavaScript来帮助我们自动插入:

function checkCount(current_total, next){  
    if(current_total % 12){ // 12 is smallest number that 3 and 4 go in to
        current_total += 1; // Proper way to do ++
        checkCount(current_total, next); // Recursion!
    } else {
        next(current_total);
    }
}
function addDummyElementsToCards(container_id){  
    var container = document.getElementById(container_id);
    var card_count = container.children.length;
    checkCount(card_count, function(final_count){
        var dummy_element;
        for(i = 0; i < card_count; i++){
            dummy_element = document.createElement('div');
            dummy_element.className = 'dummy_card';
            container.appendChild(dummy_element);
        }
    });
}
addDummyElementsToCards('flexbox');  

同时给.dummy_card添加样式:

.dummy_card {
    height: 0;
    flex: 1 1 310px;
}

其中310pxflex-basis,你可以根据自己的需要来设置。

另外还有一个有关于Flexbox更全面的示例:

更为复杂的是我们并不知道容器的宽度是多少,也不清楚每行显示多少个项目。针对于这样的场景,我们使用CSS Grid中的auto-fillauto-fit关键词来替代repeat()函数中的具体数值。这样我们就不需要考虑一行到底是显示多少个项。同时配合minmax()函数来设置一个最小宽度,比如310px,再设置一个最大宽度1fr。这样就可以更好的实现响应式的卡片布局:

有关于这方面的介绍还可以阅读:

CSS实现瀑布流布局方案

在《纯CSS实现瀑布流布局》一文中,我们介绍了在CSS中如何实现瀑布流布局(不借助任何JavaScript来实现):

有关于CSS实现瀑布流布局的相关教程还可以阅读:

@tobiasahlin在他的新教程中《CSS masonry with flexbox, :nth-child(), and order》介绍了Flexbox布局中如何使用伪类选择器:nth-child()order实现的瀑布流布局:

具体代码不在这里展示了,感兴趣的同学可以阅读@tobiasahlin写的教程!

除此之外,@Emmanuel Olaojo写了一个很轻的JavaScript库,用少许的JavaScript代码配合CSS Grid布局实现的一个瀑布流布局,这个效果被称为Magic Grid

更厉害的是,随着CSS Houdini成熟之后,我们可以借助CSS Houdini的Layout Worklet相关API封装一个瀑布流的组件,然后在实际使用的时候,我们只需要像下面这样即可:

<div id="masonry">
    <div class="item"></div>
    <!-- 省略N个div.item -->
    <div class="item"></div>
</div>

#masonry {
    display: layout(masonry);
    --padding: 20;
    --columns: 3;
}

详细的可以点击这里进行了解

有关于CSS Houdini中Layut API的介绍可以点击这里进行了解

如果你是位React的爱好者,还可以@RyLee Harrison写的一个示例,该示例React和CSS Houdini结合起来创建的瀑布流布局。当然,你要是不喜欢React,而是喜欢Vue,不仿尝试着使用Vue和CSS Houdini结合起来,也创建一个瀑布流的布局。期待您的分享。

CSS实现对角容器

对角容器(Diagonal Containers)是什么呢?上个图,来说明一下这里所说的对象容器:

@Sebastiano Guerriero把这个效果封装成了一个UI组件(Diagonal Hero Component),它是Hero Component组件的扩展。

实现这个效果的方法有很多种,比如:

而@Sebastiano Guerriero的《Diagonal Containers in CSS》教程中向大家介绍了如何使用CSS的clip-path实现这个效果。

.hero--diagonal {
    position: relative;
    background-color: transparent;

    &::before {
        content: '';
        position: absolute;
        top: -50px;
        left: 0;
        width: 100%;
        height: calc(100% + 100px);
        clip-path: polygon(0% 0%, 100% 50px, 100% 100%, 0% calc(100% - 50px));
    }

    .hero__content {
        position: relative;
        z-index: 1;
    }
}

添加一点修饰样式,看到的效果如下:

如果.hero--diagonal::before中使用了背景图片,还需要添加下面的样式:

.hero--diagonal::before {
    // ...
    background-size: cover;
    background-repeat: no-repeat;
    background-position: center center;
}

CSS Subgrid

@rachelandrew 在Newcastle上分享了一个主题Grids All The Way Down

@rachelandrew在该主题中花了大部的篇幅来解释 CSS Grid Layout Module Level 2 规范中的 subgrid。对于subgrid相关的讨论,@rachelandrew已经有过多次的讨论了,比如在2018年7月份的时候就在Smashing Magazine上就有聊过CSS Grid Level 2中的subgrid

虽然目前还没有任何浏览器支持subgrid特性,但不用过于担心,我相信这一天很快就会到来的。 不过你要是想了解一些有关于subgrid相关的特性,可以看看@rachelandrew给我们提供的截图。而且@rachelandrew也将会不定期的将有关于subgrid相关的Demo放到 Grid by Example 中。

Grid by Example 是一个专门介绍 CSS Grid的网站,该网站涵盖有关于CSS Grid的示例、教程、视频和资源等。

用于列和行的subgrid

.d{
    gap: 10px;
    grid-column: 2 / 5;
    grid-row: 2 / 4;

    display: grid;
    grid-template-columns: subgrid;
    grid-template-rows: subgrid;
}

在本例中,subgrid位于父网格的列和行上。这意味着在subgrid的网格区域中没有隐式网格,因为两个维度都绑定到父网格上的可用轨道上。

仅用于列的subgrid

.d{
    gap: 10px;
    grid-column: 2 / 5;
    grid-row: 2 / 4;

    display: grid;
    grid-template-columns: subgrid;
}

在这个示例中,只对列使用了subgrid。这意味着我们可以使用隐式网格轨道来添加尽可能多的行。行使用它们自己的大小,就像嵌套网格不使用subgrid一样。

仅用于行的subgrid

.d{
    gap: 10px;
    grid-column: 2 / 5;
    grid-row: 2 / 4;

    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-rows: subgrid;
}

在本例中,将行设置为subgrid,并将列定义为1fr。正如你所看到的,列轨道不与父轨道对齐,因为它们使用自己的网格定义。

根据subgrid网格线来设置子网格项目的位置

一旦在grid-template-columnsgrid-template-rows中设置了值为subgrid,就表示你创建了一个subgrid(子网格),那么他就会像grid一样,有自己的网格线(显式的或隐式的)。在子网格中其网格线也是从1开始,如上图所示。如果我们要在子网格中设置子网格项目的位置,可以根据子网格线编号(而不是父网格线编号)来设置。

父网格的网线名会传递到子网格中

如果我们在父网格中指定了网格线名称,那么这些网格线的名称会被传递到子网格中,并添加在子网格中定义的任何名称。

.grid {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr [col-start] 1fr 1fr 1fr [col-end] 1fr 1fr 1fr;
    grid-template-rows: repeat(4, minmax(100px, auto));
}

.item {
    grid-column: 2 / 7;
    grid-row: 2 / 4;

    display: grid;
    grid-template-columns: subgrid;
    grid-template-row: subgrid;
}

.subitem {
    grid-column: col-start / col-end;
    grid-row: 1 / 3;
}

显式给子网格设置网线名

在子网格中我们也可以像父网格一样,给子网格添加网线名称:

比如上图,在父网格中有两条列网格线分别叫col-startcol-end,同时我们在相同的网格线位置处为子网格重新设置网格线的名称为col-start sub-ccol-end sub-f

.grid {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr [col-start] 1fr 1fr 1fr [col-end] 1fr 1fr 1fr;
    grid-template-rows: repeat(4, minmax(100px, auto));
}

.item {
    grid-column: 2 / 7;
    grid-row: 2 / 4;

    display: grid;
    grid-template-columns: subgrid [sub-a] [sub-b] [sub-c] [sub-d] [sub-e] [sub-f];
    grid-template-row: subgrid;
}

.subitem {
    grid-column: sub-c / sub-f;
    grid-row: 1 / 3;
}

子网格会继承父网格的gap

在子网格中会继承父网格的gap值。当然你也可以在子网格中改变gap值。

subgrid除了上述具有的特性之外,还有其他的一些特性,比如 子网格中网格项目的大小可以更改父网格轨道的大小。如果你想了解subgrid更多的特性或相关变化,可以点击下面这些链接进行了解:

你所不知道的display属性

块元素和内联元素是学习CSS最早接触的概念之一。其中display属性可以让我改变元素的盒子模式。但一直以来,我们所知道的display只是用来改变元素盒子模式用于布局,却很少有深入的去了解该属性。@Rachel Andrew在她的新博文《Digging Into The Display Property: The Two Values Of Display》中从另一个角度向大家阐述了display属性。

该文没有介绍怎么去讨论display属性各个值如何使用,而是集中讨论displayflexgrid值。因为取值flexgrid时定义了一些有趣的东西,而只有了解或理解了这些东西才能更好地,更轻松的使用CSS来进行布局。

CSS Display Module Level 3 中对早期的dsplay的取值方式有所更改。对于我们这些从事CSS工作多年的人来说,乍一看这似乎有些不寻常,但我认为这些变化确实有助于解释当我们更改元素上的display值时发生了什么。

其实在我看到这篇文章的时候,我也非常的诧异,display还能使用两个值。这是什么鬼,后来阅读了一下规范才有所了解。对于CSS中的几个概念,块元素行内元素文档流正常聊这里就不做相应的阐述,不了解这几个概念的同学最好还是花点时间去了解一下。这里着重提一下display内部(Inner)和外部(Outer)值

在**CSS Display Module Level 3** 中,display的值定义为两个关键词。这些关键词定义display的外部值,它将是inlineblock,将定义元素在布局中与其他元素一起的行为。它们还定义了元素的内部值,用于指定该元素的直接子元素的行为方式。

这意味着,我们以前使用的 display: grid将会变成display: block grid。即,你需要的是一个块级的网格容器,一个包含所有块属性的元素,可以给这个元素指定widthheightmarginpadding,它会拉伸来填充容器。然而,该容器的子元素已经赋予了grid的内部值,因此成为网格项目。网格项目的行为会根据网格规范中的指定的display行为,告诉浏览器我们想要的布局是什么。这种思考方式是非常有用的,它能够直接解释我们如何使用各种布局方法。比如,你使用了display: inline flex,表示的是容器是一个内联元素框,其中包含的子元素是Flex项目。

回过头来,这样我们就会碰到display取一个值和两个值的状态,那么他们的取值之间有没有什么相应的关系呢?可以看看下表:

单个值 两个值 描述
block block flow 正常流内的块盒子
flow-root block flow-root 定义一个BFC的块盒子
inline inline flow 正常流内的内联盒子
inline-block inline flow-root 定义一个BFC的内联盒子
list-item block flow list-item 正常文档流和带有附加标记的块盒子
flex block flex 带有内部伸缩布局的块盒子
inline-flex inline flex 带有内部伸缩布局的内联盒子
grid block grid 带有内部网格布局的块盒子
inline-grid inline grid 带有内部网格布局的内联盒子
table block table 带有内部表格布局的块盒子
inline-table inline table 带有内部表格布局的内联盒子

上表就是display属性取一个值(老的用法)和两个值(新的用法)之间的映射关系,所以你也不用担心这会破坏我们以前的使用。我们来看一个示例:

.container {
    display: block grid;
}

上面的代码等同于:

.container {
    display: grid;
}

如果我们在.container中使用的是inline grid

.container {
    display: inline grid;
}

那么它就等同于:

.container {
    display: inline-block;
}

因此在使用的时候,可以根据上表来对照。

也就是说,当你在CSS中定义一个框(盒子,即元素)的布局时,可以根据它与布局中其他框之间的关系来定义这个框的行为。同时还定义了该框子元素的行为。在显式地将这些值声明为两个独立的东西之前,你可以以这种方式考虑,这将帮助你理解更改display的值会发生什么?

有关于这方面更详细的介绍,可以阅读@Rachel Andrew的《Digging Into The Display Property: The Two Values Of Display》教程。

小结

在这一期中将会向大家介绍CSS Shapes给布局带来的变化(Web艺术)、在Flexbox和Grid布局中如何控制最后一行的剩余项以及如何使用Flexbox和Grid实现响应式布局、CSS怎么实现瀑布流布局、CSS怎么构建对角容器和两个还未得到支持的CSS特性:subgrid带两个属性值的displayNike Huaraches Are Hyped