网页创意视觉表达 技术指导&项目实战
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不像是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月
本章将回顾CSS中重要的基本概念。对于已经有过CSS的编写经验的读者,建议也不要错过本章的内容。基本概念通常都会有些枯燥,但从艺术设计的角度来看,即使不运用复杂的概念也可以巧妙地表达内容。本章提供了29个示例,它们充分展现了CSS丰富的表现力,你会发现只用寥寥几行CSS代码就能描绘出杨辉三角形、象棋棋盘、素数集合等。读完本章,就会发现用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开始,规范被分解为若干个独立的小模块,例如文本、颜色、定位等都是单独的小模块,便于各模块分别发展,就像一个单细胞生物进化成了多细胞生物,各个模块之间相互分工和依赖,提供了更强大的生命力,本书内容主要涉及伪元素、背景和边框、滤镜、缓动、变形、动画等模块。
网页中书写CSS代码的方法有以下3种。
第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文档中,不方便集中管理,每个元素的样式也不能太复杂,否则写起来就很不方便了。这种方法我们不推荐。
第2种方法称为“style元素”,它把网页中所有元素的样式都集中写到一个名为<style>的标签里,放在页面的DOM结构之前。因为样式不是写在元素的属性里,为了区分不同元素的样式,就要分别为各元素指定名字,然后在<style>标签里按元素的名称逐个书写样式。
例1-2 用“style元素”法绘制图1-1所示的字母i。
本例是对例1-1的重构。元素的class="div1"和class="div2"属性定义了这两个子元素的类名为div1和div2,然后在<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>
第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-1列出了常用的一些CSS属性,因为这些属性的含义很简单,这里就不详细讲解了。如果已有CSS的使用经验,相信对这些属性不陌生。如果是第一次接触CSS,通过尝试修改这些属性的值,再对比修改前和修改后的效果,就可以很快了解和掌握它们了。
表1-1
为了灵活地选择将要设置样式的元素,CSS提供了很多种选择器,在此我们介绍常用的几种。
顾名思义,标签选择器就是用标签名称作为选择器。
例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节要讨论的类选择器所擅长的。
类选择器是指为元素指定一个class属性,也称为样式类,然后在样式表中通过这个属性值来引用元素。
例1-5 在例1-4的基础上,本例将演示如何使用类选择器绘制图1-3所示的一组红绿灯。
DOM结构与例1-4类似,但为所有元素定义了样式类名称。为<figure>元素指定了类名traffic-lights,为3个<div>都指定了类名light,再分别为每个<div>指定不同的颜色类名red、yellow和green。
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; }
可以为每个元素起不同的类名来区分每个元素,如red、yellow和green,也可以为有共同属性的元素起相同的类名,如light,还可以为一个元素分配多个类名,多个类名间用空格分隔,如light red。
还有一种名为“ID选择器”的选择器,它通过为元素命名唯一的ID名称,然后在样式表中以#id的形式引用元素。因为通常ID属性是配合JavaScript动态程序使用的,所以本书不在样式表中使用“ID选择器”。
本书理论部分的示例有时会用标签选择器,但在复杂的项目实例中,则几乎只用到类选择器。
DOM结构是树状结构,也就是元素可以一层一层地嵌套,便于有条理地组织内容。相应地,对于内层元素的类选择器,为了提高可读性,我们应该让它体现出DOM的层次结构,按照“父类 子类”的格式来书写。
例1-6 使用CSS绘制一棵简单的树,如图1-4所示。
它的DOM结构体现了tree→branch→leaf的层级关系。
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个或最后一个元素,或者隔一个选一个元素,或者选择处于鼠标指针悬停状态的元素。下面介绍其中几个常用的。
① :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; }
CSS的属性值有很多种数据格式,其中最复杂的是表示长度和颜色的方式。
① 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,绝对定位的top和left也是用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改为150px,20px改为30px,一共有8个属性用到了px,那就要同时改8处。再假如要把放大镜调整到原大的2.15倍,还要再改这8处,而且计算也变复杂了,可见px的缺点就是因为它是绝对尺寸,调整起来很麻烦。
虽然本章之前的示例都是用px作为单位,那是因为我们还没有对长度单位进行深入探讨,所以采用一种大家都容易理解的长度单位,但本书并不推荐使用px,此后的示例中将最大限度减少使用px。
② em
和绝对尺寸相对的一个概念是相对尺寸,也就是它先确定一个基准,然后其他尺寸都以这个基准来计算大小,当要修改时,只要改一下基准值,其他尺寸就都按比例自动改变了。
em是指相对于font-size的大小,例如一个元素有属性font-size: 10px,那么1em就等于10px,1.5em就等于15px,5em就等于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改为10em,20px改为2em。
假如现在我们要调整这个放大镜的尺寸到原尺寸的1.5倍,只要把.magnifier选择器中的font-size属性值从10px改为15px即可,其他em单位的数值都不用改动。
浏览器默认的font-size是16px,它对应的1.1em=17.6px、1.2em=19.2px、1.3em=20.8px结果都不是整数,不方便计算。本书推荐将font-size设置为10px,这样1.1em=11px、1.2em=12px、1.3em=13px,结果都是整数,换算起来很方便。
之前说过要最大限度减少使用px,结合本节的内容,再加一句,就是在能使用em的时候,要尽量使用em。
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-17中的一组照片。
上面每一张照片都被放在一个相框中,每个相框由外到内的构成包括黑色的边框、边框内大面积的象牙白留白、照片本身,相框与相框之前没有紧贴在一起,相互之前留有间距。这种结构在生活中很常见,回想一下,地铁站的大幅海报、商场里电视机的陈列墙甚至楼房的窗户都能找到这种构图结构。为此,CSS将这种构图形式抽象出来,把它命名成“盒模型”,其具体组成如图1-18所示。
一个元素就是一个矩形“盒子”,它的内容(content)就像是相框中的照片,内容与边框(border)之前的留白称为内边距(padding),元素与元素之间的间距称为外边距(margin)。我们经常使用的width和height,是指内容的宽和高,不包括内边距、边框和外边距。
盒模型虽然经典,但它有一个很大的问题,就是不符合生活经验。在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时,都只是对盒内有影响,不会影响到盒子外面的元素。
每个元素在页面上都有一个默认的位置,如果要让元素偏离这个默认位置,有两种方法:一种是相对定位;另一种是绝对定位。为了方便理解,我们打个比方:当你散步的时候,你和你的影子总是在一起,随着光源的变化,你的影子有时离你近一点,有时离你远一点,这时要想知道你的影子的位置,就要先找到你,因为影子的位置是以你的位置为基点来计算的,这就叫相对定位。再比如,你家的院子里养了一条小狗,在院子里撒开了跑,那这个院子就相当于是一个容器,小狗的位置可以从院子里固定的一个角落开始计算,就这叫绝对定位。
相对定位是指以元素默认位置为起点,移动元素到一个新的位置,此时的top、left、right、bottom都是相对于元素默认位置计算的。
例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; }
绝对定位不是以默认位置计算的,是以它的父元素的位置作为起点计算的,而它的父元素,必须是被定位过的(被相对定位过或者被绝对定位过),如果父元素没被定位过,那就看父元素的父元素(也就是祖父元素)是否被定位过,直至找到一个被定位过的祖先,要是全找遍了都没有,那就以整个窗口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); }
属性top、left、right、bottom都只有在元素被定位过之后才起作用。实践中为了塑造复杂的造型,我们多使用如例1-18这样的方式,即容器采用相对定位,然后子元素采用绝对定位。
在CSS3推出之前,CSS一直没有针对布局的语法,大家都是在用各种技巧解决问题,早期是用table来布局,令HTML文件中充斥着与内容无关的标签,后来进化到用div布局,但又滥用了float,直至CSS3推出了flex布局和grid布局,才算从语法层面提供了解决方案。本书的示例回避使用float(你不知道这是什么也没关系,它已经过时了),仅使用flex和grid完成布局工作。
若要使用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-image和background-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 span和div-nth-child(n)都没有变化,只是div容器增加了flex-direction: column;属性,并且调换了div容器width和height的值。最上面的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布局没有使用left、right、top、bottom这些方位词,而是引入了轴的概念。所谓轴,就是像数轴那样的有方向的线,用两条数轴可以定义一个平面直角坐标系,所以flex布局就有了“主轴”和“交叉轴”的概念,“主轴”是子元素延伸的方向,“交叉轴”是与“主轴”垂直的轴。在一条轴上,flex-start代表轴的起点位置,flex-end代表轴的终点位置。如果轴是从左到右方向的,那么flex-start和flex-end就分别代表左端和右端[图1-25(a)],而如果轴是从上到下方向的,那么flex-start和flex-end就分别代表顶端和底端[图1-25(b)]。
为什么flex要引入这么复杂的轴系统来代替直观的方位名词呢?因为网页中有大量的文字,各种语言对文字的书写和阅读方向是不同的,英语是在纸的上部从左到右书写,而古汉语是在纸的右侧从上到下书写,其他语言还有不同的书写方式,所以,为了能用统一的抽象概念描述这些不同的书写体系,就创作出了轴系统。
flex-direction的用途是设置主轴的方向,默认值是row,表示从左到右延伸,另一个常用值就是column,表示从上到下延伸,定义了主轴的方向,同时也就定义了交叉轴的方向。
严格地说,justify-content: center;表示子元素在主轴方向上居中,而align-items: center;表示子元素在交叉轴方向上居中。随着主轴方向的变化,这两个属性的含义会发生变化,如表1-2所示。
表1-2
flex布局的复杂之处就在于有时你需要在大脑中切换轴的方向,才能理解浏览器对相关属性的渲染逻辑。
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特性。
当多个元素相互重叠时,就像Photoshop的多个图层叠加在一起,此时需要有一种机制来管理元素之间的叠加次序,z-index属性就是专门为此而设计的,虽然这个属性只是一个数字,但它却能让元素之间发生视觉上的有趣变化。
z-index的含义是元素在z轴上所处的次序。可以把z轴想象成是一条垂直穿过屏幕的轴,以屏幕所在的平面为序号0,越靠近屏幕则值越大,越远离屏幕则值越小。网页可以由多个处于z轴不同位置的平面重叠而成,序号大的图层叠加在序号小的图层之上(图1-27)。
使z-index生效的前提是元素要被定位过(被相对定位或被绝对定位),这和left、right、top、bottom要生效,也需要元素被定位过是一样的。
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-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;}
前面讨论了元素与元素、容器与容器之间的重叠关系,接下来介绍主元素与子元素之间的重叠关系。主元素与伪元素是父子关系的一种特殊形式,因为伪元素应用较多,所以此处用伪元素为例来探讨主元素与子元素之前的重叠关系。此节涉及的伪元素概念将在第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-index为auto,令伪元素的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值,不论值是多少,主元素都作为整个容器最下面的一层,其子元素均位于主元素之上。
有些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-family、font-size、color、text-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); }
关键字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属性,所以子元素继承了父元素body的color属性值,也就是黑色,那么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属性分别设置为gold、hotpink和limegreen3种颜色。
接下来我们分别设置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节是设置边框颜色。
用height: inherit; width: inherit;可以令子元素继承父元素的容器尺寸,但是,通常情况下,一个父元素包含多个子元素,每个子元素的尺寸都比主元素小,所以需要另一种方式,令子元素可以引用父元素的尺寸,同时还能设置自身的尺寸,这种方式就是“百分比值”。
几乎所有与长度相关的属性,都可以使用百分比值。以width属性为例,100%即等于父元素width的属性值,如果父元素的width是15em,那么此时子元素的width: 90%表示子元素的宽是13.5em。
在1.10.1节的例1-26中,文字背后矩形色块的height、width、left属性的值都是百分比值,它们都引用自主元素<span>。
例1-28 本例演示了百分比值的用法,它是对1.2.2节的例1-2的重构,用于绘制与图1-1相似的字母i,如图1-33所示。
容器<figure>设置了明确的height、width值,上部的圆点的宽是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章详细讲解)等属性。但在那些场景下,它们并不代表引用父元素的值,就另当别论了。