美文网首页前端开发
CSS布局与响应式设计(下)

CSS布局与响应式设计(下)

作者: Leesper | 来源:发表于2019-04-27 23:57 被阅读0次

    五. 网格布局

    CSS的网格布局是一种二维的,基于网格的前端布局技术,完全改变了设计用户界面的方式。从布局发展的过程来看,从一开始基于tables,到float,再到positioning和inline-block,这些方法中充斥着大量的奇技淫巧,但连最基本的居中定位都做的不好。flex布局是一种很强大的基于轴线布局的一维布局方式,而网格布局则真正做到了基于行列单元格的二维布局,实际上flex搭配grid能工作的很好。网格布局与media query相结合同样也能通过寥寥数行代码实现响应式设计。

    网格布局将网页划分为不同的网格,通过组合不同网格做出各种各样的布局。以前通过复杂css框架达成的效果现在浏览器内置了,所以称它为2019年必学的新布局方法,但目前浏览器的支持还存在一定问题,但以后会越来越好。

    5.1 基本概念

    同样地,在网格布局中只要给html元素增加了相应的display属性,该元素就变成网格容器。而容器的直接子元素被称为(item),注意是直接子元素,因为display属性不可继承。网格布局还有一些专门的术语:行和列,网格线,网格轨道(track)和网格区域,下面来一一介绍。

    5.1.1 行和列

    在网格布局中,水平区域称之为,垂直区域称之为单元格是行和列的交叉区域。

    row-and-col.png

    5.1.2 网格线

    网格布局中的重要概念,分为水平网格线和垂直网格线,用于根据网格线定位位置。

    grid-lines.png

    5.1.3 网格轨道(grid track)

    网格轨道是两相邻网格线之间的区域。

    grid-track.png

    5.1.4 网格区域

    网格区域是4条网格线之间包围的区域,由1个或多个单元格构成。

    grid-area.png

    5.2 容器属性

    用display属性可以将元素定义为网格容器,并给它所包含的内容创建一个网格布局的上下文环境:

    .container {
      display: grid | inline-grid;
    }
    

    grid代表块级网格,inline-grid代表行内级网格,后者表示该容器本身体现为行内元素,但内容却是网格布局的。开启网格布局之后,接下来我们来看看如何对布局内容进行精细化调整。

    5.2.1 行高和列宽

    grid-template-rows和grid-template-columns分别用于定义行高和列宽。值表示网格大小,值之间的空格表示网格线。语法格式如下:

    .container {
      grid-template-columns: <track-size> ... | <line-name> <track-size> ...;
      grid-template-rows: <track-size> ... | <line-name> <track-size> ...;
    }
    

    比如,定义一个3行5列的网格布局最后一行和中间一列使用auto关键字来表示由浏览器自己决定长度:

    .container {
      grid-template-columns: 40px 50px auto 50px 40px;
      grid-template-rows: 25% 100px auto;
    }
    
    grid-template-rc.png

    上图中的每条网格线都得到了自动命名的4个名称。但我们可以使用方括号指定每一根网格线的名字,方便以后的引用。网格布局允许同一根线有多个名字,比如[fifth-line row-5],给上面的布局重新定义一下网格线名称,可以这么写:

    .container {
      grid-template-columns: [first] 40px [line2] 50px [line3] auto [col4-start] 50px [five] 40px [end];
      grid-template-rows: [row1-start] 25% [row1-end] 100px [third-line] auto [last-line];
    }
    
    named-grid-template-rc.png

    如果觉得这么写麻烦,还可以使用repeat()函数来指定行高和列宽。例如这样的写法:

    .container {
      grid-template-columns: repeat(3, 20px [col-start]);
    }
    

    等价于:

    .container {
      grid-template-columns: 20px [col-start] 20px [col-start] 20px [col-start];
    }
    

    fr关键字是fraction的缩写,如果两列的宽度分别为1fr和2fr,就表示后者按前者的两倍分配空闲空间,例如设置诶每个项占满容器的1/3:

    .container {
      grid-template-columns: 1fr 1fr 1fr;
    }
    

    因为可以与绝对长度的单位结合使用,空闲空间是在排除任何固定尺寸的项之后才计算的。比如下面这个例子中fr的计算就没有包含50px:

    .container {
    grid-template-columns: 1fr 50px 1fr 1fr;
    }
    

    minmax()函数产生一个长度范围,表示长度就在这个范围之中,它接受两个参数,分别为最小值和最大值。比如:

    grid-template-columns: 1fr 1fr minmax(100px, 1fr);
    

    上面这行代码中minmax(100px, 1fr) 表示长度不小于100px,不大于1fr。

    还有个auto-fill关键字可以用来表示自动填充。有时单元格的大小是固定的,但是容器的大小不确定。如果希望每一行(或每一列)容纳尽可能多的单元格,就需要用到自动填充。

    .container {
      display: grid;
      grid-template-columns: repeat(auto-fill, 100px);
    }
    

    表示每列宽度100px,然后自动填充,直到容器不能放置更多的列。很多常见的实例用网格布局都能几行代码轻松搞定,比如两栏式布局,其中左边栏70%,右边栏30%:

    .wrapper {
      display: grid;
      grid-template-columns: 70% 30%;
    }
    

    十二网格布局:

    grid-template-columns: repeat(12, 1fr);
    

    5.2.2 行间距与列间距

    grid-row-gap属性定义行与行的间隔;grid-column-gap属性定义列与列的间隔,这个属性相当于是定义了网格线的大小。语法规则如下:

    .container {
      grid-column-gap: <line-size>;
      grid-row-gap: <line-size>;
    }
    

    下面这例子设置列间距为10px,行间距为15px:

    .container {
      grid-template-columns: 100px 50px 100px;
      grid-template-rows: 80px auto 80px; 
      grid-column-gap: 10px;
      grid-row-gap: 15px;
    }
    
    grid-gap.png

    grid-gap属性将成为二者的合并简写形式。如果grid-gap省略了第二个值,浏览器将认为第二个值等于第一个值。

    根据最新标准,上面三个属性名的grid-前缀已经删除,grid-column-gap和grid-row-gap写成column-gap和row-gap,grid-gap写成gap

    5.2.3 grid-template-areas属性

    grid-template-areas属性通过引用由grid-area属性(下面会介绍)定义的网格区域名称来定义一个网格模板。重复指定的名称表示该区域跨越多个单元格。点号.表示空的单元格(不需要利用的区域)。该语法展示的是网格的结构形状。例如下面这段css代码:

    .item-a {
      grid-area: header;
    }
    .item-b {
      grid-area: main;
    }
    .item-c {
      grid-area: sidebar;
    }
    .item-d {
      grid-area: footer;
    }
    
    .container {
      display: grid;
      grid-template-columns: 50px 50px 50px 50px;
      grid-template-rows: auto;
      grid-template-areas: 
        "header header header header"
        "main main . sidebar"
        "footer footer footer footer";
    }
    

    将定义如下的4列宽,3列高的网格布局。第一行由header全部占满;第二行为一个跨两列的main区域以及一个sidebar区域,最后一行由footer全部占满。

    grid-template-areas.png

    注意,区域的命名会影响到网格线。每个区域的起始网格线,会自动命名为区域名-start,终止网格线自动命名为区域名-end。比如,区域名为header,则起始位置的水平网格线和垂直网格线叫做header-start,终止位置的水平网格线和垂直网格线叫做header-end。

    grid-template属性是grid-template-rows,grid-template-columns和grid-template-areas的简写形式,因此:

    .container {
      grid-template:
        [row1-start] "header header header" 25px [row1-end]
        [row2-start] "footer footer footer" 25px [row2-end]
        / auto 50px auto;
    }
    

    其实等价于:

    .container {
      grid-template-rows: [row1-start] 25px [row1-end row2-start] 25px [row2-end];
      grid-template-columns: auto 50px auto;
      grid-template-areas: 
        "header header header" 
        "footer footer footer";
    }
    

    5.2.4 grid-auto-flow属性

    划分网格以后,容器的子元素会按照顺序,自动放置在每一个网格中,该属性指定放置的顺序,若取值为row则表示"先行后列",若取值为column则表示"先列后行"。如果再加上了dense则表示"尽可能紧密填满”,也就是说如果有尺寸较小的元素,则尽量往前面放。

    取值:

    1. row:行优先布局,按次序填满一行,如果有需要则增加新的行(默认)
    2. column:列优先布局,按次序填满一列,如果有需要则增加新的列
    3. dense:如果有较小的项放在后面,算法会优先考虑将其放到前面来填补网格中的空余空间

    用下面的html代码举个例子:

    <section class="container">
      <div class="item-a">item-a</div>
      <div class="item-b">item-b</div>
      <div class="item-c">item-c</div>
      <div class="item-d">item-d</div>
      <div class="item-e">item-e</div>
    </section>
    

    定义一个行优先的,2行5列的网格布局如下:

    .container {
      display: grid;
      grid-template-columns: 60px 60px 60px 60px 60px;
      grid-template-rows: 30px 30px;
      grid-auto-flow: row;
    }
    

    然后我们放置网格项:

    .item-a {
      grid-column: 1;
      grid-row: 1 / 3;
    }
    .item-e {
      grid-column: 5;
      grid-row: 1 / 3;
    }
    

    就能得到:

    auto-flow-row.png

    如果改为列优先,则变为:

    auto-flow-col.png

    5.2.5 新增网格的列宽和行高

    有时候,一些项目的指定位置,在现有网格的外部。比如网格只有3列,但是某一个项目指定在第5行。这时,浏览器会自动生成多余的网格(称为隐式网格轨道)以便放置项目,如果不指定grid-auto-columns和grid-auto-rows这两个属性,浏览器会完全根据单元格内容的大小,决定新增网格的列宽和行高。

    grid-auto-columns属性指定浏览器自动创建的多余网格的列宽,grid-auto-rows属性指定浏览器自动创建的多余网格的行高。可以使用长度,百分比和fr作为单位。比如有如下的2*2网格:

    .container {
      grid-template-columns: 60px 60px;
      grid-template-rows: 90px 90px
    }
    
    grid-auto-rc.png

    现在有一个网格的位置被放置在了这个2*2的网格外面:

    .item-a {
      grid-column: 1 / 2;
      grid-row: 2 / 3;
    }
    .item-b {
      grid-column: 5 / 6;
      grid-row: 2 / 3;
    }
    

    这些自动生成的网格轨道的大小会完全根据所放置的项的大小来决定(注意第3列和第4列):

    grid-auto-rc2.png

    我们可以给这些隐式网格轨道指定列宽或者行高:

    .container {
      grid-auto-columns: 60px;
    }
    
    grid-auto-rc3.png

    5.2.6 grid属性

    grid属性是grid-template-rows,grid-template-columns,grid-template-areas,grid-auto-rows,grid-auto-columns和grid-auto-flow五个属性的简写形式。

    取值:

    1. none:设置所有的5个属性为默认值
    2. <grid-template>:与grid-template简写属性相同
    3. <grid-template-rows> / [ auto-flow && dense? ] <grid-auto-columns>?:设置grid-template-rows的值;如果auto-flow出现在反斜杠的右边,则表示先列后行;如果再加上dense关键字则表示紧凑模式;如果不设置grid-auto-columns,则默认为auto
    4. [ auto-flow && dense? ] <grid-auto-rows>? / <grid-template-columns>:设置grid-template-columns的值;如果auto-flow出现在反斜杠的左边,则表示先行后列;如果再加上dense关键字则表示紧凑模式;如果不设置grid-auto-rows,则默认为auto

    下面的css是等价的:

    .container {
        grid: 100px 300px / 3fr 1fr;
    }
    
    .container {
        grid-template-rows: 100px 300px;
        grid-template-columns: 3fr 1fr;
    }
    

    下面这段css也是等价的:

    .container {
        grid: auto-flow / 200px 1fr;
    }
    
    .container {
        grid-auto-flow: row;
        grid-template-columns: 200px 1fr;
    }
    

    以及:

    .container {
        grid: auto-flow dense 100px / 1fr 2fr;
    }
    
    .container {
        grid-auto-flow: row dense;
        grid-auto-rows: 100px;
        grid-template-columns: 1fr 2fr;
    }
    

    还有:

    .container {
        grid: 100px 300px / auto-flow 200px;
    }
    
    .container {
        grid-template-rows: 100px 300px;
        grid-auto-flow: column;
        grid-auto-columns: 200px;
    }
    

    grid属性还有一种更复杂的写法:一次性把grid-template-areas,grid-template-rows和grid-template-columns的设置都搞定,所有其他的设置都保持默认值,比如下面这两段css是等价的:

    .container {
        grid: [row1-start] "header header header" 1fr [row1-end]
              [row2-start] "footer footer footer" 25px [row2-end]
              / auto 50px auto;
    }
    
    .container {
        grid-template-areas: 
          "header header header"
          "footer footer footer";
        grid-template-rows: [row1-start] 1fr [row1-end row2-start] 25px [row2-end];
        grid-template-columns: auto 50px auto;    
    }
    

    这个属性的用法比较灵活,需要多实践才能熟练掌握。

    5.2.7 单元格对齐方式

    单元格的对齐有垂直和水平两个方向。水平方向的单元格对齐使用justify-items属性,垂直方向的单元格对齐使用align-items属性。

    它们都有4种取值:

    • start:对齐单元格的起始边缘。

    • end:对齐单元格的结束边缘。

    • center:单元格内部居中。

    • stretch:拉伸,占满单元格的整个宽度(默认值)

    start对齐单元格的起始边缘(对于水平方向来说是左边缘,对于垂直方向来说是上边缘):

    .container {
      justify-items: start;
    }
    
    start1.png
    .container {
      align-items: start;
    }
    
    start2.png

    end对齐单元格的结束边缘(对于水平方向来说是右边缘,对于垂直方向来说是下边缘):

    .container {
      justify-items: end;
    }
    
    end1.png
    .container {
      align-items: end;
    }
    
    end2.png

    center将单元格内部居中:

    .container {
      justify-items: center;
    }
    
    center1.png
    .container {
      align-items: center;
    }
    
    center2.png

    stretch将单元格拉伸,占满单元格整个宽度(默认值):

    .container {
      justify-items: stretch;
    }
    
    .container {
      align-items: stretch;
    }
    
    stretch.png

    place-items属性是align-items属性和justify-items属性的合并简写形式,如果省略第二个值,则浏览器认为与第一个值相等。

    5.2.8 内容区域对齐方式

    如果所有的网格项的大小都是用固定的单位比如px指定的,那么网格本身的尺寸就有可能比容纳网格的容器尺寸更小,这个时候我们可以通过一些属性来设置网格容器中的网格如何对齐,对齐的方式仍然包含水平和垂直两个方向。

    justify-content属性用于水平方向对齐,而align-content属性用于垂直方向对齐。它们都可以选择如下取值:

    1. start:对齐容器的起始边框
    2. end:对齐容器的结束边框
    3. center:容器内部居中
    4. stretch:项的大小没有指定时,拉伸占据整个网格容器
    5. space-around:每个项两侧的间隔相等。所以,项之间的间隔比项与容器边框的间隔大一倍
    6. space-between:项与项的间隔相等,项与容器边框之间没有间隔
    7. space-evenly:项与项的间隔相等,项与容器边框之间也是同样长度的间隔

    start对齐容器的起始边框(水平方向上是左边框,垂直方向上是上边框):

    .container {
      justify-content: start;
    }
    
    content-start.png
    .container {
      align-content: start; 
    }
    
    content-start2.png

    end对齐容器的结束边框(水平方向上是右边框,垂直方向上是下边框):

    .container {
      justify-content: end; 
    }
    
    content-end.png
    .container {
      align-content: end;   
    }
    
    content-end2.png

    center将网格在容器内居中显示:

    .container {
      justify-content: center;  
    }
    
    content-center.png
    .container {
      align-content: center;    
    }
    
    content-center2.png

    stretch使得网格拉伸占据整个容器:

    .container {
      justify-content: stretch; 
    }
    
    content-stretch.png
    .container {
      align-content: stretch;   
    }
    
    content-stretch2.png

    space-around使得两侧的间隔相等,因此中间部分的要比两边的间隔大一倍:

    .container {
      justify-content: space-around;    
    }
    
    content-space-around.png
    .container {
      align-content: space-around;  
    }
    
    content-space-around2.png

    space-between使得项与项之间间隔相等:

    .container {
      justify-content: space-between;   
    }
    
    content-space-between.png
    .container {
      align-content: space-between; 
    }
    
    content-space-between2.png

    space-evenly使得项与项之间间隔相等,也包括项与边框之间的间隔:

    .container {
      justify-content: space-evenly;    
    }
    
    content-space-evenly.png
    .container {
      align-content: space-evenly;  
    }
    
    content-space-evenly2.png

    5.3 项属性

    对于一个网格项而言,floatdisplay: inline-blockdisplay: table-cellvertical-aligncolumn-*属性都将失效。

    5.3.1 根据网格线定位项的位置

    网格布局是通过网格线来定位网格项的位置的。grid-column-start属性定位左边框所在的垂直网格线,grid-column-end属性定位右边框所在的垂直网格线,有一个grid-column属性能够提供同时定义这两个属性的简写形式。grid-row-start属性定义上边框所在的水平网格线,grid-row-end属性定义下边框所在的水平网格线,有一个grid-row属性能够提供同时定义这两个属性的简写形式。这四个属性的值,除了指定为第几个网格线,还可以指定为网格线的名字。这四个属性的值还可以使用span关键字,表示"跨越",即左右边框(上下边框)之间跨越多少个网格。四个属性都可以使用auto关键字表示自动放置/跨越/默认单位为1的跨越。使用这四个属性,如果产生了项目的重叠,则使用z-index属性指定项目的重叠顺序。

    举例1:

    .item-a {
      grid-column-start: 2;
      grid-column-end: five;
      grid-row-start: row1-start
      grid-row-end: 3;
    }
    
    grid-rc1.png
    .item-b {
      grid-column-start: 1;
      grid-column-end: span col4-start;
      grid-row-start: 2
      grid-row-end: span 2
    }
    
    grid-rc2.png

    使用简写形式:

    .item-c {
      grid-column: 3 / span 2;
      grid-row: third-line / 4;
    }
    
    grid-rc.png

    5.3.2 grid-area属性

    grid-area属性用于给项命名,该名称可以在grid-template-areas中被引用到。或者用作grid-row-start、grid-column-start、grid-row-end、grid-column-end的合并简写形式,直接指定项目的位置。

    举例,第一种用法是给网格项命名:

    .item-d {
      grid-area: header;
    }
    

    第二种用法:

    .item-d {
      grid-area: 1 / col4-start / last-line / 6;
    }
    
    grid-area.png

    5.3.3 设置单元格内容位置

    设置单元格内容位置跟设置单元格对齐方式很像,都分为水平方向和垂直方向。区别在于前者是针对单个的项,而后者针对的是全部的项。

    justify-self属性在一个单元格中对齐项的水平位置,align-self属性在一个单元格中对齐项的垂直位置。这两个属性都可以取4个值:

    • start:对齐单元格的起始边缘。

    • end:对齐单元格的结束边缘。

    • center:单元格内部居中。

    • stretch:拉伸,占满单元格的整个宽度(默认值)

    start对齐单元格的起始边缘(水平方向上的左边缘和垂直方向上的上边缘):

    .item-a {
      justify-self: start;
    }
    
    self-start.png
    .item-a {
      align-self: start;
    }
    
    self-start2.png

    end对齐单元格的结束边缘(水平方向的右边缘和垂直方向的下边缘):

    .item-a {
      justify-self: end;
    }
    
    self-end.png
    .item-a {
      align-self: end;
    }
    
    self-end2.png

    center在单元格内部居中:

    .item-a {
      justify-self: center;
    }
    
    self-center.png
    .item-a {
      align-self: center;
    }
    
    self-center2.png

    stretch拉伸并占满单元格的整个宽度:

    .item-a {
      justify-self: stretch;
    }
    
    self-stretch.png
    .item-a {
      align-self: stretch;
    }
    
    self-stretch2.png

    place-self属性是align-self属性和justify-self属性的合并简写形式,如果省略第二个值,place-self属性会认为这两个值相等。以上就是网格布局所有的属性,要熟练掌握网格布局,还是需要在实践中多加练习。有了网格布局,原来我们要实现的很多复杂的响应式布局就能够得到极大的简化,这篇文章介绍了如何用网格布局来完全替代Bootstrap框架。

    六. 表格和字体的响应式设计

    响应式设计中表格,字体和图片也是要讨论的重要主题。这里简单介绍下响应式设计中如何处理表格和字体。下一部分会介绍响应式设计中的图片。

    6.1 响应式表格

    对于网页中出现的表格而言,如果列数超出一定的范围,那么可能需要两个方向的滚动条左右上下拖动来查看,这是一种很不好的用户体验。要尽量避免出现两个方向上的滚动条,不同的情况有不同的解决方案。对于表格有三种响应式设计的技巧:隐藏列(hidden columns),无表格设计(no more tables)以及表格内滚动(contained tables)。

    6.1.1 隐藏列

    隐藏列的设计技巧是当视口尺寸缩小时,根据信息的重要性选择隐藏一些列。沿着"从小处开始"的设计技巧,先思考什么信息最重要,对这部分信息进行保留,然后用display: none隐藏其他相对不那么重要的信息。这个技巧最大的问题是它向用户隐瞒了一些信息,所以使用时要谨慎。如果有可能的话,尽量使用缩写,而不是隐藏它。

    比如下面这个表格:

    <table class="scores__table">
      <thead>
      </thead>
    </table>
    

    我们可以设计当视口宽度小于等于499px时隐藏gametime列:

    @media screen and (max-width: 499px) {
      .gametime {
        display: none;
      }
    }
    

    6.1.2 无表格设计

    当视口宽度小于一定值时,表格将使用CSS重组成长列表而不是数据表。这种解决方案的好处在于所有数据都是可见的。以下面这个棒球得分表为例:

    team-table.png
    <table>
      <thead>
        <tr>
          <th>Team</th>
          <th>1st</th>
          <th>2nd</th>
          <th>3rd</th>
          <th>4th</th>
          <th>5th</th>
          <th>6th</th>
          <th>7th</th>
          <th>8th</th>
          <th>9th</th>
          <th>Final</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td data-th="Team">Toronto</td>
          <td data-th="1st">0</td>
          <td data-th="2nd">0</td>
          <td data-th="3rd">0</td>
          <td data-th="4th">4</td>
          <td data-th="5th">0</td>
          <td data-th="6th">1</td>
          <td data-th="7th">0</td>
          <td data-th="8th">0</td>
          <td data-th="9th">0</td>
          <td data-th="Final">5</td>
        </tr>
        <tr>
          <td data-th="Team">San Francisco</td>
          <td data-th="1st">0</td>
          <td data-th="2nd">0</td>
          <td data-th="3rd">0</td>
          <td data-th="4th">4</td>
          <td data-th="5th">0</td>
          <td data-th="6th">0</td>
          <td data-th="7th">0</td>
          <td data-th="8th">0</td>
          <td data-th="9th">0</td>
          <td data-th="Final">4</td>
        </tr>
      </tbody>
    </table>
    

    当视口宽度小于500px时变成长列表:

    team-table2.png
    table {
      border: 1px solid #ddd;
    }
    
    tr:nth-child(odd) {
      background-color: #f9f9f9;
    }
          
    @media screen and (max-width: 500px) {
      table, thead, tbody, th, td, tr {
        display: block;
      }
      thead tr {
        position: absolute;
        top: -9999px;
        left: -9999px;
      }
      td { 
        position: relative;
        padding-left: 50%; 
      }
      td:before { 
        position: absolute;
        left: 6px;
        content: attr(data-th);
        font-weight: bold;
      }
      td:first-of-type {
        font-weight: bold;
      }
    }
    

    6.1.3 表格内滚动

    表格内滚动是一种为超出视口宽度的表格添加水平滚动条的方法。首先将表格包括在div中,然后使用CSS将这个div设置成100%宽并设置overflow-x: auto即可。

    div {
      width: 100%;
      overflow-x: auto;
    }
    

    6.2 响应式字体

    网页上每一行文字的字数不能太长也不能太短。太短的话人为割裂了词组会导致句子含义难以理解;太长的话不容易定位下一行,用户读着读着就没耐心开始略读了。一个好的建议是网页上每行大约65个字符,字体足够大:至少16像素,行高至少1.2em。我们可以设置一些次要的断点媒体查询来改善字体的响应式设计,比如当视口宽度增加时给一些内容增加字号,增加内边距并增大图标的尺寸。

    七. 图片的响应式设计

    在打开网页所需的平均字节数中,图片的加载要耗去60%。所以我们在设计网页时对图片的使用一定要注意。响应式图片设计的目的是用最少的字节传输最高质量的图片,因为对于移动设备以及不太好的网络环境而言,图片精度太高容易增加页面的加载延迟。我们要避免出现图片放大后像素失真,图片无法加载和裁剪后看不到图像全貌等问题,使得图片在各种尺寸设备上表现良好,从而提升用户体验。

    Create a product, don't re-imagine one for small screens, great mobile products are created, never ported. --Brian Fling

    7.1 图片加载的性能优化

    我们通常要考虑图片的质量和大小。然而对于网页上的图片而言,我们只需要考虑大小就可以了,即图片的压缩等级和实际的分辨率。图片文件的大小往往取决于像素数和每个像素所占的比特数量。所以为了提高网站的性能,我们要使用尽可能小的图片尺寸和尽可能高的图片压缩率。常见的错误是使用了过大的图片尺寸和过高的图片质量。有人统计过,平均每个网页要发出56个左右的请求来加载图片,每次请求对页面加载来说都是一项成本,一个小的页面加载延迟都可能造成明显的流量和经济损失。Google页面加载每增加0.4到0.9秒,将导致流量和广告收入降低20%;Amazon页面加载延迟每增加100ms就意味着1%销售额损失。

    7.1.1 不使用固定大小图片

    不要使用固定大小的图片,因为它无法根据视口的尺寸来改变自身大小,应该使用百分比相对大小,比如max-wdith: 100%。对于台式机或笔记本电脑而言,不要假设视口尺寸和屏幕尺寸相同,也不要假设视口会一直保持相同的大小。使用max-width是一种优雅地响应视图区域变化的方法。如果想要两张图片并列对齐,使每个图像为可用宽度的一半,并留有10px的间隔,那么就可以使用calc()函数:

    img {
      width: calc((100% - 10px) / 2);
    }
    
    img: last-of-type {
      margin-right: 0;
    }
    

    7.1.2 使用特殊的CSS单位

    vh和vw两种单位分别用来表示视口的高度和宽度。一个vh单位对应1%视口高度,一个vw单位对应1%视口宽度,所以100vh表示100%视口高度,100vw表示100%视口宽度。另一种常见的响应式用例是调整图片尺寸来适应视口宽度或者高度的较小者或较大者,就可以分别使用vmin和vmax。

    7.1.3 栅格图和矢量图

    有两种不同的基础方法创建和存储图片,一种叫栅格图,另一种叫矢量图。前者是一种点阵图片,我们平时从照相机,扫描仪中得到的图片都是栅格图。而后者是用特殊格式描述的线条组成的矢量图形。矢量图片优于栅格图片的一点是浏览器可以渲染任意尺寸的矢量图片,当视口尺寸增加时,矢量图不会失真而产生锯齿。为了提升性能,建议为照片使用jpeg格式,为矢量图使用svg格式,如果是商标等logo图案,如果不能使用svg,则使用png。

    7.1.4 使用专业工具优化图片

    有一些专业的工具可以提供对图片的批量处理。使用ImageMagick可以转换图片格式,裁剪或者应用滤镜。可以生成同一幅图片的多种版本或不同的尺寸和格式。还可以使用Grunt任务来使用ImageMagick,比如Grunt的响应式图片插件,能够一次性得到许多不同质量的图片。还有其他一些工具可以使用,ImageOptim可以利用很多开源工具来生成无损图片。

    ImageMagick:

    Grunt:

    图片处理工具:

    7.1.5 对页面进行优化检查

    怎样才能检查页面上所有的图片都被优化了呢?有一个在线检查图片优化情况的工具叫PageSpeed Insights。可以用它的网页办来检查网站,还可以在开发者工具中使用它。最厉害的是它还有API接口,可以在终端下通过curl命令来检查。也可以把这个检查过程写入到推送代码到仓库时的构建测试中,使用grunt task runner来实现。grunt有PageSpeed Insights插件可供使用,参考资料:

    7.2 标记图片的技巧

    对移动网络而言,文件请求次数和请求文件的大小同样重要。我们还需要进一步优化减少请求图片的次数,而不仅仅是关注图片的大小。性能是真正响应式设计的基本组成部分,既要压缩图片,又要减少图片的数量。这一节我们将介绍一些减少图片请求次数的技巧。

    7.2.1 不要将文字保存成图片

    首先,不要将文字保存成图片,因为放大后会失真,还会使网页文件变大,造成延迟。使用图片形式的文字还有一个问题就是无法被搜索引擎找到,也不能被屏幕阅读器读取。正确的做法应该是直接把文本覆盖在图片上面,这样图片和文字就有更好的显示和放大效果,文本也可以被选中,并且这样还能更方便的使用CSS来添加效果,文件尺寸也更小。

    7.2.2 使用CSS技巧

    除了用来调整样式属性,CSS还可以用来实现其他图形效果,比如渐变,阴影,圆角或动画效果。但是要注意使用CSS生成这些视觉效果是有处理和渲染成本的,这在移动设备上尤其明显,要谨慎使用。

    关于如何提升网站在移动设备上的响应速度,可以参考这篇文章

    7.2.3 使用符号字符和图标字体

    还有一种方法可以避免使用图片并保持网站的响应性。如果要使用一些图形标志,比如箭头,星星或者桃心,可以从unicode字符集中找到这样的字符。

    另外,图标字体让修饰网站的常用图片和图标使用起来更方便。图标字体相比图片有很多优点,它们是矢量图形,可以无限缩放,整套图像可以以一套字体的形式下载,而只消耗很小的下载量。

    7.2.4 内嵌图片

    如果想要减少网页的文件请求数量,还可以利用代码实现内嵌图片,有两种实现方式:SVG或者数据URI。内嵌的SVG具有很好的移动端和PC端浏览器支持,优化工具还能极大减少SVG的字节。数据URI提供了一种将文件(比如图片)内嵌为base64编码的字符串的机制,它也具有很好的浏览器支持。它们也可以嵌入到CSS里。

    一些参考资料:

    7.3 完全响应式

    在不同的上下文环境中都使用同一张图片可能不是一个好的做法。使用媒体查询有它自身的一些局限性,首先它不一定能支持未来出现的一些平台,其次媒体查询只参考了视窗的大小而不是图片的实际尺寸。下面介绍的一些方法采用了另一种截然不同的新思路:给浏览器提供信息以让它在多张图片中做出最好的选择。

    7.3.1 srcset属性

    img元素的src属性只能提供一个图片文件,而srcset属性给同一张图片提供可选择的多个文件,可以为更高DPI显示器选择更高分辨率的图片文件,否则使用低分辨率图片文件。这样浏览器可以根据视口尺寸和设备性能做出最合适的选择。srcset 有两种自定义方式,一种使用 x 来区分设备像素比 (DPR),另一种使用 w 来描述图像的宽度。对设备像素比的反应:

    <img src="image_2x.jpg" srcset="image_2x.jpg 2x, image_1x.jpg 1x" alt="a cool image">
    

    srcset 设置为与逗号分隔的一连串 filename multiplier 对相等,其中每个 multiplier 必须是后跟 x 的整数。例如,1x 表示 1 倍显示屏,2x 表示像素密度为两倍的显示屏,如 Apple 的 Retina 显示屏。浏览器会下载与其 DPR 对应的最佳图片。另请注意,有一个作为备用的 src 属性。对图片宽度的反应:

    <img src="image_200.jpg" srcset="image_200.jpg 200w, image_100.jpg 100w" alt="a cool image">
    

    srcset 设置为与逗号分隔的一连串 filename widthDescriptor 对相等,其中每个 widthDescriptor 都以像素为测量单位, 并且必须是后跟 w 的整数。在这里,widthDescriptor 指定每个图片文件的自然宽度,使浏览器能够根据窗口大小和 DPR 选择要请求的最适当的图片。(如果没有 widthDescriptor,浏览器需要下载图片才能知道其宽度!)

    7.3.2 sizes属性

    有一点要说明的是,在 JavaScript 中,可以通过 currentSrc 获得 img 元素的来源。sizes 属性为浏览器提供了有关图片元素显示大小的信息,它实际上不会导致图片大小调整。该操作是在 CSS 中执行的!

    包含大小的图片宽度

    如果图片不以全窗口宽度显示会怎样?那么解析完HTML后,在解析CSS前浏览器就会预加载图片。问题是浏览器对图片尺寸一无所知,我们需要告诉浏览器图片的实际显示尺寸。除了 srcset 外,还需要sizes属性。向包含媒体查询的图片添加一个 sizes 属性和一个 vw 值。srcsetsizes 合起来可让浏览器知道图片的自然宽度以及图片相对于窗口宽度的显示宽度。 知道图片的显示宽度和可用图片文件的宽度后,浏览器在解析HTML时将获得下载具有满足其需求的适当分辨率且尽可能小的图片所需的信息。 这里是一个srcset与sizes配合使用的语法示例:

    <img  src="images/great_pic_800.jpg"
          sizes="(max-width: 400px) 100vw, (min-width: 401px) 50vw"
          srcset="images/great_pic_400.jpg 400w, images/great_pic_800.jpg 800w"
          alt="great picture">
    

    sizes 由以逗号分隔的 mediaQuery width 对组成。sizes 会在加载流程初期告诉浏览器,该图片会在点击 mediaQuery 时以某个 width 显示。实际上,如果 sizes 缺失,浏览器会将 sizes 默认为 100vw,表示它预计图片将以全窗口宽度显示。sizes 会为浏览器额外提供一条信息,以确保它根据图片的最终显示宽度下载正确的图片文件。说明一下,它实际上不会调整图片的大小 - 这是 CSS 的工作。在本示例中,如果浏览器的窗口宽度等于或小于 400 像素,浏览器知道图片将为全窗口宽度;如果窗口宽度大于 400 像素,则为一半窗口宽度。浏览器知道它具有两个图片选项:一个具有 400 像素的自然宽度,另一个具有 800 像素。

    7.3.3 picture元素

    新增的picture元素可以通过source元素提供可选择的源文件:

    <picture>
      <source srcset="kittens.webp" type="image/webp">
      <source srcset="kittens.jpeg" type="image/jpeg">
      <img src="kittens.jpeg" alt="Two grey tabby kittens">
    </picture>
    

    如果浏览器可以使用第一个资源,则使用它。否则就沿着列表查询下去。浏览器可以根据设备性能选择文件。上面这个HTML文件为支持webp的浏览器选择webp文件,jpeg作为备选资源。这种方式使得支持webp的平台充分利用高性能webp格式。为不支持它的平台提供替代方案。

    值得注意的是,我们一定要为image元素增加alt属性,这是一种无障碍性承诺,这是一种对于视觉障碍者友好的方式。关于 alt 属性的一般建议:

    • 对于重要图片来说,alt 属性应该具有描述性;

    • 对于纯装饰性的图片,alt 属性应该为空;

    • 应该为每张图片设置 alt 属性。

    八. 参考文献

    1. 特别鸣谢:Udacity前端工程师纳米学位

    2. 视口相关基础知识:

      在移动浏览器中使用viewport元标签控制布局

      A tale of two viewports - part one

      两个viewport的故事-第一部分(桌面版)

    3. CSS布局基础:

      学习CSS布局

    4. 响应式网页设计

    5. Flex布局相关:

      Flex布局教程:语法篇

      Flex布局教程:实例篇

      A Complete Guide to Flexbox

      交互式Flex布局教程

    6. Grid布局相关:

      CSS Grid 网格布局教程

      A Complete Guide to Grid

      How I stopped using Bootstrap’s layout thanks to CSS Grid

      交互式Grid布局教程

    相关文章

      网友评论

        本文标题:CSS布局与响应式设计(下)

        本文链接:https://www.haomeiwen.com/subject/mlqrnqtx.html