CSS3艺术 网页设计案例实战

978-7-115-51871-2
作者: 张偶
译者:
编辑: 俞彬

图书目录:

详情

本书以 CSS3 版本为平台,使用大量生动、美观的实例,系统地剖析了 CSS 与视觉效果相关的重要语法。全书分为13章,第1章回顾基础知识,第2章~第10章介绍了 CSS3 的伪元素,边框,背景, 阴影,剪切、滤镜和色彩混合,变量与计数器,变换、缓动、动画等在视觉展现方面的内容,第 11 章~第13 章探讨了 CSS3 在造型创意、动画创意和文字特效创意方面的设计思路。

图书摘要

网页创意视觉表达 技术指导&项目实战

CSS3艺术:网页设计案例实战

张偶 著

人民邮电出版社

北京

图书在版编目(CIP)数据

CSS3艺术:网页设计案例实战/张偶著.--北京:人民邮电出版社,2020.1

ISBN 978-7-115-51871-2

Ⅰ.①C… Ⅱ.①张… Ⅲ.①网页制作工具 Ⅳ.①TP393.092.2

中国版本图书馆CIP数据核字(2019)第179392号

◆ 著 张偶

责任编辑 俞彬

责任印制 马振武

◆ 人民邮电出版社出版发行 北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

北京盛通印刷股份有限公司印刷

◆ 开本:787×1092 1/16

印张:20.5  彩插:1

字数:523千字  2020年1月第1版

字数:1-2500册千字  2020年1月北京第1次印刷

定价:89.00元

读者服务热线:(010)81055410 印装质量热线:(010)81055316

反盗版热线:(010)81055315

广告经营许可证:京东工商广登字20170147号

内容提要

CSS(层叠样式表)具有很强的视觉表达能力,专门用于解决网页界面视觉问题。例如一大段用JavaScript实现的动画程序,只需要几行CSS代码就可以表达清楚。在前端开发过程中,要想开发出风格独特、具有吸引力的网页界面,需要工程师兼具CSS编程开发技能和艺术设计能力。

本书以CSS3版本为平台,使用大量生动、美观的实例,系统地剖析了CSS与视觉效果相关的重要语法。全书分为13章,第1章回顾基础知识,第2章~第10章介绍了CSS3的伪元素,边框,背景,阴影,剪切、滤镜和色彩混合,变量与计数器,变换,缓动,动画等在视觉展现方面的内容,第11章~第13章探讨了CSS3在造型创意、动画创意和文字特效创意方面的设计思路。

本书适合所有对网页设计和网页开发感兴趣的读者阅读,包括且不限于网页设计师、前端工程师和后端工程师。

本书实战项目一览表

所有项目均为纯CSS绘制,未使用任何图片和脚本

2.4.1节51页

3.4.2节83页

5.3.1节120页

6.4.2节145 页

8.4.1节189页

2.4.2节55页

4.4.1节99页

5.3.2节124页

7.3.1节164页

8.4.2节192页

3.4.1节78页

4.4.2节105页

6.4.1节141页

7.3.2节168页

9.5.1节209页

9.5.2节213页

11.1节251页

11.4节266页

12.3节283页

13.2节296页

10.4.1节240页

11.2节256页

12.1节273页

12.4节287页

13.3节300页

10.4.2节244页

11.3节261页

12.2节277页

13.1节293页

13.4节306页

前言

一、缩小前端和设计之间的距离

近年来,得益于HTML5、ECMAScript 2015规范的发布和移动设备的普及,Web应用越来越复杂,Web前端也取得了突飞猛进的发展。在开发流程上,前端介于设计和后端之间,前端负责把后端数据展示在从设计稿转换来的页面元素上。这些年前端的发展离后端越来越近了,前端不仅通过Ajax和MV* 框架完全实现了前后端分离,使后端工程师不用再把精力放在UI界面上,而且还用Node.js“侵入”了原本属于后端的业务逻辑开发领域,前端工程师已经变得越来越像后端工程师了。但是,前端本应该站在设计和后端的中间,现在却离设计越来越远了。

是否还有人记得,16年前的2003年,CSS Zen Garden(中文译为CSS禅意花园)网站提供了一个固定的HTML文件,仅凭更换CSS文件就可以让网站呈现完全不同的风格。那些CSS作者大多身兼两职,他们既是设计师,也是网页工程师,正是他们推动了CSS和Web标准的普及。时至今日,JavaScript在网页开发中的比重越来越大,已经鲜见设计师去写代码。这诚然是行业成熟分工细化的结果,但是相比前端工程师正在不断从后端的工程化中借鉴经验,把诸如设计模式、单元测试、持续集成这些概念引入前端开发中,前端工程师与设计师之间的借鉴、影响和促进却少得多。

尽管CSS3规范已经推出,但是大多数前端工程师仍然仅使用CSS2.1的特性,在实际工作中对CSS3的运用并不充分,CSS3的潜力远没有被发掘出来。

以手机为代表的电子消费品的普及,对应用程序的交互体验提出了越来越高的要求,可是前端工程师的视觉表现能力贫乏,设计师被JavaScript挡在应用开发的大门之外。他们之间缺少充分的交流,难以在交互设计和用户体验上获得突破,最终的结果是不论是什么应用,看起来都是千篇一律,缺乏美与个性。

如何促进前端向设计方向靠拢,增进前端与设计的融合,是每个Web开发者都应该思考的问题。要想缩小前端与设计之间的距离,就要从CSS着手,让设计师与前端工程师能使用相同的技术语言进行沟通。

二、更有效的CSS学习方法

CSS不像是JavaScript那样的通用编程语言,更像是SQL(用于查询数据库的专门语言)这样的特定领域的描述语言。因为CSS专门用于解决页面视觉问题,所以它具有很强的视觉表达能力。

写CSS和写JavaScript程序需要完全不同的语法,也有着完全不一样的设计思路。笔者个人的经验是先靠模仿掌握语法,再逐步升级理论知识。而要模仿,首先要有合适的项目和代码,在反复练习中熟悉和掌握基本知识。用于练习的项目,应该满足两点要求,一是项目本身是有趣的,是美的,当你完成一个项目之后,可以把它展示在公众面前,获得朋友的称赞;二是它不能太复杂,每个项目的代码量宜在50~100行,每次练习大约需要半小时。代码量再多一点儿,有可能一天消化不完,影响学习的积极性。当你学习一个新的CSS特性时,可以模仿一个项目,就像学习写字一样,多练习几遍,从临摹到默写,从形似到神似,仔细观察,体会其中的技巧与方法,锻炼自己的思维方式。

三、本书针对的读者

本书适合所有对网页设计和网页开发感兴趣的读者阅读。

设计师 本书不涉及JavaScript代码,而且所有实战项目都是使用CSS创作的艺术作品,设计师可以把CSS当作一种艺术创作工具。

前端工程师 前端工程师通过学习用CSS表现复杂视觉效果的技巧,能够深刻理解CSS特性,在开发商业网站和交互组件时将变得游刃有余。

后端工程师 几乎所有的后端开发工程师都对CSS感兴趣,但又对CSS感到陌生和无力,通过阅读本书,将能领略到CSS的魅力,从全新的角度重新理解CSS。

四、本书的讲解方式

本书以CSS3为基础,细致地剖析与视觉效果相关的重要语法。但只懂语法是远远不够的,就像学习绘画,不仅要学习色彩、透视或构图这些理论知识,还必须通过大量的练习把理论知识融入实践中。本书不会面面俱到地讲解全部CSS属性,即使书中讲到的属性,也不会讲解它的所有语法细节。我不想让读者变成五金店的伙计——那种知道了所有配件的规格,却不会做家具的伙计。相反,我想让大家先享受作品带来的喜悦,再进一步把这种喜悦转化成学习的动力。

书中的章节也不完全按照语法来组织,例如伪元素一般是放在选择器的章节里,但本书把它独立成章,因为伪元素对于实现语义化作用重大,有必要单独讨论;另外,一些稍有跳跃的安排还有drop-shadow()滤镜被放到“阴影”一章,outline属性被放在了“边框”一章等。

本书没有讨论厂商前缀和浏览器兼容问题,因为这些问题都会随着时间的推移,随着厂商对规范的实现而自动解决。

本书分为13章,第1章回顾了CSS的基础知识,第2章~第10章每章讨论一个主题内容,第11章~第13章探讨CSS艺术作品的设计思路。如果把这本书比作一本旅行手册的话,相当于是先介绍旅行目的地的风土人情,然后再单独解说各个景点,最后把本次旅程的点滴记忆制作成精美的纪念页。

在讲解过程中,本书提供了一百多个基于视觉艺术设计的实例,这些实例的演示文件可以在电子资源中找到。

哪怕你不是刚刚学习CSS,也建议你从第1章读起,因为即使是简单的知识点,它们的示例也是很有意思的。为了避免出现无法理解的色值,示例中尽量使用颜色名称,你可以通过附录查看颜色名称对应的色彩。

除讲解过程中给出的实例之外,本书提供了30个实战项目,你可以通过“本书实战项目一览表”找到感兴趣的项目所在的章节,这些项目还配有同步讲解的视频,帮助你找到新的灵感。

书中的实战项目都有一定的难度。当我演示一种特性的实际用途时,也许要结合其他特性一起使用才能实现一种视觉效果。有的特性可能你还不熟悉,你要先把还不熟悉的部分暂时跳过,在学习过后面的知识之后,再转回头来,慢慢地加以理解。

五、本书资源

扫描下面的“资源下载”二维码,就可以获取本书配套电子资料包的下载方式。

扫描下面的“云课”二维码,可以观看全书视频。你也可以扫描正文中的二维码观看对应章节的视频。

我们精彩的CSS艺术之旅就要正式开始了,祝你旅途愉快!

张偶

2019年8月

第1章 CSS基础知识

本章将回顾CSS中重要的基本概念。对于已经有过CSS的编写经验的读者,建议也不要错过本章的内容。基本概念通常都会有些枯燥,但从艺术设计的角度来看,即使不运用复杂的概念也可以巧妙地表达内容。本章提供了29个示例,它们充分展现了CSS丰富的表现力,你会发现只用寥寥几行CSS代码就能描绘出杨辉三角形、象棋棋盘、素数集合等。读完本章,就会发现用CSS创作艺术作品是一种快乐的体验。

1.1 CSS简介

CSS全称是“Cascading Style Sheets”,中文译为“层叠样式表”,不过一般不用这样的官名称呼它,提到它时只简称为“CSS”或“样式表”。CSS代码和HTML代码(本书也经常称它为DOM结构)一起配合,构建出网页的外观。打个比方,HTML就像是人的骨骼,而CSS则像是人的皮肤和肌肉,骨骼定义了身体的结构,而皮肤和肌肉塑造了我们的外貌,当网页的DOM结构确定下来之后,我们就可以通过书写CSS来灵活地配置网页的外观了。如果给你和你的同学相同的DOM结构,但CSS代码由你们分别来写,最后的网页一定会长得不一样,就好像你和你的同学都有206块骨头,但你们的肤色、高矮、胖瘦不同,所以没有人会说你们是同一个人。

最初的网页是没有CSS的,就像最初的房子不用装修一样,看起来相当简陋。1996年CSS 1.0横空出世,提供了文本、颜色等功能,1998年推出的2.0版本和随后推出的2.1修正版本,支持了定位、盒模型等功能,2001年开始制定3.0规范,支持动画、变形等丰富的视觉效果。如此看来,CSS3已经有十几年的历史了,不算是什么新技术,事实是尽管标准制定得相当快,但因为在浏览器大战期间各浏览器厂商并不尊重公开标准,对CSS 2.1的支持都不统一,更别说CSS3了,这导致CSS 3直到10年之后才真正得到广泛支持。因为CSS支持的功能越来越多,所以从3.0开始,规范被分解为若干个独立的小模块,例如文本、颜色、定位等都是单独的小模块,便于各模块分别发展,就像一个单细胞生物进化成了多细胞生物,各个模块之间相互分工和依赖,提供了更强大的生命力,本书内容主要涉及伪元素、背景和边框、滤镜、缓动、变形、动画等模块。

1.2 在页面中应用CSS

网页中书写CSS代码的方法有以下3种。

1.2.1 内联样式

第1种方法称为“内联样式”,它把样式属性写在HTML标签的style属性里,也是“内联”这个词的含义。

例1-1 用CSS内联样式绘制图1-1所示的字母i。

容器<figure>中包含两个子元素:第1个<div>绘制一个橙色的圆形;第2个<div>绘制一个竖着的黄色矩形。

X:\实例代码\第1章\demo-1-01.html

<figure>
    <div style="width: 50px; height: 50px; background-color: orange; border-radius: 50%;
margin-bottom: 10px;"></div>
    <div style="width: 50px; height: 100px; background-color: gold; border-radius: 10px;">
</div>
</figure>

这种方法的优点是不用定义选择器(下一节会讲到),但缺点是各元素的样式分散在整个HTML文档中,不方便集中管理,每个元素的样式也不能太复杂,否则写起来就很不方便了。这种方法我们不推荐。

1.2.2 style元素

第2种方法称为“style元素”,它把网页中所有元素的样式都集中写到一个名为<style>的标签里,放在页面的DOM结构之前。因为样式不是写在元素的属性里,为了区分不同元素的样式,就要分别为各元素指定名字,然后在<style>标签里按元素的名称逐个书写样式。

例1-2 用“style元素”法绘制图1-1所示的字母i。

本例是对例1-1的重构。元素的class="div1"class="div2"属性定义了这两个子元素的类名为div1div2,然后在<style>中用.div1.div2来引用它们。

X:\实例代码\第1章\demo-1-02.html

<!DOCTYPE html>
<head>
    <title>Document</title>
    <style>
        .div1 {
            width: 50px;
            height: 50px;
            background-color: orange;
            border-radius: 50%;
            margin-bottom: 10px;
        }

        .div2 {
            width: 50px;
            height: 100px;
            background-color: gold;
            border-radius: 10px;
        }
    </style>
</head>
<body>
    <figure>
        <div class="div1"></div>
        <div class="div2"></div>
    </figure>
</body>
</html>

1.2.3 link标记

第3种方法称为“link标记”,它在第2种方法的基础上,把<style>标签的内容单独存放到扩展名为.css的文件中,然后在HTML文件中用<link rel="stylesheet" href="file-name.css">标记导入这个CSS文件。

例1-3 用“link标记”法绘制图1-1所示的字母i。

例1-3是对例1-2的进一步重构,从1个HTML文件又分拆出了一个CSS文件,然后用<link>标记把它们关联起来。

DOM结构如下。

X:\实例代码\第1章\demo-1-03.html

<!DOCTYPE html>
<head>
     <title>Document</title>
     <link rel="stylesheet" href="demo-1-03.css">
</head>
<body>
     <figure>
          <div class="div1"></div>
          <div class="div2"></div>
     </figure>
</body>
</html>

CSS结构如下。

X:\实例代码\第1章\demo-1-03.css

.div1 {
    width: 50px;
    height: 50px;
    background-color: orange;
    border-radius: 50%;
    margin-bottom: 10px;
}

.div2 {
    width: 50px;
    height: 100px;
    background-color: gold;
    border-radius: 10px;
}

这种方法的好处是,从物理上分离了结构和样式,HTML文件中只存储文档的DOM结构,CSS文件中只存储样式。在商业应用中多用这种方式,因为多个HTML文件可以引用同一个样式表文件,达到复用的目的,节省开发和维护成本。

本书的示例有两种:一种是伴随理论知识部分的示例,因为示例的数量较多,为了避免文件太多,这种示例均采用第2种“style元素”方法,把页面结构和样式写在一个文件中;另一种是项目实战,样式的代码量大一些,项目也更正式一些,这种示例采用第3种“link标记”方法。

1.3 常用CSS属性一览

表1-1列出了常用的一些CSS属性,因为这些属性的含义很简单,这里就不详细讲解了。如果已有CSS的使用经验,相信对这些属性不陌生。如果是第一次接触CSS,通过尝试修改这些属性的值,再对比修改前和修改后的效果,就可以很快了解和掌握它们了。

表1-1

1.4 选择器

为了灵活地选择将要设置样式的元素,CSS提供了很多种选择器,在此我们介绍常用的几种。

1.4.1 标签选择器

顾名思义,标签选择器就是用标签名称作为选择器。

例1-4 用标签选择器绘制图1-2所示的3个圆。

DOM结构是一个包含3个子元素的容器。

X:\实例代码\第1章\demo-1-04.html

<figure>
    <div></div>
    <div></div>
    <div></div>
</figure>

<figure>容器被设置为一个矩形框,<div>样式被设置为描边的圆形。因为有3个<div>标签,所以绘制出了3个圆形。

X:\实例代码\第1章\demo-1-04.html

figure {
    background-color: silver;
    width: 350px;
    padding: 20px;
    display: flex;
    justify-content: space-between;
}

div {
    width: 100px;
    height: 100px;
    border: 1px solid black;
    border-radius: 50%;
}

尽管标签选择器用起来很简便,但缺点也很明显。

第一,在一个复杂的页面或构图中,一定会有很多同名标签,那么多个元素就会共享相同的样式,本来共享是好事,能节约代码量,但问题出在共享的粒度实在太粗了,可能经常会出现我们预料之外的效果。在例1-4中,如果页面中还有别的<div>元素,那么它们都会变成圆形。

第二,我们希望代码尽量语义化,也就是让我们的代码读起来就像读自然语言一样,这样便于理解和维护,但标准的HTML标签名并不能体现具体的业务含义。如果我们把本例的3个圆圈图案想象成红绿灯,那么,若能用“traffic-lights”来命名和引用它,就再好不过了。这正是1.4.2节要讨论的类选择器所擅长的。

1.4.2 类选择器

类选择器是指为元素指定一个class属性,也称为样式类,然后在样式表中通过这个属性值来引用元素。

例1-5 在例1-4的基础上,本例将演示如何使用类选择器绘制图1-3所示的一组红绿灯。

DOM结构与例1-4类似,但为所有元素定义了样式类名称。为<figure>元素指定了类名traffic-lights,为3个<div>都指定了类名light,再分别为每个<div>指定不同的颜色类名redyellowgreen

X:\实例代码\第1章\demo-1-05.html

<figure class="traffic-lights">
    <div class="light red"></div>
    <div class="light yellow"></div>
    <div class="light green"></div>
</figure>

在CSS中以.class-name的格式来选择对应的元素。

X:\实例代码\第1章\demo-1-05.html

.traffic-lights {
    background-color: silver;
    width: 350px;
    padding: 20px;
    display: flex;
    justify-content: space-between;
}

.light {
    width: 100px;
    height: 100px;
    border: 1px solid black;
    border-radius: 50%;
}

更进一步,通过引用3个圆圈的类名,为它们分别上色,绘制出一组红绿灯。

X:\实例代码\第1章\demo-1-05.html

.red { background-color: coral; }
.yellow { background-color: gold; }
.green { background-color: lightgreen; }

可以为每个元素起不同的类名来区分每个元素,如redyellowgreen,也可以为有共同属性的元素起相同的类名,如light,还可以为一个元素分配多个类名,多个类名间用空格分隔,如light red

还有一种名为“ID选择器”的选择器,它通过为元素命名唯一的ID名称,然后在样式表中以#id的形式引用元素。因为通常ID属性是配合JavaScript动态程序使用的,所以本书不在样式表中使用“ID选择器”。

本书理论部分的示例有时会用标签选择器,但在复杂的项目实例中,则几乎只用到类选择器。

1.4.3 后代选择器

DOM结构是树状结构,也就是元素可以一层一层地嵌套,便于有条理地组织内容。相应地,对于内层元素的类选择器,为了提高可读性,我们应该让它体现出DOM的层次结构,按照“父类 子类”的格式来书写。

例1-6 使用CSS绘制一棵简单的树,如图1-4所示。

它的DOM结构体现了treebranchleaf的层级关系。

X:\实例代码\第1章\demo-1-06.html

<figure class="tree">
    <div class="branch">
        <span class="leaf"></span>
        <span class="leaf"></span>
        <span class="leaf"></span>
    </div>
    <div class="branch">
        <span class="leaf"></span>
        <span class="leaf"></span>
    </div>
</figure>

设置元素的样式,令树干为深棕色的竖长矩形,令树枝为浅棕色的细长矩形,并稍向上倾斜,体现树枝生长的方向,令树叶为绿色,用圆角画出叶片的轮廓。

X:\实例代码\第1章\demo-1-06.html

.tree {
    width: 30px;
    height: 300px;
    background-color: saddlebrown;
    border-radius: 5px;
    display: flex;
    flex-direction: column;
    justify-content: space-around;
}

.tree .branch {
    width: 160px;
    height: 20px;
    background-color: peru;
    border-radius: 10px;
    display: flex;
    justify-content: flex-end;
    transform: rotate(-30deg);
}

.tree .branch .leaf {
    width: 40px;
    height: 40px;
    border-radius: 100% 0 100% 0;
    background-color: green;
    transform: translateY(-100%);
}

使用后代选择器,从技术上是为了避免选择到超出范围的元素,更重要的是,这样写能使语义更清晰,令CSS选择器与DOM结构保持同样的层级关系,例如.tree .branch .leaf就体现了“树干→树枝→树叶”这样的递进结构。

1.4.4 伪类选择器

伪类选择器用于选择处于特殊位置或状态的元素,例如选择一堆元素中的第1个或最后一个元素,或者隔一个选一个元素,或者选择处于鼠标指针悬停状态的元素。下面介绍其中几个常用的。

① :first-child和:last-child

从名称一望即知,:first-child用于选择第1个元素,:last-child用于选择最后一个元素。

例1-7 用以上两个选择器绘制由6行圆点堆叠的三角形。三角形特征如图1-5所示,其中每行圆点的左右两端均为红色,中间为绿色。

DOM结构是6个容器,第1个容器中有1个子元素,第2个容器中有2个子元素,以此类推,第6个容器中有6个子元素。

X:\实例代码\第1章\demo-1-07.html

<figure class="triangle">
    <div>
        <span></span>
    </div>
    <div>
        <span></span>
        <span></span>
    </div>
    <div>
        <span></span>
        <span></span>
        <span></span>
    </div>
    <div>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
    </div>
    <div>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
    </div>
    <div>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
    </div>
</figure>

令各容器中的子元素水平均匀分布,子元素为绿色的圆,形成一个由圆点组成的三角形。

X:\实例代码\第1章\demo-1-07.html

.triangle {
    width: 500px;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.triangle div {
    height: 50px;
    margin: 5px;
    display: flex;
    justify-content: space-between;
}

.triangle div span {
    width: 50px;
    height: 50px;
    margin: 10px;
    background-color: lightgreen;
    border-radius: 50%;
}

再把三角形的左右两边改成红色。

X:\实例代码\第1章\demo-1-07.html

.triangle div span:first-child,
.triangle div span:last-child {
    background-color: lightcoral;
}

由于强调了三角形的左右两边,希望能让人联想到杨辉三角形,因为在杨辉三角形中,左右两边的数字是特殊的,值均为1,而其他位置的数字都大于1。

② :nth-child()

:nth-child()伪类流传较为广泛的用法是:nth-child(odd):nth-child(even),其中:nth-child(odd)表示选择所有第奇数个元素,:nth-child(even)表示选择所有第偶数个元素。例如有一个含有10个元素的容器,则:nth-child(odd)会选中第1、3、5、7、9这5个元素,而:nth-child(even)会选中第2、4、6、8、10这5个元素。

例1-8 用CSS绘制一个木色背景的棋盘,如图1-6所示。

DOM结构是4个<div>容器,每个容器中再包含4个<span>元素。

X:\实例代码\第1章\demo-1-08.html

<figure class="chessboard">
    <div>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
    </div>
    <!-- 一共有 4 组 <div> 容器,此处略去后面的 3 组 -->
</figure>

先绘制出棋盘的轮廓。

X:\实例代码\第1章\demo-1-08.html

.chessboard {
    width: 400px;
    background-color: burlywood;
    border: 10px solid darkgray;
}

.chessboard div {
    display: flex;
}

.chessboard div span {
    width: 100px;
    height: 100px;
}

然后令处于第奇数行第偶数列的元素div:nth-child(odd) span:nth-child(even)和第偶数行第奇数列的元素div:nth-child(even) span:nth-child(odd)的背景色加深,从而形成交错的棋盘状。

X:\实例代码\第1章\demo-1-08.html

.chessboard div:nth-child(odd) span:nth-child(even),
.chessboard div:nth-child(even) span:nth-child(odd) {
    background-color: rgba(0, 0, 0, 0.3);
}

除此之外,:nth-child()函数还可以接收一个形如an+b的表达式作为参数,其中a和b是整数,n是从0开始的自然数列,由此表达式得到的数列,即为被选中的元素的下标,元素的下标从1开始计算。

例1-9 用:nth-child(an+b)选择器,绘制的效果如图1-7所示。

DOM结构是一个包含18个子元素的容器。

X:\实例代码\第1章\demo-1-09.html

<figure>
    <div></div>
    <div></div>
    <!-- 一共 18 个 <div> 标签,此处略去 14 个 -->
    <div></div>
    <div></div>
</figure>

令每个子元素显示为一个小圆,18个小圆排成一排[图1-7(a)]。

X:\实例代码\第1章\demo-1-09.html

figure {
    display: flex;
    justify-content: space-between;
}

figure div {
    width: 3vw;
    height: 3vw;
    background-color: lightgreen;
    border-radius: 50%;
    margin: 0.2vw;
}

针对这组子元素,则有以下几种情况。

 ●div:nth-child(0n+7),此时的数列为{7},表示选择第7个元素,可以简写为div:nth-child(7),见图1-7(b)。

 ●div:nth-child(1n+3),此时的数列为{3, 4, 5, 6, ...},表示选择第3个元素及其后所有元素,见图1-7(c)。

 ●div:nth-child(2n+1),此时的数列为{1, 3, 5, 7, ...},表示选择第奇数个元素,也就相当于div:nth-child(odd),见图1-7(d)。

 ●div:nth-child(2n+0),此时的数列为{2, 4, 6, 8, ...},表示选择第偶数个元素,可以简写为:nth-child(2n),也就相当于div:nth-child(even),见图1-7(e)。

 ●div:nth-child(5n),此时的数列为{5, 10, 15, 20, ...},表示选择第5的倍数的元素,见图1-7(f)。

 ●div:nth-child(6n+3),此时的数列为{3, 9, 15, 21, ...},表示从第3个元素开始,其后每隔6个元素被选中一个,见图1-7(f)。

③ :not()

:not()伪类用于排除掉一些元素,例如:not(:first-child)表示排除掉第1个元素,:not(:nth-child(3))表示排除掉第3个元素。

例1-10 用CSS标注出100以内的素数,其中橙色背景的数字为素数,如图1-8所示。

DOM结构是一个容器中包含100个子元素。

X:\实例代码\第1章\demo-1-10.html

<figure class="prime-numbers">
    <div></div>
    <div></div>
    <!-- 一共 100 个 <div> 标签,此处略去 96 个 -->
    <div></div>
    <div></div>
</figure>

令这100个元素按10*10矩阵排列,每个元素是一个绿色的圆。这段代码中有两个知识点我们还没有讲到,::before伪元素用于显示计数器的数字,它的用法请参考2.1节;counter用于计数,它的用法请参考7.2节;在这里无须全部看懂。其中.prime-numbers div::before用于绘制圆。

X:\实例代码\第1章\demo-1-10.html

.prime-numbers {
    width: 400px;
    height: 400px;
    display: grid;
    grid-template-columns: repeat(10, 1fr);
    counter-reset: n;
}

.prime-numbers div {
    counter-increment: n;
    position: relative;
    width: 30px;
    height: 30px;
}

.prime-numbers div::before {
    content: counter(n);
    position: absolute;
    width: inherit;
    height: inherit;
    background-color: lightgreen;
    border-radius: 50%;
    text-align: center;
    line-height: 30px;
}

接下来从100个数字中挑选出素数,把素数的背景设置为橙色。

X:\实例代码\第1章\demo-1-10.html

.prime-numbers div:nth-child(2)::before,
.prime-numbers div:nth-child(3)::before,
.prime-numbers div:nth-child(5)::before,
.prime-numbers div:nth-child(7)::before,
.prime-numbers div:not(:nth-child(1)):not(:nth-child(2n)):not(:nth-child(3n)):not(:nthchild(5n)):not(:nth-child(7n))::before {
    background-color: orange;
}

这里的选择器共有5行。前4行分别把数字2、3、5、7标明为素数。第5行很长,我们拆开看,它的第1部分是.prime-numbers div,意思是所有<div>元素都被选中(被当作素数),后面跟随了5个:not()选择器,用于排除掉不是素数的数::not(:nth-child(1))排除掉了第1个数,因为数字1不是素数,接下来:not(:nth-child(2n))排除掉了所有能被数字2整除的数,再接下来的:not(:nth-child(3n)):not(:nth-child(5n)):not(:nth-child(7n))排除掉了所有能被数字3、5、7整数的数,以上这些选择器都是连缀着一路写下来的,最后的::before表示设置选择的是以上剩余的元素的伪元素。

④ :hover

:hover表示当鼠标指针悬停在元素上时的状态,这个样式的用途是告知用户当前鼠标指针指在哪个元素上,我们经常在导航菜单、表单按钮上见到这种交互方式。

例1-11 把单词ELEMENTS的每个字母分拆开,当鼠标指针划过每个字母时,该字母放大,其静态图效果如图1-9所示。

下面将具体说明实现该效果的方法。

DOM结构是容器中包含若干子元素,每个子元素包含1个字母。

X:\实例代码\第1章\demo-1-11.html

<figure class="word">
    <div>E</div>
    <div>L</div>
    <div>E</div>
    <div>M</div>
    <div>E</div>
    <div>N</div>
    <div>T</div>
    <div>S</div>
</figure>

令每个字母表现为一个描了虚线边框的圆。

X:\实例代码\第1章\demo-1-11.html

.word {
    display: flex;
}

.word div {
    width: 50px;
    height: 50px;
    background-color: lightgreen;
    border-radius: 50%;
    color: darkgreen;
    border: 1px dashed;
    font-size: 35px;
    font-family: sans-serif;
    text-align: center;
    line-height: 50px;
    transition: 0.5s 0.4s ease-out;
}

然后,设置鼠标指针悬停在字母上时的样式,其中的transform用于把字母变大,它的用法将在第8章详细讲解;transition用于增加缓动效果,也就是字母不是一下子变大的,而是在0.5s内逐渐变大的,transition的用法将在第9章详细讲解。

X:\实例代码\第1章\demo-1-11.html

.word div:hover {
    background-color: gold;
    transform: scale(1.5);
    transition: 0.5s ease-out;
}

1.5 单位

CSS的属性值有很多种数据格式,其中最复杂的是表示长度和颜色的方式。

1.5.1 长度

① px

px就是指像素点的数量,100px就是100个像素点的长度。

例1-12 用CSS绘制一个图1-10所示的放大镜。

DOM结构是容器.magnifier中包含两个子元素,其中.lens表示镜片,.handle表示镜柄。

X:\实例代码\第1章\demo-1-12.html

<figure class="magnifier">
    <div class="lens"></div>
    <div class="handle"></div>
</figure>

这里我们关心的是长度单位,其他CSS属性就不详细解读了。此处用的单位是px,例如镜片的宽、高是100px,镜柄宽是20px,绝对定位的topleft也是用px。

X:\实例代码\第1章\demo-1-12.html

.magnifier {
    position: relative;
    color: dodgerblue;
}

.lens {
    position: absolute;
    width: 100px;
    height: 100px;
    border: 10px solid;
    border-radius: 50%;
}

.handle {
    position: absolute;
    width: 20px;
    height: 100px;
    border-radius: 0 0 10px 10px;
    background-color: currentColor;
    left: 100px;
    top: 100px;
    transform: rotate(-45deg);
    transform-origin: top;
}

假如现在我们要调整这个放大镜的尺寸到原尺寸的1.5倍,那么我们就要把CSS中的100px改为150px20px改为30px,一共有8个属性用到了px,那就要同时改8处。再假如要把放大镜调整到原大的2.15倍,还要再改这8处,而且计算也变复杂了,可见px的缺点就是因为它是绝对尺寸,调整起来很麻烦。

虽然本章之前的示例都是用px作为单位,那是因为我们还没有对长度单位进行深入探讨,所以采用一种大家都容易理解的长度单位,但本书并不推荐使用px,此后的示例中将最大限度减少使用px。

② em

和绝对尺寸相对的一个概念是相对尺寸,也就是它先确定一个基准,然后其他尺寸都以这个基准来计算大小,当要修改时,只要改一下基准值,其他尺寸就都按比例自动改变了。

em是指相对于font-size的大小,例如一个元素有属性font-size: 10px,那么1em就等于10px1.5em就等于15px5em就等于50px。如果一个容器内所有的子元素都使用em单位,那么,当要调整容器的大小时,只要调整font-size的值即可。

例1-13 以em为长度单位,用CSS绘制图1-10所示的放大镜。

本例是在例1-12的基础上进行,只是将长度单位改为em。CSS代码如下。

X:\实例代码\第1章\demo-1-13.html

.magnifier {
    position: relative;
    color: dodgerblue;
    font-size: 10px;
}

.lens {
    position: absolute;
    width: 10em;
    height: 10em;
    border: 1em solid;
    border-radius: 50%;
}

.handle {
    position: absolute;
    width: 2em;
    height: 10em;
    border-radius: 0 0 1em 1em;
    background-color: currentColor;
    left: 10em;
    top: 10em;
    transform: rotate(-45deg);
    transform-origin: top;
}

我们在.magnifier选择器中先定义了font-size: 10px;,然后把其后所有尺寸都改为em单位,例如100px改为10em20px改为2em

假如现在我们要调整这个放大镜的尺寸到原尺寸的1.5倍,只要把.magnifier选择器中的font-size属性值从10px改为15px即可,其他em单位的数值都不用改动。

浏览器默认的font-size是16px,它对应的1.1em=17.6px1.2em=19.2px1.3em=20.8px结果都不是整数,不方便计算。本书推荐将font-size设置为10px,这样1.1em=11px1.2em=12px1.3em=13px,结果都是整数,换算起来很方便。

之前说过要最大限度减少使用px,结合本节的内容,再加一句,就是在能使用em的时候,要尽量使用em。

1.5.2 颜色

CSS提供了4种表示颜色的方式,分别是颜色名称、HSL表示法、RGB表示法和增加了透明效果的HSLA/RGBA表示法。

① 颜色名称

颜色名称是指“red”“blue”“green”这些英文单词,例如红色系的就有图1-11中的9种。

本书附录中列举了全部100多种颜色名称。本书的示例会尽量使用颜色名称,以方便理解。不过实际创作过程中,只有这100多种颜色显然是不够用的。

② HSL

HSL是用色相(hue)、饱和度(saturation)和亮度(lightness)调配出的颜色,如图1-12所示。

其中色相也就是我们日常说的“颜色”,左边的圆环就是色相,它的值是从0°到360°,也就是一个圆周角的角度,按彩虹色的顺序从0°开始排列,0°是红色,30°是橙色,60°是黄色,120°是绿色,180°是青色,240°是蓝色,300°是紫色,360°又回到红色。

饱和度是指纯色与灰色混合之后,纯色的占比,取值是0%到100%,100%表示未混入任何灰色的纯色,0%表示全灰。

亮度的取值范围也是从0%到100%,表示从暗到明的程度,0%表示全黑,100%表示全白,50%表示纯色。

因为纯色的饱和度是100%,亮度是50%,所以只要调整色相的度数,就可以得到不同的纯色,例如红色是hsl(0, 100%, 50%),绿色是hsl(120, 100%, 50%),蓝色是hsl(240, 100%, 50%)

理解了HSL的原理,我们就可以自己调出颜色了,比如想调出浅珊瑚红lightcoral的颜色,首先知道它是红色,所以色相取0°,因为比大红要浅,所以加大亮度到70%,最后再把饱和度设置为80%,用来混入一点灰色,最终的色值是hsl(0, 100%, 70%)

反之,见到一个HSL值,我们也能大致判断它的颜色,例如hsl(203, 92%, 75%),它的色相为203°,即在青色180°和蓝色240°之间,所以它的色相是青蓝,亮度75%表示比纯色亮一些,也就是颜色浅一些(想象一下颜料中加了水),饱和度92%表示混入了一点灰色,不如纯色那么鲜艳,那么这个颜色就是亮的掺了水的青蓝色加了一点灰,我们把这种颜色叫作浅天蓝色。

③ RGB

RGB颜色模式是用红色、绿色、蓝色3色调配出的颜色,RGB色值是以# 号开头的6个十六进制数,每种颜色用2位十六进制表示,取值范围是从#00到#ff,例如红色(red)是#ff0000,绿色(green)是#00ff00,蓝色(blue)是#0000ff,黄色是#ffff00(红色和绿色混合),如图1-13所示。

因为同比例的R、G、B颜色混合之后呈灰色,例如#282828、#676767、#cacaca都是灰色,又因为若一个颜色的两个十六进制数字相同,可以缩写为一个数字,所以我们可以得到缩写的从#000(黑)到#fff(白)之间的16个灰度颜色,如图1-14所示。

用RGB模式来记录颜色已经有100多年的历史了,电视机、显示器都是用这个原理显示颜色的,不过它不太易读,很难让人看到一个RGB色值就能估算出它大概的颜色。RGB颜色还有一种表示方法,就是用rgb(r,g,b)的形式把3种颜色写成10进制数或百分比值,例如#ff0000写成rgb(255, 0, 0),这比十六进制数稍好一点,不过还是不易读。

总之,RGB颜色只有16级灰度颜色比较好记,在创作时,如果100多种颜色名称不够用,应该优先使用好理解的HSL颜色。

颜色名称、HSL模式的颜色和RGB模式的颜色可以混合使用,下面通过例1-14进行展示。

例1-14 通过4种颜色表现一年四季的特征,如图1-15所示。

DOM结构是名为.seasons的容器,其中包含4个表示四季的子元素。

X:\实例代码\第1章\demo-1-14.html

<figure class="seasons">
    <div>spring</div>
    <div>summer</div>
    <div>fall</div>
    <div>winter</div>
</figure>

为各子元素分别设置圆角,令它们围合成一个圆形。

X:\实例代码\第1章\demo-1-14.html

.seasons {
    width: 20em;
    height: 20em;
    font-size: 20px;
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    grid-gap: 0.2em;
}

.seasons div {
    border: 0.1em solid gray;
    text-align: center;
    line-height: 6em;
    font-size: 1.5em;
    color: black;
}


    .seasons div:nth-child(3) {
    order: 4;
}

.seasons div:nth-child(1) { border-radius: 100% 0 0 0; }
.seasons div:nth-child(2) { border-radius: 0 100% 0 0; }
.seasons div:nth-child(3) { border-radius: 0 0 100% 0; }
.seasons div:nth-child(4) { border-radius: 0 0 0 100%; }

各子元素用不同的背景色,其中春季用色彩单词springgreen(嫩绿),夏季用十六进制色值#ff4500(橘红),秋季用rgb色值rgb(255, 215, 0)(金黄),冬季用HSL色值hsl(195, 100%, 50%)(宝石蓝)。

X:\实例代码\第1章\demo-1-14.html

.seasons div:nth-child(1) { background-color: springgreen; }
.seasons div:nth-child(2) { background-color:#ff4500; }
.seasons div:nth-child(3) { background-color: rgb(255, 215, 0);}
.seasons div:nth-child(4) { background-color: hsla(195, 100%, 50%); }

④ HSLA/RGBA

HSLA/RGBA比HSL/RGB多出的最后一个A,学名叫“alpha”通道,实践中我们把它当作一个不透明度值对待,取值范围是从0到1,0表示完全透明,那么这个颜色就像不存在一样,1表示完全不透明,就像没有设置过这个值一样,0.5当然就表示半透明了。

例1-15 用CSS绘制了一个#符号,相互交叉的位置的颜色是两种颜色的混合色,如图1-16所示。

DOM结构是名为.hashtag的容器包含4个子元素,每个子元素表示一条线。

X:\实例代码\第1章\demo-1-15.html

<figure class="hashtag">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
</figure>

在CSS中用绝对定位(后面的1.7节会讲解定位)令线与线互相叠加。

X:\实例代码\第1章\demo-1-15.html

.hashtag {
    width: 10em;
    height: 10em;
    position: relative;
}

.hashtag div {
    position: absolute;
}

.hashtag div:nth-child(odd) {
    width: 20%;
    height: 100%;
}

.hashtag div:nth-child(even) {
    width: 100%;
    height: 20%;
}

.hashtag div:nth-child(1) { left: 10%; }
.hashtag div:nth-child(2) { top: 10%; }
.hashtag div:nth-child(3) { right: 10%; }
.hashtag div:nth-child(4) { bottom: 10%; }

为每条线设置了不同的颜色,颜色模式都是HSLA,其中“A”的值是0.8。

X:\实例代码\第1章\demo-1-15.html

.hashtag div:nth-child(1) { background-color: hsl(197, 71%, 73%, 0.8); }
.hashtag div:nth-child(2) { background-color: hsl(300, 47%, 75%, 0.8); }
.hashtag div:nth-child(3) { background-color: hsl(0, 79%, 72%, 0.8); }
.hashtag div:nth-child(4) { background-color: hsl(28, 100%, 86%, 0.8); }

⑤ transparent

transparent关键字表示透明色,就像透明玻璃一样,用户看不到这个颜色。单独使用color: transparent的意义不大,因为有不止一种方法可以实现相同的效果,例如通过在RGBA/HSLA模式下把A值设置为0,或者使用visibility: hidden属性。

transparent主要是用在一系列颜色中,例如通过把元素的4个边框中的3个边框设置为透明色,再加上其他样式,就可以创作出三角形;再如元素的背景是多种颜色生成的渐变色时,在其中有规律地加入透明色,则可以创作出条纹背景。这些技巧将在第3章边框和第4章背景中详细讲解。

1.6 盒模型

1.6.1 内边距、边框和外边距

在介绍这几个术语之前,我们先看图1-17中的一组照片。

上面每一张照片都被放在一个相框中,每个相框由外到内的构成包括黑色的边框、边框内大面积的象牙白留白、照片本身,相框与相框之前没有紧贴在一起,相互之前留有间距。这种结构在生活中很常见,回想一下,地铁站的大幅海报、商场里电视机的陈列墙甚至楼房的窗户都能找到这种构图结构。为此,CSS将这种构图形式抽象出来,把它命名成“盒模型”,其具体组成如图1-18所示。

一个元素就是一个矩形“盒子”,它的内容(content)就像是相框中的照片,内容与边框(border)之前的留白称为内边距(padding),元素与元素之间的间距称为外边距(margin)。我们经常使用的width和height,是指内容的宽和高,不包括内边距、边框和外边距。

1.6.2 box-sizing

盒模型虽然经典,但它有一个很大的问题,就是不符合生活经验。在1.6.1小节的一组照片图中,从生活经验上,我们一般认为这是6个相框,照片和相框是一个整体,当要在墙上布置这些相框时,我们不会考虑其中包含的照片大小,而只会考虑相框的大小,为相框与相框之间安排合理的间距。在网页布局中也是这样,我们更多的是考虑内容+内边距+边框(content+padding+border)三者整体的尺寸,而不仅仅是内容(content)的尺寸,但是盒模型的width和height指的却正是内容的尺寸,如果我们想得到内容+内边距+边框的尺寸,就要掰着手指头算一算,整体的宽度应该是width + padding*2 + border*2,因为内容的左右两边都有内边距和边框,所以要乘以2,类似地,整体的高度应该是height + padding*2 + border*2。

为了解决这个问题,在CSS3中引入了一个名为box-sizing的属性,它有两个可选的值。一个值是content-box,表示盒子的width和height以content的边界计算,这也就是上面讲的经典的盒模型;另一个值是border-box,表示盒子的width和height以border的边界计算,也就是盒子的整体尺寸是content+padding+border占据的空间。

例1-16 用CSS绘制图1-19所示的两种盒模型的效果。

DOM结构是两个包含子元素的容器。

X:\实例代码\第1章\demo-1-16.html

<div class="box">
    <div class="inner"></div>
</div>

<div class="box">
    <div class="inner"></div>
</div>

令两个容器的width(10em)、height(6em)、padding(1em)、border(1em)属性都有相同的值。

X:\实例代码\第1章\demo-1-16.html

body {
    background-color: moccasin;
    display: flex;
}

.box {
    font-size: 20px;
    width: 10em;
    height: 6em;
    background-color: mediumaquamarine;
    padding: 1em;
    border: 1em solid lightyellow;
    margin: 2em;
}

.box .inner {
    background-color: steelblue;
    width: 100%;
    height: 100%;
}

令两个容器分别使用不同的盒模型,实际效果是content-box[图1-19(a)]要大得多,是因为content-box实际占据的空间是14em*10em,而border-box[图1-19(b)]实际占据的空间只有10em*6em。

X:\实例代码\第1章\demo-1-16.html

.box:nth-child(1) {box-sizing: content-box;}
.box:nth-child(2) {box-sizing: border-box;}

在实践中,我们推荐使用border-box这种模型,它比较符合生活经验,当设置好width、height属性之后,再调整padding和border时,都只是对盒内有影响,不会影响到盒子外面的元素。

1.7 定位

每个元素在页面上都有一个默认的位置,如果要让元素偏离这个默认位置,有两种方法:一种是相对定位;另一种是绝对定位。为了方便理解,我们打个比方:当你散步的时候,你和你的影子总是在一起,随着光源的变化,你的影子有时离你近一点,有时离你远一点,这时要想知道你的影子的位置,就要先找到你,因为影子的位置是以你的位置为基点来计算的,这就叫相对定位。再比如,你家的院子里养了一条小狗,在院子里撒开了跑,那这个院子就相当于是一个容器,小狗的位置可以从院子里固定的一个角落开始计算,就这叫绝对定位。

1.7.1 相对定位

相对定位是指以元素默认位置为起点,移动元素到一个新的位置,此时的topleftrightbottom都是相对于元素默认位置计算的。

例1-17 用两个圆环分别组成数字8,如图1-20所示。

DOM结构是个名为.eight的容器,其中有两个子元素,分别表示数字8的上部和下部。

X:\实例代码\第1章\demo-1-17.html

<figure class="eight">
    <div></div>
    <div></div>
</figure>

默认情况下组成数字8的两个圆环是紧挨着的且由上到下排列,如图1-20(a)所示。

X:\实例代码\第1章\demo-1-17.html

.eight {
    font-size: 6px;
    margin: 5em;
    background-color: lightyellow;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.eight div {
    border-radius: 50%;
    border: 3em solid lightgreen;
}

.eight div:nth-child(1) {
    width: 18em;
    height: 18em;
}

.eight div:nth-child(2) {
    width: 20em;
    height: 20em;
}

图1-20(b)中的上圆环比默认位置下移一些,与下边圆环的边框重叠在一起,使它看起来更像是数字8。这是因为此处使用了相对定位position: relative; top: 3em

X:\实例代码\第1章\demo-1-17.html

.eight:nth-child(2) div:nth-child(1) {
    position: relative;
    top: 3em;
}

1.7.2 绝对定位

绝对定位不是以默认位置计算的,是以它的父元素的位置作为起点计算的,而它的父元素,必须是被定位过的(被相对定位过或者被绝对定位过),如果父元素没被定位过,那就看父元素的父元素(也就是祖父元素)是否被定位过,直至找到一个被定位过的祖先,要是全找遍了都没有,那就以整个窗口body为父元素。

因为绝对定位首先要有一个被定位过的父元素,所以绝对定位是作用在子元素上的,也就是说,我们要先选定一个容器,然后再对容器里的子元素进行绝对定位。

例1-18 用CSS绘制一个伤心人的形象,如图1-21所示。

DOM结构是一个名为.main的容器,包含头(.head.body)和两只脚(.feet)3个子元素。

X:\实例代码\第1章\demo-1-18.html

<figure class="man">
    <div class="head"></div>
    <div class="body"></div>
    <div class="feet"></div>
</figure>

其中.man使用相对定位position: relative,3个子元素都使用绝对定位position: relative,代码中的.feet::before.feet::after伪元素表示两只脚,伪元素将在第2章中详细讲解,transform将在第8章详细讲解。

X:\实例代码\第1章\demo-1-18.html

.man {
    width: 12em;
    height: 33em;
    font-size: 10px;
    position: relative;
    color: gray;
    margin: 5em;
}

.head {
    position: absolute;
    width: 7em;
    height: 7em;
    background-color: currentColor;
    border-radius: 50%;
    right: 0;
}

.body {
    position: absolute;
    width: 6.2em;
    height: 14.4em;
    background-color: currentColor;
    top: 7em;
    border-radius: 100% 20% 0 0;
}

.feet::before,
.feet::after {
    content: '';
    position: absolute;
    width: 4em;
    height: 1.4em;
    background-color: currentColor;
    bottom: 0;
    left: -1.6em;
    border-radius: 1em 80% 0.4em 0.4em;
}

.feet::after {
    transform: translateX(5.6em) translateY(-0.6em) rotate(4deg);
}

属性topleftrightbottom都只有在元素被定位过之后才起作用。实践中为了塑造复杂的造型,我们多使用如例1-18这样的方式,即容器采用相对定位,然后子元素采用绝对定位。

1.8 布局

在CSS3推出之前,CSS一直没有针对布局的语法,大家都是在用各种技巧解决问题,早期是用table来布局,令HTML文件中充斥着与内容无关的标签,后来进化到用div布局,但又滥用了float,直至CSS3推出了flex布局和grid布局,才算从语法层面提供了解决方案。本书的示例回避使用float(你不知道这是什么也没关系,它已经过时了),仅使用flex和grid完成布局工作。

1.8.1 flex布局

若要使用flex布局,需先在父元素上声明display: flex;,这样它的所有直系子元素就成为flex元素。

① 居中

在flex布局出现之前,让一个元素在容器中居中的方法有很多种,充满各种技巧,但在flex中,居中是非常简单的。

例1-19 让一个元素在容器中垂直居中[图1-22(a)]、水平居中[图1-22(b)]和垂直水平居中[图1-22(c)]。

DOM结构是3个容器,每个容器包含1个子元素。

X:\实例代码\第1章\demo-1-19.html

<div>
    <span></span>
</div>
<div>
    <span></span>
</div>
<div>
    <span></span>
</div>

background-imagebackground-size为容器生成十字形的参考线,容器用蓝色背景,子元素是红色的圆,背景属性会在第4章详细讲解。

X:\实例代码\第1章\demo-1-19.html

div {
    font-size: 16px;
    width: 10em;
    height: 10em;
    background-color: skyblue;
    display: flex;
    margin: 2em;
    background-image: linear-gradient(
    transparent 50%,
    darkgreen calc(50% + 1px),
    transparent calc(50% + 1px)
),linear-gradient(
    to right,
    transparent 50%,
    darkgreen calc(50% + 1px),
    transparent calc(50% + 1px)
);
    background-size: 10em, 10em;
}

div span {
    width: 7em;
    height: 7em;
    background-color: hsl(0, 100%, 72%, 0.8);
    border-radius: 50%;
}

分别设置3个容器中子元素的居中方式,align-items: center;表示垂直居中,justify-content: center;表示水平居中,这两个属性一起使用,即align-items: center; justify-content: center;就表示垂直水平居中(居于容器正中)。

X:\实例代码\第1章\demo-1-19.html

div:nth-child(1) { align-items: center; }
div:nth-child(2) { justify-content: center; }
div:nth-child(3) {
    align-items: center;
    justify-content: center;
}

② 横向排列元素

例1-20 当容器中有多个子元素时,绘制图1-23所示的子元素的4种排列方式。

DOM结构是4个容器,每个容器包含3个子元素。

X:\实例代码\第1章\demo-1-20.html

<div class="container">
    <span></span>
    <span></span>
    <span></span>
</div>
<!-- 一共 4 组容器,此处略去后面的 3 组 -->

令背景为浅蓝色,子元素为深青色的圆。

X:\实例代码\第1章\demo-1-20.html

div {
    font-size: 16px;
    display: flex;
    align-items: center;
    width: 35em;
    height: 7em;
    background-color: lightblue;
    margin: 2em;
}
div span {
    width: 5em;
    height: 5em;
    background-color: darkcyan;
    border-radius: 50%;
}

接下来分别设置4个容器的中子元素的排列方式。图1-23(a):justify-content:flex-start表示居左;图1-23(b):justify-content: flex-end表示居右; 图1-23(c):justify-content: space-between表示首尾的两个元素挨着容器边缘,中间的其他元素平均排列;图1-23(d):justify-content: space-around表示首尾的两个元素与容器边框的距离是元素之间间距的一半,各元素平均排列。

X:\实例代码\第1章\demo-1-20.html

div:nth-child(1) { justify-content: flex-start; }
div:nth-child(2) { justify-content: flex-end; }
div:nth-child(3) { justify-content: space-between; }
div:nth-child(4) { justify-content: space-around; }

③ 纵向排列元素

当子元素纵向排列时,在容器中增加flex-direction: column;属性即可。

例1-21 本例仍用例1-20的DOM结构,令子元素用4种纵向排列方式排列,如图1-24所示。

CSS代码也延用例1-20的代码,其中,div spandiv-nth-child(n)都没有变化,只是div容器增加了flex-direction: column;属性,并且调换了div容器widthheight的值。最上面的body { display: flex }是为了使4个容器横向排列。

X:\实例代码\第1章\demo-1-21.html

body {
    display: flex;
}
div {
    font-size: 16px;
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 7em;
    height: 25em;
    background-color: lightblue;
    margin: 1em;
}

④ 轴

flex布局没有使用leftrighttopbottom这些方位词,而是引入了轴的概念。所谓轴,就是像数轴那样的有方向的线,用两条数轴可以定义一个平面直角坐标系,所以flex布局就有了“主轴”和“交叉轴”的概念,“主轴”是子元素延伸的方向,“交叉轴”是与“主轴”垂直的轴。在一条轴上,flex-start代表轴的起点位置,flex-end代表轴的终点位置。如果轴是从左到右方向的,那么flex-startflex-end就分别代表左端和右端[图1-25(a)],而如果轴是从上到下方向的,那么flex-startflex-end就分别代表顶端和底端[图1-25(b)]。

为什么flex要引入这么复杂的轴系统来代替直观的方位名词呢?因为网页中有大量的文字,各种语言对文字的书写和阅读方向是不同的,英语是在纸的上部从左到右书写,而古汉语是在纸的右侧从上到下书写,其他语言还有不同的书写方式,所以,为了能用统一的抽象概念描述这些不同的书写体系,就创作出了轴系统。

flex-direction的用途是设置主轴的方向,默认值是row,表示从左到右延伸,另一个常用值就是column,表示从上到下延伸,定义了主轴的方向,同时也就定义了交叉轴的方向。

严格地说,justify-content: center;表示子元素在主轴方向上居中,而align-items: center;表示子元素在交叉轴方向上居中。随着主轴方向的变化,这两个属性的含义会发生变化,如表1-2所示。

表1-2

flex布局的复杂之处就在于有时你需要在大脑中切换轴的方向,才能理解浏览器对相关属性的渲染逻辑。

1.8.2 grid布局

flex布局本质上是令所有子元素排列在一条轴线上,尽管在子元素较多时可以回行显示,但逻辑上仍是同一行,就像一个<p>标签中有很长一段文本,尽管回行显示了,但逻辑上仍是一段文本。如果子元素要布局成行列形式的矩阵,flex就不能实现了,于是grid布局应运而生。grid布局的思路是对早年间table布局的升华,即把网页看成有若干行和列的网格,然后让各元素分别与网络线对齐。

若要使用grid布局,需先在父元素上声明display: grid;,这样它的所有直系子元素就会成为grid元素。

例1-22 用grid布局绘制图1-26所示的效果。

DOM结构共3组容器,每一组容器均包含12个子元素。

X:\实例代码\第1章\demo-1-22.html

<div class="container">
    <span></span>
    <span></span>
    <!-- 一共 12 个 <span> 标签,此处略去 8 个 -->
    <span></span>
    <span></span>
</div>
<!-- 一共有 3 个上面这样的容器,此处略去后面的 2 个 -->

每个子元素是一个绿色的圆,并在容器中声明使用grid布局,其中grid-gap的意思是子元素之间的间隔。

X:\实例代码\第1章\demo-1-22.html

.container {
    font-size: 10px;
    display: grid;
    grid-gap: 0.5em;
    background-color: lightblue;
}
.container span {
    width: 5em;
    height: 5em;
    border-radius: 50%;
    background-color: darkcyan;
}

接下来设置子元素的行列矩阵。

X:\实例代码\第1章\demo-1-22.html

.container:nth-child(1) { grid-template-columns: repeat(4, 1fr); }
.container:nth-child(2) { grid-template-columns: repeat(3, 1fr); }
.container:nth-child(3) { grid-template-columns: repeat(2, 1fr); }

grid-template-columns用于规定每一行要分成几列,以及每列的尺寸,repeat(4, 1fr)表示把一行平均分成4份,1fr是一个长度单位,表示平分之后的1份的宽度。

第1组容器的grid-template-columns属性值是repeat(4, 1fr),因为一共有12个子元素,每行4个元素,所以组成了4列3行的矩阵[图1-26(a)];第2组容器的grid-template-columns属性值是repeat(3, 1fr),表示把一行平分成3份,这样就组成了3列4行的矩阵[图1-26(b)];类似地,第3组容器组成了2列6行的矩阵[图1-26(c)]。

grid布局的体系比flex布局复杂得多,对于本书的示例来说,我们只掌握这些知识就暂时够用了。

flex布局和grid布局不是互相替代的关系,而是互相合作的关系,在一个复杂的页面中,通常用grid做全局化粗粒度的布局,然后再用flex布局对每个网格进行细粒度的布局。详细介绍flex布局和grid布局足够写一本书了,内容也超出了本书讨论的范围,我们就此打住,继续介绍其他CSS特性。

1.9 重叠

当多个元素相互重叠时,就像Photoshop的多个图层叠加在一起,此时需要有一种机制来管理元素之间的叠加次序,z-index属性就是专门为此而设计的,虽然这个属性只是一个数字,但它却能让元素之间发生视觉上的有趣变化。

1.9.1 元素之间的重叠关系

z-index的含义是元素在z轴上所处的次序。可以把z轴想象成是一条垂直穿过屏幕的轴,以屏幕所在的平面为序号0,越靠近屏幕则值越大,越远离屏幕则值越小。网页可以由多个处于z轴不同位置的平面重叠而成,序号大的图层叠加在序号小的图层之上(图1-27)。

使z-index生效的前提是元素要被定位过(被相对定位或被绝对定位),这和leftrighttopbottom要生效,也需要元素被定位过是一样的。

z-index属性的默认值是auto,所有未设置z-index属性的元素并非位于同一平面内,事实上,元素在DOM文档中出现的顺序,即是它们的叠加次序。

例1-23 用z-index绘制4个彩色圆的不同排列效果,如图1-28所示。

DOM结构是一个包含4个子元素的容器。

X:\实例代码\第1章\demo-1-23.html

<div class="container">
    <span></span>
    <span></span>
    <span></span>
    <span></span>
</div>

令子元素为圆形,横向排列,用绝对定位使它们相互叠加,从左到右依次设置背景色为红、橙、黄、绿,在未设置z-index时,因为绿圆在DOM文档的最后,所以它在最上层,黄圆次之,橙圆再次之,红圆在最下层[图1-28(a)]。

X:\实例代码\第1章\demo-1-23.html

.container {
    height: 20em;
    font-size: 6px;
    display: flex;
    margin: 4em;
    position: relative;
}

.container span {
    display: block;
    width: 20em;
    height: 20em;
    border-radius: 50%;
    position: absolute;
}

.container span:nth-child(1) { left: 0; }
.container span:nth-child(2) { left: 10em; }
.container span:nth-child(3) { left: 20em; }
.container span:nth-child(4) { left: 30em; }

.container span:nth-child(1) { background-color: hsl(0, 90%, 70%); }
.container span:nth-child(2) { background-color: hsl(30, 90%, 70%); }
.container span:nth-child(3) { background-color: hsl(60, 90%, 70%); }
.container span:nth-child(4) { background-color: hsl(90, 90%, 70%); }

如果为元素指定了z-index属性,则元素就会按指定的值设置图层顺序,下面我们分别令这4个圆的z-index值为4、3、2、1,那么它们的叠加次序就会反转,变为红圆在最上、绿圆在最下[图1-28(b)]。

X:\实例代码\第1章\demo-1-23.html

.container span:nth-child(1) {z-index: 4;}
.container span:nth-child(2) {z-index: 3;}
.container span:nth-child(3) {z-index: 2;}
.container span:nth-child(4) {z-index: 1;}

如果我们为最左端的红圆和最右端的绿圆的z-index属性指定相同的值,比如1,中间的两个圆不指定z-index值,也就是默认的auto,那么红圆和绿圆将处于上层,中间的两个圆处于下层,同时中间的两个圆仍按它们在DOM文档中出现的先后顺序,黄圆叠加在橙圆上面[图1-28(c)]。

X:\实例代码\第1章\demo-1-23.html

.container span:nth-child(1) {z-index: 1;}
.container span:nth-child(2) {z-index: auto;}
.container span:nth-child(3) {z-index: auto;}
.container span:nth-child(4) {z-index: 1;}

1.9.2 含有子元素的容器之间的重叠关系

上面讨论的是多个单独元素之间的重叠关系,接下来我们把这些单独元素换成包含子元素的容器,继续讨论含有子元素的容器之间的重叠关系。

例1-24 本例演示容器间添加z-index属性前后的使用效果,如图1-29所示。

DOM结构是两个相同的容器.container,每个容器中各含1个子元素.sub

X:\实例代码\第1章\demo-1-24.html

<figure>
    <div class="container">
        <div class="sub"></div>
    </div>
    <div class="container">
        <div class="sub"></div>
    </div>
</figure>

容器本身是浅色的大正方形,子元素是深色的小正方形,第1个容器是绿色的,第2个容器是黄色的,黄色容器定位到与绿色容器有部分叠加。因为黄色容器在DOM中比绿色容器出现得晚,所以黄色容器位于绿色容器上层[图1-29(a)]。

X:\实例代码\第1章\demo-1-24.html

.container {
    width: 10em;
    height: 10em;
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 1em;
}

.container:nth-child(2) {
    top: -6em;
    left: 4em;
}

.sub {
    width: 7em;
    height: 7em;
    border-radius: 0.5em;
}

.container:nth-child(1) {background-color: lightgreen;}
.container:nth-child(2) {background-color: moccasin;}
.container:nth-child(1) .sub {background-color: green;}
.container:nth-child(2) .sub {background-color: orange;}

现在,我们为这两个容器和它们的子元素设置重叠关系,先令绿色容器的z-index值大于黄色容器的值(2 > 1),再令绿色容器子元素的z-index小于黄色容器子元素的值(3 < 4),那么此时这两个容器中的两个子元素哪个在上,哪个在下?答案是,绿上黄下,即使绿色的子元素的z-index要小一些。这是因为同一个父元素下的子元素之间才能比较z-index的大小,而这两个子元素分属不同的父元素,所以它们的z-index值不能比较。实际上,因为绿色容器的z-index值大于黄色容器的值,绿色容器将整体居于黄色容器之上[图1-29(b)]。

X:\实例代码\第1章\demo-1-24.html

.container:nth-child(1) {z-index: 2;}
.container:nth-child(2) {z-index: 1;}
.container:nth-child(1) .sub {z-index: 3;}
.container:nth-child(2) .sub {z-index: 4;}

1.9.3 主元素与子元素(伪元素)之间的重叠关系

前面讨论了元素与元素、容器与容器之间的重叠关系,接下来介绍主元素与子元素之间的重叠关系。主元素与伪元素是父子关系的一种特殊形式,因为伪元素应用较多,所以此处用伪元素为例来探讨主元素与子元素之前的重叠关系。此节涉及的伪元素概念将在第2章详细讲解。

一个主元素可以有两个伪元素,这样加上主元素本身一共是3个元素,它们在DOM中的顺序为主元素、::before伪元素、::after伪元素,所以这3个元素默认的叠加关系是:主元素在最下面,中间是::before伪元素,最上面是::after伪元素。

例1-25 本例展示主元素与子元素间的不同重叠关系,其不同效果如图1-30所示。

DOM很简单,只有1个元素。

X:\实例代码\第1章\demo-1-25.html

<div></div>

::before伪元素是黄圆,::after伪元素是橙圆,默认情况下,绿圆在最下面,黄圆在中间,橙圆在最上面[图1-30(a)]。

X:\实例代码\第1章\demo-1-25.html

div,
div::before,
div::after {
    width: 10em;
    height: 10em;
    border-radius: 50%;
}

div {
    font-size: 10px;
    margin: 4em;
    position: relative;
    background-color: lightgreen;
}

div::before,
div::after {
    content: '';
    position: absolute;
    top: 5em;
}

div::before { background-color: moccasin; }
div::after { background-color: orange; }

div::before { right: 3em; }
div::after { left: 3em; }

如果保持主元素的z-indexauto,令伪元素的z-index为负数,那么伪元素将位于主元素之下,伪元素之间按照z-index值的顺序排列,黄圆是-1,橙圆是-2,所以黄圆在橙圆的上层[图1-30(b)]。

X:\实例代码\第1章\demo-1-25.html

div {z-index: auto;}
div::before {z-index: -1;}
div::after {z-index: -2;}

如果为主元素的z-index明确设置一个数值,哪怕主元素z-index的值大于伪元素的值,主元素仍将位于下层,例如我们把主元素的值改为正数1,子元素的值改为负数,按理说正数应在负数之上,但实际上我们看到主元素反而位于下层[图1-30(c)]。

X:\实例代码\第1章\demo-1-25.html

div {z-index: 1;}
div::before {z-index: -1;}
div::after {z-index: -2;}

究其原因,主元素和伪元素因不是同级关系,所以它们之间的z-index不能比较,一旦主元素设置为z-index值,不论值是多少,主元素都作为整个容器最下面的一层,其子元素均位于主元素之上。

1.10 继承和引用

1.10.1 继承

有些CSS属性的属性值默认采用父元素的属性值,如color属性,这称为继承,还有一些CSS属性值是不继承的,如margin

例1-26 用CSS创作几个字母卡片,如图1-31所示。我们通过这个例子来体会一下无处不在的继承。

DOM结构是在名为.word的容器中包含了几个子元素,每个子元素包含1个字母。

X:\实例代码\第1章\demo-1-26.html

<figure class="word">
    <div>c</div>
    <div>s</div>
    <div>s</div>
</figure>

.word设置的font-familyfont-sizecolortext-transform属性都被子元素<div>继承了,所以我们不用再为每一个<div>设置它们的样式了。

X:\实例代码\第1章\demo-1-26.html

.word {
    font-family: monospace;
    font-size: 100px;
    color: orange;
    text-transform: uppercase;
    width: 2em;
    display: flex;
    justify-content: space-around;
}

.word div {
    position: relative;
}

.word div::before {
    content: '';
    position: absolute;
    height: 100%;
    width: 130%;
    left: -15%;
    background-color: moccasin;
    border-radius: 0.1em;
    z-index: -1;
    transform: rotate(-25deg);
}

1.10.2 引用颜色

关键字currentColor表示使用“当前”颜色,“当前”颜色是指color的属性值。currentColor的用途是为了在边框、背景、渐变函数中使用与前景色一致的颜色。

例1-27 本例演示了currentColor的用法,其效果见图1-32。

DOM结构是一个包含3个子元素的容器。

X:\实例代码\第1章\demo-1-27.html

<div class="container">
    <span></span>
    <span></span>
    <span></span>
</div>

令子元素为圆形,并设置一个方格背景图案,此时因为没有明确指定color属性,所以子元素继承了父元素bodycolor属性值,也就是黑色,那么currentColor也就是黑色了,所以现在画出的方格图案是黑色的,方格的分隔线的颜色是lightyellow

X:\实例代码\第1章\demo-1-27.html

.container {
    display: flex;
}
.container span {
    width: 10em;
    height: 10em;
    margin: 1em;
    border-radius: 50%;
    background: repeating-radial-gradient(currentColor, lightyellow);
    background-size: 10% 10%;
}

方格的默认颜色是lightblue,使用它的父元素的color属性为十字型的5个方格上色,3组容器的color属性分别设置为goldhotpinklimegreen3种颜色。

接下来我们分别设置3个子元素的color属性为橙色、粉色、绿色,则3个子元素的背景分别变成了橙色方格[图1-32(a)]、粉色方格[图1-32(b)]和绿色方格[图1-32(c)]。

X:\实例代码\第1章\demo-1-27.html

.container span:nth-child(1) { color: orange; }
.container span:nth-child(2) { color: hotpink; }
.container span:nth-child(3) { color: limegreen; }

在2.3.2节的示例“拼接圆环”中,也有类似应用,本节是设置背景渐变颜色,2.3.2节是设置边框颜色。

1.10.3 引用尺寸

height: inherit; width: inherit;可以令子元素继承父元素的容器尺寸,但是,通常情况下,一个父元素包含多个子元素,每个子元素的尺寸都比主元素小,所以需要另一种方式,令子元素可以引用父元素的尺寸,同时还能设置自身的尺寸,这种方式就是“百分比值”。

几乎所有与长度相关的属性,都可以使用百分比值。以width属性为例,100%即等于父元素width的属性值,如果父元素的width15em,那么此时子元素的width: 90%表示子元素的宽是13.5em

在1.10.1节的例1-26中,文字背后矩形色块的heightwidthleft属性的值都是百分比值,它们都引用自主元素<span>

例1-28 本例演示了百分比值的用法,它是对1.2.2节的例1-2的重构,用于绘制与图1-1相似的字母i,如图1-33所示。

容器<figure>设置了明确的heightwidth值,上部的圆点的宽是100%,即与容器一样宽,高是33%;下部的矩形的宽也是100%,即与容器一样宽,高是66%

X:\实例代码\第1章\demo-1-28.html

figure {
    display: flex;
    flex-direction: column;
    width: 50px;
    height: 160px;
    outline: 1px dashed black;
}

    figure .div1 {
    width: 100%;
    height: 33%;
    background-color: orange;
    border-radius: 50%;
    margin-bottom: 10px;
}

    figure .div2 {
    width: 100%;
    height: 66%;
    background-color: gold;
    border-radius: 10px;
}

除了用于尺寸,百分比值也用于颜色(如HSL颜色)、动画关键帧(将在第10章详细讲解)等属性。但在那些场景下,它们并不代表引用父元素的值,就另当别论了。

相关图书

从零开始 PHP网页开发基础
从零开始 PHP网页开发基础
全栈工程师Web开发指南
全栈工程师Web开发指南
Web前端开发精品课 HTML CSS JavaScript基础教程
Web前端开发精品课 HTML CSS JavaScript基础教程
HTML CSS JavaScript 网页制作从入门到精通 第3版
HTML CSS JavaScript 网页制作从入门到精通 第3版
网页设计与前端开发 Dreamweaver+Flash+Photoshop+HTML+CSS+JavaScript 从入门到精通
网页设计与前端开发 Dreamweaver+Flash+Photoshop+HTML+CSS+JavaScript 从入门到精通
网站设计 开发 维护 推广 从入门到精通
网站设计 开发 维护 推广 从入门到精通

相关文章

相关课程