书名:CSS世界
ISBN:978-7-115-47066-9
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
• 著 张鑫旭
责任编辑 杨海玲
• 人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
• 读者服务热线:(010)81055410
反盗版热线:(010)81055315
本书从前端开发人员的需求出发,以“流”为线索,从结构、内容到美化装饰等方面,全面且深入地讲解前端开发人员必须了解和掌握的大量的CSS知识点。同时,作者结合多年的从业经验,通过大量的实战案例,详尽解析CSS的相关知识与常见问题。作者还为本书开发了专门的配套网站,进行实例展示、问题答疑。
作为一本CSS深度学习的书,书中介绍大量许多前端开发人员都不知道的CSS知识点。本书语言通俗易懂,内容深入浅出,并结合实战经验,更适合对CSS有所了解的前端开发人员阅读。通过阅读本书,读者会对CSS世界的深度和广度有一个全新的认识。
我是一个利他主义非常明显的人,这多半与小时候都是在他人的帮助下成长有关,帮助我的有亲戚、邻居、老师,以及很多不认识的人。所以,现在的自己总是很乐意帮助他人成长。
我从2007年读大三的时候开始接触并使用CSS,到现在已经整整10年了,在这10年时间里,我从未间断过对CSS的研究和学习。现在想想,能够坚持下来还真是不容易,其核心动力其实就是上面的“帮助他人,成就自我”。
开始的时候,我和大多数人一样,因为CSS简单,一开始成长很快,页面写多了之后还能够总结出一些准则之类的东西,并自我感觉良好,或许是自己运气好,误打误撞走出了庐山幻境,突破了学习CSS的一个又一个瓶颈。但是,在与诸多同行的邮件交流中我发现,很多CSS开发人员感到迷茫,职位得不到重视,技术也无法提高,我感觉邮件的交流一次最多只能帮助一个人,效率实在太低。
人在做抉择的时候是需要有一些指引的。实际上,很多年前,我自己曾犹豫过,是否要继续深入学习CSS,探索每一个边界,因为对于个人而言,这会是一件吃力不讨好的事情,对于CSS这门语言,3年学习80分和10年学习90分对于产品价值的区别其实有限。但那一封封交流邮件坚定了自己的方向,艰苦的路让我一个人走就好了,等我踏遍整个CSS世界,再把完整的地图绘制出来,岂不就能帮助更多人了?
所以,随着自己的不断前行,身边的人越来越少,少到好像就我一个人,无比孤寂的时候,让我坚持下来的就是“日后可以帮助更多人,是很有价值的”的信念。
10年过去了,10年的努力和付出终于开始开花结果,而其中一个果实就是《CSS世界》这本书。
10年风雨积累,踏遍CSS世界的千山万水,哪里有美景,哪里有秘境,哪里是陷阱,哪里是路径,我全了然于心。我这样做为的就是给予清晰明确的指引,拓展对CSS世界的认知,挖掘CSS的潜力,帮助他人突破一个又一个CSS学习的瓶颈。
大家应该都注意到了,最近行业非常缺前端开发人员,前端开发人员培训机构也如雨后春笋般大量涌现。拨开眼前的面纱,定睛一看,会发现,缺的其实不是前端,而是优秀的、有资历的、技术有深度的前端开发人员。
通过和一些前端同行、某些人力资源接触和我收到的诸多简历我发现,目前的现状是这样的:行业里有很大一拨儿人,也自称为前端开发人员,但他们仅仅是可以根据设计稿写出页面这种水平。换句话说,就是会HTML和CSS以及一点儿JavaScript。环顾四周,这种程度的人实在是太多了,完全没有任何技术上的优势。虽然这些也是前端开发人员,但是公司要抢的前端开发人员并不是这类人。
为什么会这样呢?
因为CSS这门语言入门实在是太简单了,比如说我夫人,完全不懂代码,我手把手教她1个星期,写出一个长得像某某网首页的页面绝对是没问题的,因为CSS常用属性就那么多,且鲜有逻辑,无须算法,熟记各个属性值对应的特性就能上手了。所以,很多没有编程基础的人,就通过HTML和CSS进入了这个圈子。但当他们发现自己可以很愉快地实现页面的时候,就会觉得CSS也就这样,导致困于庐山,止步不前,就算日后听到或见到“CSS深入很难”的言论并打算着手精进,也不知道接下来该怎么走、如何突破现有的瓶颈,于是就产生了迷茫。
这类高不成低不就的前端人员急需本书深入“CSS世界”,突破瓶颈,告别迷茫。
在这个世界上,越是看似简单的东西反而越是难以深入。CSS这门语言也是如此。很多自认为学了CSS有八九成的人,实际上仅仅是熟记CSS手册中的各种属性,或者理解一些CSS概念,再进一步,甚至对某一两个CSS属性有过深入的分析。但是,这些人依然无法理解很多页面上看似简单的现象(我想更多的是根本就没在意这些现象),也无法基于现有的规则创造一些完全创新的CSS实现,仅仅停留在熟练地使用这种程度。
为什么会这样呢?那是因为CSS是一门有别于传统程序语言的语言。绝大多数编程语言,比方说JavaScript,各种字符串、数组、方法,记住一个就是一个,使用的时候,forEach()
就是循环,replace()
就是替换,不要担心执行replace()
的时候字符串突然增加了!很多人就是用这种思路学习CSS的,导致很快就遇到了天花板。为什么呢?这是因为,在CSS的世界里,页面上的任何看似简单的呈现都是由许许多多CSS属性共同作用的结果。例如,对于一个图片浮动,单纯认为只有float
在工作是不全面的,实际上,行高、字体、鼠标手形等都在暗地里“搞鬼”,此时如果仅仅套用一两个CSS属性值应有的表现来理解,是根本理解不了的。换句话说,有些人的CSS水平之所以停滞不前,是因为他们没有把所有的CSS当作一个整体,放在一个完整的世界中去看待。所以,没有比“学CSS看看CSS中文手册就够了”更愚蠢的言论了,手册仅仅是表层的、独立的一些特性,每个CSS属性在CSS世界中都是有其存在的原因的,都是和其他多个CSS属性发生着千丝万缕的关系的,这背后的种种远比他们想象得要庞大很多。
而本书完完全全区别于传统教条式的手册或参考书或教程之类,一步一步带领读者接触真正丰富多彩、妙趣横生的CSS世界,一番畅快自在的CSS世界之旅下来,读者一定会得到不一样的“洗礼”,困扰多年的CSS成长瓶颈说不定就会不知不觉地突破了。
首先,大家务必明确这一点,那就是本书的所有内容都是我个人的理解。没错,是人的理解,不是干巴巴的文档。这些理解是我自己多年持之以恒对CSS研究和思考后,经过个人情感润饰和认知提炼加工的产物,完全是我自己所理解和认识的CSS世界,可能是不具有权威性的。写这本书主要是希望通过分享自己的心得给对CSS感兴趣的各个领域的人以启迪,引发其思考,或者使其有不一样的感悟。
从另一方面讲,本书正是因为其内容都是经过人这个个体的加工,并融入了情感化的思维,才能做到有的放矢、通俗易懂。我们大多数人是感性的,看伤感电影会哭,看综艺节目会笑。所以,与干巴巴的教条式的技术书籍相比,本书的表达方式和语言风格更能给人以心灵的启迪。读完之后,读者可能会有这样的想法:要是所有的技术书都这么写就好了。
另外,本书不是技术文档,也不是参考手册,因此知识点不会面面俱到,也就是说,不少CSS相关的内容我会忽略,例如,选择器这一部分最多提一下。同时,为了保证书的内容足够简练,简单的CSS语法和常规的使用在本书中基本上不会提及,只会重点分析在其他地方看不到的内容。
我专门为本书制作了一个网站(http://www.cssworld.cn),在这个配套网站读者可以了解更多关于本书或者我个人的一些信息。
最后,由于很多内容是个人理解,难免会有不准确或者让大家产生怀疑的地方,欢迎去官方论坛http://bbs.cssworld.cn/对应版块进行提问或反馈。
衷心感谢人民邮电出版社的每一个人。
感谢人民邮电出版社的编辑杨海玲,她的专业建议对我帮助很大,她对细节的关注令人印象深刻,她使我的工作变得更加轻松。
感谢那些致力于提高整个行业CSS水平而默默努力的优秀人士,感谢那些在我成长路上指出错误的前端同仁,是你们让我在探索边界的道路上可以走得更快、更踏实。
感谢读者,你们的支持给了我工作的动力。
最后,最最感谢我的妻子丹丹,没有她在背后的爱和支持,本书一定不会完成得这么顺利。
要深入理解一个事物之前,最好先对其整体有个大概了解,这样才不至于蠡测管窥。如果把CSS比作一座大山,则我们对整体的认知就好比知道这座山的位置、山势、道路状况等,这样,当我们深入其中的时候,就不容易迷路,不会做出错误的决策。
例如,对CSS这门语言特性的描述就有助于对CSS的整体认知。具体表现为:擅长C++或者Java之类的程序员学习CSS往往没有如鱼得水的感觉,其背后的原因是,典型的计算机开发语言看重逻辑思维和抽象能力,但是CSS这门语言本身并无逻辑可言,看重的是特性间的相互联系和具象能力。
具象往往以情感为纽带,无意识不自觉产生,是非常感性的一种能力,这往往是偏理性的程序员所不擅长的。在某些程序员眼中,CSS属性就是干巴巴的属性,无法建立类似“人与人关系”这种很情感化的联系,于是学习CSS总是只得其形、不得其髓。
当然,认知可以从多个角度进行。例如,接下来要介绍的CSS“世界观”以及CSS的历史故事,可以让我们多种角度同时进行认知,对CSS这门语言的理解更为准确和丰满。
对于CSS这门语言,我学习和研究已经有10年之久,在点点滴滴的积累中,逐渐形成了一套完整的体系。在CSS这个世界中,CSS并不是一个机械枯燥的语言,所有属性都是有血有肉、有着不同个性和身世的个体。不同的个体可以碰撞出不同的火花,激荡出异彩纷呈的故事。
这里,我们不妨“脑洞大开”一下:如果把CSS世界拍成动漫的话,会是什么样子?
首先,动漫名可以叫作《建筑神域》,讲述一群建筑魔法师为国家存亡惊心动魄战斗的故事。然后,出现了“Chrome王国”的几位建筑魔法师日常训练的画面。只见名为
width
的魔法师手持名叫选择器的法器,准确指向称为<div>
的最普通的块状建筑魔法石,口中念道:“层叠天星,幻化有形,50%,变!”只听见一声清脆的“啪”,<div>
魔法石宽度变成了原来的一半。然而,width
却锁眉摇头,口中喃喃念道:“1毫秒,还不够快,还要再练,不然在和‘IE王国’的战斗中很难占得先机!”(如图1-1所示)。
图1-1 CSS世界观示意1
此时,
width
突然发现前面1米之处有一块<span>
之石,具有class="test"
的特殊标记,立即拿出法器,念道:“类名之石,test
为名,为我选择,出!”话音刚落,<span>
之石蓝色荧光一闪,明眼人都能看出来,width
魔法师和<span>
魔法石现在处于契约状态。width
继续念道:“层叠天星,幻化有形,50%,变!”但<span>
魔法石却没有任何变化,此时width
一拍自己的脑门,似乎明白了什么,转过头对旁边的display
魔法师大声叫道:“小D,这边这边,过来帮个忙……来呀,快点……”只见
display
迅速结束自己的练习,屁颠屁颠跑过来:“咋啦?”“此为内联之石,我无法驾驭,你帮我重塑一下。”
“小问题!正好,魔法师技能委员会刚通过了我的一个新法术,我给你秀一秀?”
“哟,不错啊,快让我瞅瞅!”
“好嘞!”只见
display
拿出自己的法器,念道:“类名之石,test
为名,为我选择,出!”紧接着,“层叠天星,幻化有形,flex,变!”只听见一声清脆的“啪”,在两人的合作之下,
<span>
魔法石宽度也变化了(如图1-2所示)。
图1-2 CSS世界观示意2
“哟,很酷嘛!”
width
对display
竖着大拇指称赞道。只见
display
腼腆一笑,小手在面前轻轻一挥:“就算你这么夸我,人家也不会开心的啦……”
从上面的描述可以看出,在CSS世界中,HTML是魔法石,选择器就是选择法器,CSS属性就是魔法师,CSS各种属性值就是魔法师的魔法技能,浏览器就是他们所在的“王国”,“王国”会不断更新法律法规(版本升级),决定是否允许使用新的魔法石(HTML5新标签新属性),是否允许新的魔法师入“国籍”(CSS3新属性),或者允许魔法师使用某些新技能(CSS新的属性值),以及是否舍弃某些魔法技能(如display:run-in
);操作系统就是他们所在的世界,不同的操作系统代表不同的平行世界,所以,CSS世界有这么几个比较大的平行世界,即Windows世界、OS X世界以及移动端的iOS世界和Android世界。不同世界的浏览器王国的命运不一样,例如,在OS X世界中,IE王国是不存在的,而Safari王国却异常强大,但在Windows世界中,Safari王国却异常落寞。
以上这一切就构成了完整的CSS世界的“世界观”。
下面回答一个很重要的问题:为何要这样认识CSS世界呢?
首先,将抽象的CSS直接和具体的现实世界相对应,更加易于理解。试想一下,对于普通人,理解魔法师和魔法石是不是要比理解CSS代码容易得多?其次,以完整的体系来学习CSS要比单纯关注属性值理解得更加深刻,可以培养从宏观层面认识与理解CSS的习惯。再次,这也方便我们记忆,枯燥的代码总是过目就忘,鲜活的角色总是印象深刻。最后,这样也可以让本书散发出与众不同的气质。
世界都是创造出来的。很自然,CSS世界也是一点一点创造出来的。这世间上的事情只要发生了,都是有原因的。CSS世界的出现也不例外。
下面我们就来看一下CSS世界出现的历史。虽然我知道,有些人对这些历史可能不感兴趣,但是要想深入理解CSS属性的一些设计原因、表现原理还真离不开当时的历史环境。
大家可能都听说过马云1995年去美国,第一次接触了互联网,在这个时间点,HTML才是第一版且诞生没几年,W3C才刚刚成立,CSS还没出现。那时候的互联网几乎都是文字信息,显示一张图片都是要上天的感觉。
大家可能没意识到,那个时候前端的发展和现在一样快,设计师要求越来越多,HTML也越来越庞杂。急需要其他专门负责样式的语言,据说当时有几个样式表语言,最后是CSS胜出了,为什么呢?它的胜出靠的是“层叠”特性。
CSS全称是Cascading Style Sheets,翻译成中文就是“层叠样式表”。所谓“层叠”,顾名思义,就是样式可以层层累加,比方说页面元素都继承了12像素的大小,某标题就可以设置成14像素进行叠加。发现没?这种层叠策略对于样式的显示是相当的灵活。
于是,从1996年12月17日CSS1诞生后,CSS在样式呈现领域可谓所向披靡,没有遇到任何竞争对手。1998年5月12日CSS2发布,推行内容和表现分离,表格(table)布局开始落寞。
1998年腾讯、新浪和网易成立,当时搜狐则成立1年不到。那个时候是门户的时代,人们更关注的是信息的获取,所以网站的功能主要就是信息展示,信息是什么?在那个时代,在互联网领域,信息就是图片和文字。换句话说,那时候的网站前端技术关心的是图片和文字的呈现,而CSS2(包括9年之后,也就是2007年才出现的CSS2.1)都是为图文展示服务的。
我再重复一遍:CSS世界的诞生就是为图文信息展示服务的。这句话在本书中会非常频繁地出现,知道这一点你就会明白很多事情。
好,下面让我们回到本节开头的那句话——“世界都是创造出来的”!为何我要反复强调这句话呢?如果站在造物主的角度去思考CSS世界的种种表现,很多问题就会迎刃而解。
现在给你机会当一回造物主,让你自己重新构建一个CSS世界,唯一的要求就是,这个世界要非常便于图片和文字的呈现,你会去如何构建呢?
在2003年1月,SVG 1.1被确立为W3C标准。你没看错,是2003年。要知道,CSS 2.1是2007年才发布的。考虑到SVG开始火起来是最近几年,也就是差不多10年的时间,SVG都默默无闻,鲜有人问津,到底是怎么回事呢?
很多人认为SVG的竞争对手是Flash。对,是竞争对手。但是,现在看来,SVG显然要比Flash优秀很多,SVG开放、标准,和CSS和JavaScript都能很方便地进行交互,如果单纯SVG和Flash比,难说谁胜谁负。在我看来,造成SVG被冷落10年的原因不是别的,正是看似毫不相关的CSS,SVG是被CSS给打败的。
正如上面提到的,在很长一段时间里,网站的主要功能都是图片和文字信息的展示,但是,SVG的强项是图形,其文字内容的呈现实在不敢恭维。举个例子,在CSS中写上一段文字,这段文字会自然换行、多行显示,于是,可以像书本一样阅读;但是,在SVG中,文字要自动折行,感觉有点儿赶鸭子上架——强人所难。人家一看,SVG连基本的文字排版都做不好,要SVG何用?于是,SVG被“打入冷宫”,CSS一如既往被重用。
但是,如今技术得到了发展,Web呈现更加复杂和丰富多彩,图文显示仅仅是网页功能的一部分,于是,矢量且图形领域颇有造诣的SVG开始迎来了自己的第一春。
不知大家有没有思考过这样的问题:为什么CSS世界的图文显示能力那么强?为什么它可以抑制SVG这么多年?
答案就是:流!
和CSS有过亲密接触的人一定听过“文档流”这个概念,我个人总是习惯把“文档”二字去掉,直接称为“流”(纯粹个人爱好,因为够简洁)。听过它的人很多,但是,深入思考过“何为流?”这个问题的人怕是就没这么多了。
那究竟CSS世界中的“流”指的是什么呢?“流”实际上是CSS世界中的一种基本的定位和布局机制,可以理解为现实世界的一套物理规则,“流”跟现实世界的“水流”有异曲同工的表现。
现实世界中,如果我们让水流入一个容器,水面一定是平整的;我们在水里面放入物体,如普通的木头,此时水位就会上升,木头多半浮在水面上,但只露出一点点头,如图1-3所示。这些现象我们都会认为是理所当然的,因为这就是我们从小接触的一套物理规则。我们知道这套规则,就可以理解现象,并且预知现象。例如,水量超过容器的容积很多,我们就可以预测到水会溢出来。
感谢物理学,它让我们理解CSS世界的“流”就轻松多了。CSS世界的“流”似乎就是按照物理世界的“水流”创造的。
CSS世界构建的基石是HTML,而HTML最具代表的两个基石<div>
和<span>
正好是CSS世界中块级元素和内联级元素的代表,它们对应的正是图1-3所示的盛水容器中的水和木头,其特性表现也正如现实世界的水和木头,如图1-4所示。
图1-3 流
图1-4 CSS世界构建的基石HTML
所以,所谓“流”,就是CSS世界中引导元素排列和定位的一条看不见的“水流”。
在CSS2.1时代,我们直接称CSS为“流的世界”真是一点儿也不为过,整个CSS世界几乎就是围绕“流”来建立的,那么流是如何影响整个CSS世界的呢?
(1)擒贼先擒王。因为CSS世界的基石是HTML,所以只要让HTML默认的表现符合“流”,那么整个CSS世界就可以被“流”统治,而事实就是如此!
(2)特殊布局与流的破坏。如果全部都是以默认的“流”来渲染,我们只能实现类似W3C那样的文档网页,但是,实际的网页是有很多复杂的布局的,怎么办?可以通过破坏“流”来实现特殊布局。实际上,还是和“流”打交道。
(3)流向的改变。默认的流向是“一江春水向东流”,以及“飞流直下三千尺”。然而,这种流向我们是可以改变的,可以让CSS的展现更为丰富。因此,“文档流从左往右自上而下”这种说法是不严谨的,大家一定要纠正过来。
好了,下面我想反问大家:如果你是造物主,你会想到设计“流”这套机制来实现强大的图文排列功能吗?
好好想一想……是不是觉得目前CSS的设计还是很有智慧的?如果你来重新设计CSS,实现图文排列,你是否还有其他的设计思路,比方说“亲缘机制”之类?
适当地反问这些问题,通过逆向思维,会让我们对CSS世界有另外一个角度的认识。
所谓“流体布局”,指的是利用元素“流”的特性实现的各类布局效果。因为“流”本身具有自适应特性,所以“流体布局”往往都是具有自适应性的。但是,“流体布局”并不等同于“自适应布局”。“自适应布局”是对凡是具有自适应特性的一类布局的统称,“流体布局”要狭窄得多。例如,表格布局也可以设置为100%自适应,但表格和“流”不是一路的,并不属于“流体布局”。
CSS中最常用的魔法石,也就是最常使用的HTML标签,是<div>
,而<div>
是典型的具有“流”特性的元素,因此,曾经风靡的“div+CSS布局”,实际上指的就是这里的“流体布局”。
本书书名为《CSS世界》,这里的“世界”特指的是CSS2.1的世界,并不包括CSS3,CSS3的世界更为庞杂和宏大,但CSS2.1的世界已经足够我们畅游很多年了。现在前端技术发展迅猛,加上氛围略显浮躁,有必要让广大前端开发人员静下心来认识CSS2.1的世界,否则面对CSS3的真正到来,只能是浅水游弋、搬砖打杂。
对CSS2.1的全面支持是从微软公司的IE8开始的,因此,本书中几乎所有特性、行为表现都是针对IE8以上浏览器的。
table
自己的世界如果我没记错的话,<table>
比CSS还要老,也就是CSS正式诞生之前,<table>
就已经出现了。前面提到了“流影响了整个CSS世界”,其中并不包括<table>
。<table>
有着自己的世界,“流”的特性对<table>
并不适用,一些CSS属性的表现,如单元格的vertical-align,
也和普通的元素不一样。
虽然CSS2.1加强了和<table>
的联系,如对table
类别的display
属性值的支持等,但是本书并不会对<table>
进行专门的介绍,因为毕竟不是同一个世界的。
时代在变迁,科技在发展,人们对互联网的需求也在变化,以前的以图文展示为主的门户网站已经无法满足用户的需求。技术总是随着需求发展的,正如10年前的图文展示需求缔造了CSS世界一样,如今的移动互联网以及硬件发展也带动CSS进入了新的世界。
(1)布局更为丰富。
srcset
属性、CSS的object-fit
属性。(2)视觉表现长足进步。
transform
变换让元素有更多可能。filter
滤镜和混合模式让Web轻松变成在线的Photoshop;animation
让动画变得非常简单。上面提到的全部都是CSS3的新属性。因为CSS3的设计初衷是为了实现更丰富、更复杂的网页,所以基本上和“流”的关系并不大。可以说,和CSS2相比CSS3就是一个全新的世界,更加丰富,更加规范,更加体系化,也更加复杂。考虑到CSS3尚未完全成型,且自己尚未有足够深入的研究,无法同时驾驭太复杂的内容,因此,本书不会深入CSS3的知识点。
尽管本书内容会用很轻松的方式表达,但还是避免不了会出现一些CSS领域的专业术语。因此,在学习技术内容之前,我们需要先了解一下CSS世界里的一些专业术语。
首先,假设我们现在有如下一段常见的CSS代码:
.vocabulary {
height: 99px;
color: transparent;
}
下面就针对这段代码,逐一引出其涉及的专业术语。
属性对应的是平常我们书面或交谈时对CSS的中文称谓。例如,上面示意CSS代码中的height
和color
就是属性。当我们聊天或者分享时说起CSS的时候,嘴里冒出来的都是“这个元素高度99像素”,或者“这个文字颜色透明”,对吧?这里提到的“高度”和“颜色”就是CSS世界的属性,感觉有点儿像现实世界里人的姓氏。
“值”大多与数字挂钩。例如,上面的99px
就是典型的值。在CSS世界中,值的分类非常广泛,下面是一些常用的类型。
z-index:1中
的1
,属于<integer>,同时也属于<number>
。line-height:1.5中
的1.5
,属于<number>
。padding:50%
中的50%
,属于<percent>
。99px
。#999。
此外,还有字符串值、位置值等类型。在CSS3新世界中,还有角度值、频率值、时间值等类型,这里就不全部展示了。
顾名思义,关键字指的是CSS里面很关键的单词,这里的单词特指英文单词,abc
是单词吗?不是,因此,如果CSS中出现它,一定不是关键字。上面示例CSS代码中的transparent
就是典型的关键字,还有常见的solid
、inherit
等都是关键字,其中inherit
也称作“泛关键字”,所谓泛关键字,可以理解为“公交车关键字”,就是“所有CSS属性都可以使用的关键字”的意思。
CSS中目前可以称为变量的比较有限,CSS3中的currentColor
就是变量,非常有用。不过,这属于《CSS新世界》的内容,本书不会详细阐述,有兴趣的读者可以访问http://www.zhangxinxu.com/wordpress/?p=4385或者扫右侧的二维码,做简单的了解。
CSS中的单位有时间单位(如s
、ms),
还有角度单位(如deg
、rad
等),但最常见的自然还是长度单位(如px
、em
等)。需要注意的是,诸如2%
后面的百分号%
不是长度单位。再说一遍,%
不是长度单位!因为2%
就是一个完整的值,就是一个整体,我想你一定认为0.02
是值,没错,2%
也同样是值。
有人可能会有疑问,我就认为%
是单位,有什么关系,页面还是长那样,有必要这么较真吗?
问的很在理,如果大家平时没有看原始文档的习惯,没必要较真,知道怎么使用就好了。但是,如果经常去MDN或W3C看一些CSS技术文档,搞清楚概念,看文档的时候就不容易犯迷糊,就不会看不懂具体说些什么,尤其都是英文的时候。
可能有人会有疑问,“值”那里提到的<length>
,貌似和这里的“长度单位”比较暧昧啊?好眼力!没错,确实暧昧,但暧昧是不好的,我们必须把它们之间的关系搞清楚。一句话:
<number> + 长度单位 = <length>
如果继续细分,长度单位又可以分为相对长度单位和绝对长度单位。
(1)相对长度单位。相对长度单位又分为相对字体长度单位和相对视区长度单位。
em
和ex
,还有CSS3新世界的rem
和ch
(字符0的宽度)。vh
、vw
、vmin
和vmax
。(2)绝对长度单位:最常见的就是px
,还有pt
、cm
、mm
、pc
等了解一下就可以,在我看来,它们实用性近乎零,至少我这么多年一次都没用过。
值以函数的形式指定(就是被括号括起来的那种),主要用来表示颜色(rgba和hsla
)、背景图片地址(url
)、元素属性值、计算(calc
)和过渡效果等,如rgba(0,0,0,.5)
、url('css-world.png')
、attr('href')
和scale(-1)
。
属性冒号后面的所有内容统一称为属性值。例如,1px solid rgb(0,0,0)
就可以称为属性值,它是由“值+关键字+功能符”构成的。属性值也可以由单一内容构成。例如,z-index:1
的1
也是属性值。
属性名加上
属性值就是声明,例如:
color: transparent;
声明块是花括号({}
)包裹的一系列声明,例如:
{
height: 99px;
color: transparent;
}
出现了选择器,而且后面还跟着声明块,比如本小节一开始的那个例子,就是一个规则集:
.vocabulary {
height: 99px;
color: transparent;
}
选择器是用来瞄准目标元素的东西,例如,上面的.vocabulary
就是一个选择器。
“
.”这个点号开头的选择器。很多元素可以应用同一个类选择器。“类”,天生就是被公用的命。“
#”打头,权重相当高。ID一般指向唯一元素。但是,在CSS中,ID样式出现在多个不同的元素上并不会只渲染第一个,而是雨露均沾。但显然不推荐这么做。[]
的选择器,形如[title]{}
、[title= "css-world"]{}
、[title~="css-world"]{}
、[title^="css-world"]{}
和[title$="css-world"]{}等。
:)
的选择器,如:first-child
或:last-child
等。::first-line
、::first-letter
、::before和::after
关系选择器是指根据与其他元素的关系选择元素的选择器,常见的符号有空格、>
、~
,还有+
等,这些都是非常常用的选择器。
>
连接。适用于IE7以上版本。~
连接。适用于IE7以上版本。+
连接。适用于IE7以上版本。@规则指的是以@
字符开始的一些规则,像@media
、@font-face
、@page
或者@support
,诸如此类。
当某个浏览器中出现与其他浏览器不一样的行为或样式表现的时候,我们总会习惯把这种不一样的表现认为是浏览器的bug。但在CSS世界,这种认识是狭隘的。
在现实世界中,有法律来约束我们的行为,如果越界,就称为违法;同样地,在CSS世界里,有Web标准来约束元素的行为,如果越界,就称为bug。但是,法律总是人制定的,世间万象是不可能面面俱到的,会存在法律空白;同样地,Web应用场景千变万化,Web标准也是不可能面面俱到的,也会存在规范描述以外的场景,此时,各大浏览器厂家只能根据自己的理解与喜好去实现,一旦个性化就会出现差异,就会遇到“火狐火狐,你怎么啦?平时表现挺好的,今天怎么被IE带坏了?”的情景。实际上,此时遇到的表现差异并不是浏览器的bug,用计算机领域的专业术语描述应该是“未定义行为”(undefined behavior)
。
下面我们来看一个“未定义行为”的例子。
CSS世界中有很多伪类,其中一个比较常用的就是:active
,在IE8及以上版本的浏览器行为表现非常统一,支持非焦点元素[1],鼠标按下,执行:active
伪类对应的CSS样式,鼠标抬起还原。
通用情况下,:active
的表现都是符合预期的,但是,当遭遇其他一些处理的时候,事情就会变得不一样,具体指什么处理呢?
假设我们现在有一个<a>
标签模拟的按钮,CSS如下:
a:active { background-color: red; }
假设此按钮的DOM对象变量名为button
,JavaScript代码如下:
button.addEventListener("mousedown", function(event) {
// 此处省略N行
event.preventDefault();
});
也就是鼠标按下的时候,阻止按钮的默认行为,这样设置可以让拖动效果更流畅。
看似平淡无奇的一段代码,最后却发生了意想不到的情况:Firefox浏览器的:active
阵亡了,鼠标按下去没有UI变化,按钮背景没有变红!其他所有浏览器,如IE和Chrome浏览器,:active
正常变红,符合预期。
眼见为实,手动输入http://demo.cssworld.cn/2/2-1.php或者扫下面的二维码。图2-1左图所示为目标效果,右图所示是Firefox浏览器中的效果。
图2-1 Firefox浏览器:active
的作用效果
这里,Firefox和IE/Chrome浏览器表现不一样,这是Firefox浏览器的bug吗?这可不是bug,而是因为规范上并没有对这种场景的具体描述,所以Firefox认为:active
发生在mousedown
事件之后,你也不能说它什么,对吧?
像这种规范顾及不到的细枝末节的实现,就称为“未定义行为”。
[1] 像< a>
、< button>
这样的元素,当我们使用键盘进行Tab键切换的时候,是可以被focus
的,表现为虚框或者外发光,这类元素称为“焦点元素”;非焦点元素指没有设置tabindex
属性的< div>
、< span>
等普通元素。在IE6/IE7
浏览器下,非焦点元素对:active
置若罔闻。
第1章提过了,“流”之所以影响了整个CSS世界,就是因为影响了CSS世界的基石HTML。那具体是如何影响的呢?
HTML常见的标签有<div>
、<p>
、<li>
和<table>
以及<span>
、<img>
、和<em>
等。虽然标签种类繁多,但通常我们就把它们分为两类:块级元素(block-level element)和内联元素(inline element)。
注意,如果按照W3C的CSS规范区分,这里应该分为“块级元素”和“内联级元素” (inline-level element)。但是,在W3C的HTML4规范中,已经明确把HTML元素分成了“块级元素”和“内联元素”,没错,是“内联元素”而不是“内联级元素”。两个规范貌似有微小的冲突。本书中所采用的是“内联元素”这种称谓,原因有两点:第一,这种称谓更亲切、更自然,因为大家平时都是这么叫的;第二,使用“内联元素”这个称谓对我们深入理解与内联相关的概念并没有什么影响。考虑到本书的目的不是为CSS规范做科普,而是以通俗易懂的方式展示CSS的精彩世界,所以,采用了更老一点的HTML规范中的叫法。
“块级元素”对应的英文是block-level element,常见的块级元素有<div>
、<li>
和<table>
等。需要注意是,“块级元素”和“display
为block的元素”不是一个概念。例如,<li>
元素默认的display
值是list-item
,<table>
元素默认的display
值是table
,但是它们均是“块级元素”,因为它们都符合块级元素的基本特征,也就是一个水平流上只能单独显示一个元素,多个块级元素则换行显示。
正是由于“块级元素”具有换行特性,因此理论上它都可以配合clear
属性来清除浮动带来的影响。例如:
.clear:after {
content: '';
display: table; // 也可以是block,或者是list-item
clear: both;
}
手动输入http://demo.cssworld.cn/3/1-1.php或者扫右侧的二维码。
从容器的背景色高度变化我们可以看出清除的效果,如图3-1所示。
图3-1 容器的背景色高度变化的效果
实际开发时,我们要么使用block
,要么使用table
,并不会使用list-item
,主要有3个原因。
(1)list-item
的字符比较多,其他都是5个字符。
(2)会出现不需要的项目符号,如图3-2箭头所示。这其实并不是什么大问题,再加一行list-style:none
声明就可以了。
(3)IE浏览器不支持伪元素的display
值为list-item
。这是不使用display:list-item
清除浮动的主因,兼容性不好。对于IE浏览器(包括IE11),普通元素设置display:list-item
有效,但是:before/:after
伪元素就不行。
图3-2 出现项目符号
下面是提问环节了。请问,为什么IE浏览器不支持伪元素的display
值为list-item
呢?
其实这个问题的答案可以从下面这个问题中找到线索:请问,为什么设置display:list-item
,元素会出现项目符号?
list-item
元素会出现项目符号在CSS世界中,很多看似“理所当然”的现象的背后,实际上可能有一整套的体系支撑。挖掘简单现象背后的原因,会让你学到很多别人很难学到的CSS技能和知识。
回到这个问题。此问题本身并不难,但是,问题所能延伸出来的东西就要吓到诸位了。此问题牵扯到CSS世界中各种盒子。
由于牵扯名词甚多,所以我尽量以通俗易懂的方式给大家解释。
创造CSS的造物主原本的想法很简单:我要创造一个世界,就像只有男性和女性一样,这个世界只有块级盒子(block-level box)和内联盒子(inline box)。块级盒子就负责结构,内联盒子就负责内容。事非经过不知难,造物主后来才发现,这世界不止有男性和女性,还有特殊的性别,CSS世界的盒子也是这样。
原本以为块级盒子一套就够用了,也就是所有“块级元素”就只有一个“块级盒子”,但是,半路杀出个list-item
,其默认要显示项目符号的,一个盒子解释不了,怎么办?
就跟我们写JavaScript组件遇到新功能增加API一样,造物主灵机一动:我给list-item
再重新命名一个盒子,就叫“附加盒子”。好了,这下顺了,所有的“块级元素”都有一个“主块级盒子”,list-item
除此之外还有一个“附加盒子”。
现在大家知道上面问题的答案了吧!之所以list-item
元素会出现项目符号是因为生成了一个附加的盒子,学名“标记盒子”(marker box),专门用来放圆点、数字这些项目符号。IE浏览器下伪元素不支持list-item
或许就是无法创建这个“标记盒子”导致的。
但是,我们的故事还没结束。搞定了list-item,
造物主本以为可以安安心心睡个午觉,结果碰到了真正的特殊性别的display:inline-block
元素。
穿着inline
的皮藏着block
的心,现有的几个盒子根本没法解释啊,怎么办?
造物主再次灵机一动,没错,你猜对了,又新增一个盒子,也就是每个元素都有两个盒子,外在盒子[1]和内在盒子。外在盒子负责元素是可以一行显示,还是只能换行显示;内在盒子负责宽高、内容呈现什么的。但是呢,造物主又想了想,叫“内在盒子”虽然容易理解,但是未免有些俗气,难登大雅之堂,于是,又想了一个更专业的名称,叫作“容器盒子”。
于是,按照display
的属性值不同,值为block
的元素的盒子实际由外在的“块级盒子”和内在的“块级容器盒子”组成,值为inline-block
的元素则由外在的“内联盒子”和内在的“块级容器盒子”组成,值为inline
的元素则内外均是“内联盒子”。
现在,大家应该明白为何display
属性值是inline-block
的元素既能和图文一行显示,又能直接设置width
/height
了吧!因为有两个盒子,外面的盒子是inline
级别,里面的盒子是block
级别。
实际上,如果遵循这种理解,display:block
应该脑补成display:block-block
,display:table
应该脑补成display:block-table
,我们平时的写法实际上是一种简写。
好了,说了这么多,出个小题测试一下大家的学习成果。请问:display:inline-table
的盒子是怎样组成的?
display:inline-table
的盒子是怎样组成的这个问题应该无压力:外面是“内联盒子”,里面是“table盒子”。得到的就是一个可以和文字在一行中显示的表格。
可以和文字在一行中显示的表格?没错,为了证明我没忽悠大家,我特意做了个演示页面,演示页面中<div>
元素的相关CSS代码如下:
.inline-table {
display: inline-table;
width: 128px;
margin-left: 10px;
border: 1px solid #cad5eb;
}
手动输入http://demo.cssworld.cn/3/1-2.php或者扫下面的二维码。结果该元素和文字一行显示,且行为表现如同真正的表格元素(子元素宽度等分),如图3-3所示。
图3-3 表格元素和文字在一行显示
上面示意的CSS代码表面上看起来很简单,但是,我也说过,简单的背后往往是不简单。这里CSS中有个width:128px
,从最终的效果来看,宽度设置是起作用了。如果我们使用display:inline-block
也会是同样的宽度表现。下面问题来了:元素都有内外两个盒子,我们平常设置的width
/height
属性是作用在哪个盒子上的?
width/height
作用在哪个盒子上这个问题也是很简单的,因为在解释内外盒子的时候就已经提到过了:是内在盒子,也就是“容器盒子”。
不知大家有没有进一步深入思考过:width
或height
作用的具体细节是什么呢?
width/height
作用的具体细节因为块级元素的流体特性主要体现在水平方向上,所以我们这里先着重讨论width
。
估计很多人的第一次CSS属性书写就献给了width
,就像路边的小草,好常见、好平淡、好简单的样子。如果你有这样的想法,此书你就买对了。
width:auto
我们应该都知道,width
的默认值是auto。auto因为
是默认值,所以出镜率不高,但是,它是个深藏不露的家伙,它至少包含了以下4种不同的宽度表现。
(1)充分利用可用空间。比方说,<div>
、<p>
这些元素的宽度默认是100%于父级容器的。这种充分利用可用空间的行为还有个专有名字,叫作fill-available
,大家了解即可。
(2)收缩与包裹。典型代表就是浮动、绝对定位、inline-block
元素或table
元素,英文称为shrink-to-fit,直译为“收缩到合适”,有那么点儿意思,但不够形象,我一直把这种现象称为“包裹性”。CSS3中的fit-content
指的就是这种宽度表现。
(3)收缩到最小。这个最容易出现在table-layout
为auto
的表格中,想必有经验的人一定见过图3-4所示的这样一柱擎天的盛况吧!
眼见为实,有兴趣的读者可以手动输入http://demo.cssworld.cn/3/2-1.php或者扫下面的二维码。
图3-4 单元格中的一柱擎天效果
当每一列空间都不够的时候,文字能断就断,但中文是随便断的,英文单词不能断。于是,第一列被无情地每个字都断掉,形成一柱擎天。这种行为在规范中被描述为“preferred minimum width”或者“minimum content width”。后来还有了一个更加好听的名字min-content
。
(4)超出容器限制。除非有明确的width
相关设置,否则上面3种情况尺寸都不会主动超过父级容器宽度的,但是存在一些特殊情况。例如,内容很长的连续的英文和数字,或者内联元素被设置了white-space:nowrap
,则表现为“恰似一江春水向东流,流到断崖也不回头”。
例如,看一下下面的CSS代码:
.father {
width: 150px;
background-color: #cd0000;
white-space: nowrap;
}
.child {
display: inline-block;
background-color: #f0f3f9;
}
这段代码的结果如图3-5所示。
图3-5 文字超出容器也不换行示意
子元素既保持了inline-block
元素的收缩特性,又同时让内容宽度最大,直接无视父级容器的宽度限制。这种现象后来有了专门的属性值描述,这个属性值叫作max-content
,这个属于CSS3新世界内容,本书点到为止,不深究。
眼见为实,若有兴趣,可以手动输入http://demo.cssworld.cn/3/2-2.php或扫右侧的二维码亲自感受一下。
上面列举的4点就是width:auto
在不同场景下的宽度表现的简介。
在CSS世界中,盒子分“内在盒子”和“外在盒子”,显示也分“内部显示”和“外部显示”,同样地,尺寸也分“内部尺寸”和“外部尺寸”。其中“内部尺寸”英文写作“Intrinsic Sizing”,表示尺寸由内部元素决定;还有一类叫作“外部尺寸”,英文写作“Extrinsic Sizing”,宽度由外部元素决定。现在,考考大家:上面4种尺寸表现,哪个是“外部尺寸”?哪个是“内部尺寸”?
这里就不卖关子了。就第一个,也就是<div>
默认宽度100%显示,是“外部尺寸”,其余全部是“内部尺寸”。而这唯一的“外部尺寸”,是“流”的精髓所在。
(1)正常流宽度。当我们在一个容器里倒入足量的水时,水一定会均匀铺满整个容器,如图3-6所示。
图3-6 水流自动铺满容器示意
在页面中随便扔一个<div>
元素,其尺寸表现就会和这水流一样铺满容器。这就是block
容器的流特性。这种特性,所有浏览器的表现都是一致的。因此,我就实在想不通,为何那么多网站或同行会有类似下面的CSS写法。例如,一个垂直导航:
a {
display: block;
width: 100%;
}
<a>
元素默认diplay
是inline
,所以,设置display:block
使其块状化绝对没有问题,但后面的width:100%
就没有任何出现的必要了。
我很多年前总结过一套“鑫三无准则”,即“无宽度,无图片,无浮动”。为何要“无宽度”?原因很简单,表现为“外部尺寸”的块级元素一旦设置了宽度,流动性就丢失了。
所谓流动性,并不是看上去的宽度100%
显示这么简单,而是一种margin/border/padding
和content
内容区域自动分配水平空间的机制。
我们来看一个简单的例子,手动输入http://demo.cssworld.cn/3/2-3.php或者扫右侧的二维码。这是一个对比演示(见图3-7),上下两个导航均有margin
和padding
,前者无width
设置,完全借助流特性,后者宽度width:100%
。结果,后者的尺寸超出了外部的容器,完全就不像“水流”那样完全利用容器空间,即所谓的“流动性丢失”。
图3-7 设定宽度与流动性缺失
当然,实际开发的时候,是不会设置宽度 100%的,毕竟有显示问题。此时,可能有人会突然灵光一现,借助流动性来实现……要是这样就好了,然而其基本上采取的策略是,发挥自己天才般的计算能力,通过“容器宽度−水平padding
−水平margin=?
”重新设定具体的宽度。
于是,最终的CSS代码如下:
.nav {
width: 240px;
}
.nav-a {
display: block;
/* 200px = 240px - 10px*2 - 10px*2 */
width: 200px;
margin: 0 10px;
padding: 9px 10px;
...
}
典型的“砌砖头”“搭积木”式思维方式!虽然说最后的效果是一样的,但是,如果模块的宽度变化了,哪怕只变了1像素, width
也需要重新计算一遍。但是,如果借助流动性无宽度布局,那么就算外面容器尺寸变化,我们的导航也可以自适应,这就是充分利用浏览器原生流特性的好处。
因此,记住“无宽度”这条准则,少了代码,少了计算,少了维护,何乐而不为呢?
你应该还记得前面说过display:block
应该脑补成display:block-block
,这是我自己想出来的,便于大家理解,CSS世界其实并没有这样的说法。虽有“臆想”成分在里面,但其实也是有理可循的。本节详细讲了块级元素的流体特性,这种特性就是体现在里面的“容器盒子”上的。所以,在CSS3最新的世界中,CSS规范的撰写者们使用了另外一个名词来表示这个内在盒子,就是“flow”,也就是本书的核心“流”。因此,display:block
更规范的脑补应该是display:block flow
。注意中间是空格。当然,由于规范(草案)2015年10月才发布,因此直到2017年6月为止还没有浏览器支持。好了,这属于比CSS3新世界还要新的世界的知识点,了解即可。
(2)格式化宽度。格式化宽度仅出现在“绝对定位模型”中,也就是出现在position
属性值为absolute
或fixed
的元素中。在默认情况下,绝对定位元素的宽度表现是“包裹性”,宽度由内部尺寸决定,但是,有一种情况其宽度是由外部尺寸决定的,是什么情况呢?
对于非替换元素(见本书第4章),当left/right
或top/bottom
对立方位的属性值同时存在的时候,元素的宽度表现为“格式化宽度”,其宽度大小相对于最近的具有定位特性(position
属性值不是static
)的祖先元素计算。
例如,下面一段CSS代码:
div { position: absolute; left: 20px; right: 20px; }
假设该<div>
元素最近的具有定位特性的祖先元素的宽度是1000像素,则这个<div>
元素的宽度是960(即1000−20−20)像素。
此外,和上面的普通流一样,“格式化宽度”具有完全的流体性,也就是margin
、border
、 padding
和content
内容区域同样会自动分配水平(和垂直)空间。
“格式化宽度”水很深,同时也非常实用,这里先简单提及,更多内容可参见本书第6章与position:absolute
相关的内容。
上一节讲的是“外部尺寸”,本节就讲讲“内部尺寸”。所谓“内部尺寸”,简单来讲就是元素的尺寸由内部的元素决定,而非由外部的容器决定。如何快速判断一个元素使用的是否为“内部尺寸”呢?很简单,假如这个元素里面没有内容,宽度就是0
,那就是应用的“内部尺寸”。
据我所知,在CSS世界中,“内部尺寸”有下面3种表现形式。
(1)包裹性。
“包裹性”是我自己对“shrink-to-fit”理解后的一种称谓,我个人觉得非常形象好记,一直用了很多年。“包裹性”也是CSS世界中很重要的流布局表现形式。
中文就是博大精深,顾名思义,“包裹性”,除了“包裹”,还有“自适应性”。“自适应性”是区分后面两种尺寸表现很重要的一点。那么这个“自适应性”指的是什么呢?
所谓“自适应性”,指的是元素尺寸由内部元素决定,但永远小于“包含块”容器的尺寸(除非容器尺寸小于元素的“首选最小宽度”)。换句话说就是,“包裹性”元素冥冥中有个max-width:100%
罩着的感觉(注意,此说法只是便于大家理解,实际上是有明显区别的)。
因此,对于一个元素,如果其display
属性值是inline-block
,那么即使其里面内容再多,只要是正常文本,宽度也不会超过容器。于是,图文混排的时候,我们只要关心内容,除非“首选最小宽度”比容器宽度还要大,否则我们完全不需要担心某个元素内容太多而破坏了布局。
凡事发生必有缘由。CSS世界的造物主为何要设计“包裹性”这个东西呢?是为谁设计的呢?
反问是探究知识的很好的习惯和方式。要回答上面的问题,我们只要请一个嘉宾出来,答案就基本明确了。下面我们就请出重量级嘉宾——著名的“按钮”元素。没错,就是默认长得比较丑,样式定义兼容性又不好的按钮元素。
按钮通常以如下两种形式出现在页面代码中:
<button>按钮</button>
<input type="button" value="按钮">
按钮就是CSS世界中极具代表性的inline-block
元素,可谓展示“包裹性”最好的例子,具体表现为:按钮文字越多宽度越宽(内部尺寸特性),但如果文字足够多,则会在容器的宽度处自动换行(自适应特性)。
按钮会自动换行?没错,你之所以没印象,可能是因为:
<button>
标签按钮才会自动换行,<input>
标签按钮,默认white-space:pre,
是不会换行的,需要将pre
值重置为默认的normal
。眼见为实,手动输入http://demo.cssworld.cn/3/2-4.php或者扫下面的二维码。上面的示例页面的效果如图3-8所示。
图3-8 IE11浏览器IE8模式下按钮换行效果
按钮最大宽度就是容器的240像素,1像素不多1像素不少,顿时有了一种内外兼修的感觉。
“包裹性”对实际开发有什么作用呢?
请看这个需求:页面某个模块的文字内容是动态的,可能是几个字,也可能是一句话。然后,希望文字少的时候居中显示,文字超过一行的时候居左显示。该如何实现?
核心CSS代码如下:
.box {
text-align: center;
}
.content {
display: inline-block;
text-align: left;
}
这样,文字少的时候,就会如图3-9所示。文字多的时候,如图3-10所示。
图3-9 文字少时的显示效果
图3-10 文字多时的显示效果
眼见为实,手动输入http://demo.cssworld.cn/3/2-5.php或者扫右侧的二维码,进入页面点击“更多文字”按钮体验。
除了inline-block
元素,浮动元素以及绝对定位元素都具有包裹性,均有类似的智能宽度行为。
(2)首选最小宽度。
所谓“首选最小宽度”,指的是元素最适合的最小宽度。我们接着上面的例子,在上面例子中,外部容器的宽度是240像素
,假设宽度是0,请问里面的inline-block
元素的宽度是多少?
是0吗?不是。在CSS世界中,图片和文字的权重要远大于布局,因此,CSS的设计者显然是不会让图文在width:auto
时宽度变成0的,此时所表现的宽度就是“首选最小宽度”。具体表现规则如下。
14
。图3-11 中文汉字与最小宽度效果
图3-12 连续字符换行点示意
display:inline-block
”这几个字符以连接符“-
”作为分隔符,形成了“display:inline
”和“block
”两个连续单元,由于连接符“-
”分隔位置在字符后面,因此,最后的宽度就是“display:inline-
”的宽度,如图3-12所示。如果想让英文字符和中文一样,每一个字符都用最小宽度单元,可以试试使用CSS中的word-break:break-all
。
“首选最小宽度”对我们实际开发有什么作用呢?
可以让我们遇到类似现象的时候知道原因是什么,方便迅速对症下药,其他就没什么用了。
有点失望?那好,我就举个利用“首选最小宽度”构建图形的例子吧。请问,如何使用一层HTML标签分别实现图3-13所示的“凹”和“凸”效果(注意要兼容IE8)?
由于要兼容IE8,CSS新世界中图形构建利器的盒阴影和背景渐变全都没有用武之地,怎么办呢?我们可以利用“首选最小宽度”的行为特点把需要的图形勾勒出来。核心CSS代码如下(以“凹”效果示意):
.ao {
display: inline-block;
width: 0;
}
.ao:before {
content: "love你love";
outline: 2px solid #cd0000;
color: #fff;
}
还没看明白?那我把文字颜色放出来(见图3-14),大家应该就知道实现原理了。
图3-13 需要实现的效果图
图3-14 图形由文字区域勾勒而成
利用连续英文单词不换行的特性,我们就可以控制什么地方“凹”,什么地方“凸”啦!
想看在线演示,请手动输入http://demo.cssworld.cn/3/2-6.php或者扫右侧的二维码。
(3)最大宽度。
最大宽度就是元素可以有的最大宽度。我自己是这么理解的,“最大宽度”实际等同于“包裹性”元素设置white-space:nowrap
声明后的宽度。如果内部没有块级元素或者块级元素没有设定宽度值,则“最大宽度”实际上是最大的连续内联盒子的宽度。
什么是连续内联盒子?“内联盒子”的内容会在3.4节深入讲解,这里你就简单地将其理解为display
为inline
、inline-block、
inline-table等元素。“连续内联盒子”指的全部都是内联级别的一个或一堆元素,中间没有任何的换行标签<br>
或其他块级元素。
一图胜千言,图3-15所示是一段很平常的HTML片段的“连续内联盒子”信息标注图。其中,有3处连续内联盒子,分别是:
<br>
前面的4个内联盒子组合;<br>
后面“我是下一行”字样所在的匿名内联盒子;<p>
标签内的内联盒子,也就是一段文本。与标注图“内联”文字对应的标注相一致,此时“最大宽度”就是这3个连续内联盒子的宽度的最大值。
如果把标注图的代码在浏览器中运行一下,则在最大宽度模式下,效果如图3-16所示。可以发现最后的宽度就是第一个“连续内联盒子”的宽度。
图3-15 “连续内联盒子”信息标注图
图3-16 最大宽度模式下的效果
“最大宽度”对我们实际开发有什么作用呢?
根据我这么多年的经验,大部分需要使用“最大宽度”的场景可以通过设置一个“很大宽度”来实现。注意,这里的“很大宽度”和“最大宽度”是有本质区别的。比方说,有5张图片,每张图片宽度200像素,假设图片元素紧密排列,则“最大宽度”就是1000像素。但是,实际开发的时候,我们懒得计算,可能直接设置容器width:2000px
,这里2000px
就是“很大宽度”,宽度足够大,作用是保证图片不会因为容器宽度不足而不在一行内显示。两者都能实现几张图片左右滑来滑去的效果。
那有没有场景只能是“最大宽度”而不是“很大宽度”呢?有!不知大家有没有听过iScroll,它可以实现非常平滑的滚动效果,在前端界颇有名气。
一般来讲,实现自定义滚动有两种原理:一种借助原生的滚动,scrollLeft
/scrollTop
值变化,它的优点是简单,不足是效果呆板;另一种是根据内部元素的尺寸和容器的关系,通过修改内部元素的位置实现滚动效果,优点是效果可以很绽放。iScroll就是使用的后者,因此,如果我们希望使用iScroll模拟水平滚动,只能使用“最大宽度”,这样,滚动到底的时候才是真的到底。
眼见为实,手动输入http://demo.cssworld.cn/3/2-7.php或者扫下面的二维码。保证在一行显示,同时不浪费一点空白,如图3-17所示。
图3-17 最大宽度与精确滚动示意
width
值作用的细节细心的读者有没有发现,前面那么多页,其实就讲了一个点——width:auto
,说“深藏不露”不是忽悠你们吧?下面,转换思维,我们来看一下width
属性使用具体数值会有怎样的表现。
比方说,对于一个<div>
元素,我们设定其宽度为100
px,如下:
div { width: 100px; }
请问,100px
的宽度是如何作用到这个<div>
元素上的?
要回答这个问题,就需要了解CSS世界中与尺寸相关的一个重要概念——“盒尺寸”(box dimension)。
前文多次强调了,width
是作用在“内在盒子”上的,乍一看是一个普通的盒子,实际上,这个“内在盒子”是由很多部分构成的。这个盒子的构成和地球结构的构成惊人地类似,可参考图3-18所示的这张我制作的示意图(图中虚线、实线是为了区分不同结构,本身并没有意义)。
图3-18 盒尺寸盒子结构和地球结构对比图
仔细对比会发现,两者不仅结构类似,对应结构的视觉表现和含义也类似。比方说,CSS的margin
区域和地球的大气层区域都是透明的,又比方说content
之于CSS,正如地核之于地球,都是属于核心,因为CSS世界的诞生就是为图文信息展示服务的,因此,内容一定是最重要的核心。然后在这个核心的外面包裹了padding
、border
和margin
。
CSS世界什么最多?盒子!比方前面介绍过的块状盒子、内联盒子以及外在盒子和内在盒子,以及这里即将出现的4个盒子,以及3.4节介绍的一堆盒子。如果这些盒子纯粹是概念,没什么实际作用,我就不讲或直接一步带过,但是,这里的这4个盒子不仅仅是概念,还真的有付诸实践的关键字,所以还是要好好说一说。
我们的这个“内在盒子”又被分成了4个盒子,分别是content box、padding box、border box和margin box。
原本这几个盒子只存在于规范中,我们写代码的知不知道无所谓的,都能写出很棒的CSS代码,这就和玩游戏没必要知道游戏怎么制作的道理一样。但是后来,这几个盒子在CSS语言层有名字了,一下子变成有身份的人了,事情就变了,如果对这些盒子不了解,有些CSS属性就不好理解,也不容易记住。
那么都给它们起了些什么名字呢?content box 写作 content-box
,padding box 写作padding-box
,border box写作border-box
,margin box写作……
突然发现,margin box居然没有名字!为何唯独margin box
并没有对应的CSS关键字名称呢?因为目前没有任何场景需要用到margin box。
“margin
的背景永远是透明的”,因此不可能作为backgound-clip
或background-origin
属性值出现。margin
一旦设定具体宽度和高度值,其本身的尺寸是不会因margin
值变化而变化的,因此作为box-sizing
的属性值存在也就没有了意义(这会在后面深入阐述)。既然无用武之地,自然就被抛弃了。
现在回到一开始的问题:width:100px
是如何作用到<div>
元素上的?
在CSS2.1的规范中,有一段非常露骨的描述:content box环绕着width
和height
给定的矩形。
说得这么直白,我已经没什么其他可说的了。明摆着,width:100px
作用在了content box上,由于<div>
元素默认的padding
、border
和margin
都是0
,因此,该<div>
所呈现的宽度就是100像素。
那么按照这种说法,如果我们在水平方向给定padding
和border
大小,则元素的尺寸就不是100像素
了?我们看一个简单的例子:
div { width: 100px; padding: 20px; border: 20px solid; }
手动输入http://demo.cssworld.cn/3/2-8.php或者扫下面的二维码。结果变成了180像素宽,如图3-19所示。
图3-19 默认盒尺寸下的offsetWidth
为什么会变宽呢?其实很好理解,因为宽度是作用在content box上的,而外面围绕的padding box和border box又不是摆设,自然实际尺寸要比设定的大。这就好比某超模的腰围61 cm,裹了件东北大棉袄,自然此时的腰围要远大于61 cm。如图3-20所示,中间有点蓝的就是content
区域,宽度100像素,再加上padding
和border
左右各20像素,最终宽度就是180像素啦!
图3-20 180像素宽度的盒模型
如果单看定义和表现,似乎一切都合情合理,但实际上,多年的实践告诉我,有时候,这种宽度设定和表现并不合理。我总结为以下两点。
对于块状元素,如果width:auto
,则元素会如水流般充满整个容器,而一旦设定了width
具体数值,则元素的流动性就会被阻断,因为元素给定宽度就像河流中间竖了两个大闸一样,就没有了流动性。尤其宽度作用在content box上,更是内外流动性全无,如图3-21所示。
这世界上任何事物,一旦限死了,就丧失了灵活性,其发展潜力及作用范围就会大大受限。
长江为何生机勃勃数千年,就是因为滔滔江水,奔流不息。CSS的流动性也是其生机蓬勃之本,如果直接宽度设死,流动性丢失,在我看来,就是江河变死水,手机变板砖。这就是我提出“无宽度准则”的原因——布局会更灵活,容错性会更强。
图3-21 流动性缺失示意图
鉴于“流动性丢失”在3.2.1节其实已经提过,还有实例,这里就不再过多展开。
包含padding
或border
会让元素宽度变大的这种CSS表现往往会让CSS使用者困惑:我设置宽度为100像素
,其实是希望整个最终的宽度是100像素
,这样才符合现实理解嘛。比方说,我买个140m2的房子,肯定是连墙体面积在内的啊,实际使用面积比140m2小才是现实,你说现在最终面积比140m2还大,这种事情显然是不科学、不合理的。
或许是因为CSS 2.1是面向内容(图文信息)设计的,所以,width
设计成了直接作用在content box上。
这对一些CSS新手的布局造成了一定的障碍,因为这些CSS从业者眼中的CSS结构是砖块,而不是水流。因此,布局讲求尺寸精确计算。这就导致在一些CSS属性值发生变化的时候(如padding
值变大,元素尺寸也变大),空间不足,出现页面布局错位的问题。
那有没有什么办法能避免这种错位问题的出现呢?方法之一就是采用书写方式约束,如使用“宽度分离原则”。
所谓“宽度分离原则”,就是CSS中的width
属性不与影响宽度的padding/border
(有时候包括margin
)属性共存,也就是不能出现以下的组合:
.box { width: 100px; border: 1px solid; }
或者
.box { width: 100px; padding: 20px; }
有人可能会问:不这么写,该怎么写呢?很简单,分离,width
独立占用一层标签,而padding
、border、
margin利用流动性在内部自适应呈现。
.father {
width: 180px;
}
.son {
margin: 0 20px;
padding: 20px;
border: 1px solid;
}
现在关键问题来了:为何要宽度分离?
在前端领域,一提到分离,作用一定是便于维护。比方说,样式和行为分离、前后端分离或者这里的“宽度分离”。
道理其实很简单,当一件事情的发展可以被多个因素所左右的时候,这个事情最终的结果就会变数很大而不可预期。宽度在这里也是类似,由于盒尺寸中的4个盒子都能影响宽度,自然页面元素的最终宽度就很容易发生变化而导致意想不到的布局发生。例如,下面这个简单的CSS:
.box {
width: 100px;
border: 1px solid;
}
此时宽度是102像素。然后,设计师希望元素边框内有20像素的留白,这时候,我们会增加padding
设置:
.box {
width: 100px;
padding: 20px;
border: 1px solid;
}
结果此时宽度变成了142像素,大了40像素,跟原来宽度差异明显,显然布局很容易出问题。为了不影响之前的布局,我们还需要通过计算减去40像素的padding
大小才行:
.box {
width: 60px; // 通过计算,减去40像素
padding: 20px;
border: 1px solid;
}
但是,如果我们使用了宽度分离,事情就会轻松很多:
.father {
width: 102px;
}
.son {
border: 1px solid;
}
嵌套一层标签,父元素定宽,子元素因为width
使用的是默认值auto
,所以会如水流般自动填满父级容器。因此,子元素的content box宽度就是100像素,和上面直接设置width
为100像素表现一样。
然后,同样的故事,设计师希望元素边框内有20像素的留白,这时候,我们会增加padding
设置:
.father {
width: 102px;
}
.son {
border: 1px solid;
padding: 20px;
}
然后……就没有然后了,宽度还是102像素,子元素的content box自动变成了60像素,和上面反例的表现一样。没错,自动变化了,就是这么智能!
虽然表现一样,但是写代码的人的体验大不一样:width
、padding
、border
混用的时候,任何修改我们都需要实时去计算现在width
应该设置多大才能和之前的占用的宽度一样,而后面width
分离的实现,我们没有任何计算,要padding
留白,加一下就好,要修改边框宽度,改一下就好,浏览器会自动计算,完全不用担心尺寸的变化。
也就是说,使用“宽度分离”后,咱们不需要烧脑子去计算了,而且页面结构反而更稳固。这么好的事情,完全没有理由拒绝啊!
有人可能会提出挑战:你这“宽度分离”多使用了一层标签啊,这HTML成本也是成本啊!
没错,问题本身是对的。HTML的成本也是成本,过深的嵌套是会增加页面渲染和维护成本的。但是,我这里要抛出一句话,实际上,如果不考虑替换元素,这世界上绝大多数的网页,只需要一个width
设定就可以了,没错,只需要一个width
,就是最外层限制网页主体内容宽度的那个width
,而里面所有元素都没有理由再出现width
设置。所以,“宽度分离”虽然多了一层标签,但最终也就多了一层标签而已,这个成本跟收益比起来简直就是毛毛雨。
但是,话又说回来,“无宽度”网页布局是需要很深的CSS积累才能驾驭自如的,很多同行没好好品鉴本书的内容,要是让他们完全遵循“宽度分离”来实现,怕是HTML会变得很啰唆。
那有没有什么既无须计算,又无须额外嵌套标签的实现呢?有,那就是可以改变width
作用细节的box-sizing
属性。
width/height
作用细节的box-sizing
box-sizing
虽然是CSS3属性,但是,让人受宠若惊的是IE8浏览器也是支持它的,不过需要加-ms-
私有前缀,但IE9浏览器开始就不需要私有前缀了。
本书内容是针对IE8及以上版本浏览器的,因此,box-sizing
也加入了CSS世界魔法师的队伍。
box-sizing
的作用box-sizing
顾名思义就是“盒尺寸”。稍等,前文好像也出现了一个“盒尺寸”(box dimension),咦?两者是一样的吗?我个人觉得是一样的,只是dimension这个词太过于官方了,用在规范中很合适,但是,要是作为CSS属性,拼写就不那么容易了,所以就使用了更口语化的box-sizing
。
虽然box-sizing被
直译为“盒尺寸”,实际上,其更准确的叫法应该是“盒尺寸的作用细节”,或者说得更通俗一点,叫“width
作用的细节”,也就是说,box-sizing
属性的作用是改变width
的作用细节。
那它改变了什么细节呢?一句话,改变了width
作用的盒子。还记不记得“内在盒子”的4个盒子?它们分别是content box、padding box、border box和margin box。默认情况下,width
是作用在content box上的,box-sizing
的作用就是可以把width
作用的盒子变成其他几个,因此,理论上,box-sizing
可以有下面这些写法:
.box1 { box-sizing: content-box; }
.box2 { box-sizing: padding-box; }
.box3 { box-sizing: border-box; }
.box4 { box-sizing: margin-box; }
理论美好,现实残酷。实际上,支持情况如下:
.box1 { box-sizing: content-box; } /* 默认值 */
.box2 { box-sizing: padding-box; } /* Firefox曾经支持 */
.box3 { box-sizing: border-box; } /* 全线支持 */
.box4 { box-sizing: margin-box; } /* 从未支持过 */
所以,我只能拿border-box
属性值做对比,如图3-22所示。
图3-22 box-sizing
不同值的作用原理示意
可以看到,所谓box-sizing:border-box
就是让100像素的宽度直接作用在border box上,从默认的content box变成border box。此时,content box从宽度值中释放,形成了局部的流动性,和padding
一起自动分配width
值。于是,
.box {
width: 100px;
box-sizing: border-box;
}
宽度是100像素,
.box {
width: 100px;
box-sizing: border-box;
border: 1px solid;
}
宽度也是100像素,
.box {
width: 100px;
box-sizing: border-box;
padding: 20px;
border: 1px solid;
}
宽度还是100像素。
我们似乎找到了解决问题的钥匙,自从用了box-sizing
,标签层级少了,错位问题不见了,一口气写5张页面,不费劲。
实际上,当遭遇类似图3-23所示的布局时,你会发现,box-sizing
也是捉襟见肘,因为边框外的间距只能是margin
,但box-sizing
并不支持margin-box
,若想用一层标签实现,还是需要计算!
图3-23 box-sizing
也无能为力的布局结构
box-sizing
不支持margin-box
遇到这样的场景的时候,想必有人会感叹:“要是box-sizing
支持margin-box
就好了。”是啊,要是这样就好了,但是现实就是不支持,为什么呢?
网上有这样的说法,说margin
在垂直方向有合并重叠特性,如果支持了margin-box
,合并规则就要发生变更,会比较复杂。我对此观点不敢苟同,其实当下很多属性可以灭掉margin
合并,多一个box-sizing
又何妨,且浏览器厂商实现起来并不难,跟之前的规范也不冲突。
我个人认为,不支持margin-box
最大的原因是它本身就没有价值!我们不妨好好想想,一个元素,如果我们使用width
或height
设定好了尺寸,请问,我们此时设置margin
值,其offset
尺寸会有变化吗?不会啊,100像素
宽的元素,再怎么设置margin
,它还是100像素
宽。但是,border
和padding
就不一样了,100像素
宽的元素,设置个20像素
大小的padding
值,offsetWidth
就是140像素了,尺寸会变化。你说,一个本身并不会改变元素尺寸的盒子,它有让box-sizing
支持的道理吗?box-sizing
就是改变尺寸作用规则的!margin
只有在width
为auto
的时候可以改变元素的尺寸,但是,此时元素已经处于流动性状态,根本就不需要box-sizing
。所以,说来说去就是margin-box
本身就没有价值。
另外一个原因牵扯到语义。如果box-sizing
开了先河支持了margin-box
,margin box就变成了一个“显式的盒子”,你让background-origin
等属性何去何从,支持还是不支持呢?“margin
的背景永远是透明的”这几个大字可是在规范写得清清楚楚,难道让背景色在所谓的margin box中也显示?显然是不可能的,我们可以打自己的脸,但是要想让规范打自己的脸,可能吗?
最后还有一个可能的原因就是使用场景需要。对于box-sizing
的margin-box
效果,如果是IE10及以上版本浏览器,可以试试flex布局,如果要兼容IE8及以上版本可以使用“宽度分离”,或者特定场景下使用“格式化宽度”来实现,也就是并不是强需求。比方box-sizing:padding-box
,就是因为使用场景有限,仅Firefox浏览器支持,并且是曾经支持,从版本50开始也不支持了。其实,我个人觉得没必要舍弃,浏览器都应该支持,就像background
属性那样。成为套餐不挺好的?
人们写代码时的思维逻辑,总是不由自主地与现实世界相映射,这是人之常情。因此,大家对box-sizing:border-box
的好感度普遍要远大于默认的box-sizing:content-box
,甚至我见到有同行称默认的content-box
作用机制是反人类的,因此,很多同行开始使用*{box-sizing:border-box}
进行全局重置,对于这种做法,我是有自己的看法的。
*{box-sizing:border-box}
从纯个人角度讲,我是不喜欢这种做法的,我一向推崇的是充分利用元素本身的特性来实现我们想要的效果,足够简单纯粹。因此,全局重置的做法是有悖我的理念的。
即使抛开个人喜好,这种做法也是有些问题的。
(1)这种做法易产生没必要的消耗。通配符应该是一个慎用的选择器,因为它会选择所有的标签元素。对于普通内联元素(非图片等替换元素),box-sizing
无论是什么值,对其渲染表现都没有影响,因此,对这些元素而言就是没有必要的消耗;同时有些元素,如search
类型的搜索框,其默认的box-sizing
就是border-box
(如果浏览器支持),因此,*
对search
类型的<input>
而言也是没有必要的消耗。
(2)这种做法并不能解决所有问题。box-sizing
不支持margin-box
,只有当元素没有水平margin
时候,box-sizing
才能真正无计算,而“宽度分离”等策略则可以彻底解决所有的宽度计算的问题。因此,我们有必要好好地想一想,box-sizing
属性发明的初衷到底是什么?是为了让那些对block
水平元素滥用width
属性的人少出bug吗?我不这么认为!
box-sizing
发明的初衷根据我这么多年的开发经验,在CSS世界中,唯一离不开box-sizing:border-box
的就是原生普通文本框<input>
和文本域<textarea>
的100%自适应父容器宽度。
拿文本域<textarea>
举例,<textarea>
为替换元素,替换元素的特性之一就是尺寸由内部元素决定,且无论其display
属性值是inline
还是block
。这个特性很有意思,对于非替换元素,如果其display
属性值为block
,则会具有流动性,宽度由外部尺寸决定,但是替换元素的宽度却不受display
水平影响,因此,我们通过CSS修改<textarea>
的display
水平是无法让尺寸100%自适应父容器的:
textarea {
display: block; /* 还是原来的尺寸 */
}
所以,我们只能通过width
设定让<textarea>
尺寸100%自适应父容器。那么,问题就来了,<textarea>
是有border
的,而且需要有一定的padding
大小,否则输入的时候光标会顶着边框,体验很不好。于是,width
/border
和padding
注定要共存,同时还要整体宽度100%自适应容器。如果不借助其他标签,肯定是无解的。
在浏览器还没支持box-sizing
的年代,我们的做法有点儿类似于“宽度分离”,外面嵌套<div>
标签,模拟border
和padding
,<textarea>
作为子元素,border
和padding
全部为0
,然后宽度100%自适应父级<div>
。
手动输入http://demo.cssworld.cn/3/2-9.php或者扫右侧的二维码。
然而,这种模拟也有局限性,比如无法使用:focus
高亮父级的边框,因为CSS世界中并无父选择器,只能使用更复杂的嵌套加其他CSS技巧来模拟。
因此,说来说去,也就box-sizing:border-box
才是根本解决之道!
textarea {
width: 100%;
-ms-box-sizing: border-box; /* for IE8 */
box-sizing: border-box;
}
在我看来,box-sizing
被发明出来最大的初衷应该是解决替换元素宽度自适应问题。如果真的如我所言,那*{box-sizing:border-box}
是不是没用在点儿上呢?是不是应该像下面这样CSS重置才更合理呢?
input, textarea, img, video, object {
box-sizing: border-box;
}
height:auto
width
和height
是CSS世界中同一类型魔法师,都是直接限定元素尺寸的。所以,它们共用一套“盒尺寸”模型,box-sizing
的解释也是类似的。但是,它们在不少地方还是有明显区别的,其中之一就是height:auto
要比width:auto
简单而单纯得多。
原因在于,CSS的默认流是水平方向的,宽度是稀缺的,高度是无限的。因此,宽度的分配规则就比较复杂,高度就显得比较随意。比方说,小明没钱交房租而去搬砖,一块砖头5 cm高,请问,10块砖摞在一起多高?很简单,50 cm。height:auto
的表现也基本上就是这个套路:有几个元素盒子,每个多高,然后一加,就是最终的高度值了。
当然,涉及具体场景,就会有其他的小故事,比方说元素float
容器高度没了,或者是margin
直接穿过去,高度比预期的矮了。这个其实不是height
的问题。关于这一点,我会在对应的属性章节帮大家一探究竟。
此外,height:auto
也有外部尺寸特性。但据我所知,其仅存在于绝对定位模型中,也就是“格式化高度”。“格式化高度”与“格式化宽度”类似,就不展开讲解了。
height:100%
height
和width
还有一个比较明显的区别就是对百分比单位的支持。对于width
属性,就算父元素width
为auto
,其百分比值也是支持的;但是,对于height
属性,如果父元素height
为auto
,只要子元素在文档流中,其百分比值完全就被忽略了。例如,某小白想要在页面插入一个<div>
,然后满屏显示背景图,就写了如下CSS:
div {
width: 100%; /* 这是多余的 */
height: 100%; /* 这是无效的 */
background: url(bg.jpg);
}
然后他发现这个<div>
高度永远是0
,哪怕其父级<body>
塞满了内容也是如此。事实上,他需要如下设置才行:
html, body {
height: 100%;
}
并且仅仅设置<body>
也是不行的,因为此时的<body>
也没有具体的高度值:
body {
/* 子元素height:100%依旧无效 */
}
只要经过一定的实践,我们都会发现对于普通文档流中的元素,百分比高度值要想起作用,其父级必须有一个可以生效的高度值!但是,怕是很少有人思考过这样一个问题:为何父级没有具体高度值的时候,height:100%
会无效?
height:100%
无效有一种看似合理的说法:如果父元素height:auto
子元素还支持height:100%
,则父元素的高度很容易陷入死循环,高度无限。例如,一个<div>
元素里面有一张vertical-align
为bottom
同时高度为192像素
的图片,此时,该<div>
高度就是192像素
,假设此时插入一个子元素,高度设为100%
,如果此时height:100%
可以计算,则子元素应该也是192像素
。但是,父元素height
值是auto
,岂不是现在高度要从原来的192像素
变成384像素,然后height:100%
的子元素高度又要变成384像素,父元素高度又双倍……死循环了!
实际上,这种解释是错误的,大家千万别被误导。证据就是宽度也存在类似场景,但并没有死循环。例如,在下面这个例子中,父元素采用“最大宽度”,然后有一个inline-block
子元素宽度100%
:
<div class="box">
<img src="1.jpg">
<span class="text">红色背景是父级</span>
</div>
.box {
display: inline-block;
white-space: nowrap;
background-color: #cd0000;
}
.text {
display: inline-block;
width: 100%;
background-color: #34538b;
color: #fff;
}
如果按照上面“高度死循环”的解释,这里也应该“宽度死循环”,因为后面的inline-block
元素按照我们的理解应该会让父元素的宽度进一步变大。但实际上并没有,宽度范围可能超出你的预期(见图3-24)。父元素的宽度就是图片加文字内容的宽度之和。
手动输入http://demo.cssworld.cn/3/2-10.php或者扫右侧的二维码。
图3-24 宽度为图片加文字内容的宽度之和
为什么会这样表现呢?
要明白其中的原因要先了解浏览器渲染的基本原理。首先,先下载文档内容,加载头部的样式资源(如果有的话),然后按照从上而下、自外而内的顺序渲染DOM内容。套用本例就是,先渲染父元素,后渲染子元素,是有先后顺序的。因此,当渲染到父元素的时候,子元素的width:100%
并没有渲染,宽度就是图片加文字内容的宽度;等渲染到文字这个子元素的时候,父元素宽度已经固定,此时的width:100%
就是已经固定好的父元素的宽度。宽度不够怎么办?溢出就好了,overflow
属性就是为此而生的。
同样的道理,如果height
支持任意元素100%
,也是不会死循环的。和宽度类似,静态渲染,一次到位。
那问题又来了:为何宽度支持,高度就不支持呢?规范中其实给出了答案。如果包含块的高度没有显式指定(即高度由内容决定),并且该元素不是绝对定位,则计算值为auto
。一句话总结就是:因为解释成了auto
。要知道,auto
和百分比计算,肯定是算不了的:
'auto' * 100/100 = NaN
但是,宽度的解释却是:如果包含块的宽度取决于该元素的宽度,那么产生的布局在CSS 2.1中是未定义的。
还记不记得本书第2章最后的“未定义行为”吗?这里的宽度布局其实也是“未定义行为”,也就是规范没有明确表示该怎样,浏览器可以自己根据理解去发挥。好在根据我的测试,布局效果在各个浏览器下都是一致的。这里和高度的规范定义就区别明显了,高度明确了就是auto
,高度百分比计算自然无果,width
却没有这样的说法,因此,就按照包含块真实的计算值作为百分比计算的基数。
height:100%
效果如何让元素支持height:100%
效果?这个问题的答案其实上面的规范已经给出了,即有两种方法。
(1)设定显式的高度值。这个没什么好说的,例如,设置height:600px
,或者可以生效的百分比值高度。例如,我们比较常见的:
html, body {
height: 100%;
}
(2)使用绝对定位。例如:
div {
height: 100%;
position: absolute;
}
此时的height:100%
就会有计算值,即使祖先元素的height
计算为auto也是如此
。需要注意的是,绝对定位元素的百分比计算和非绝对定位元素的百分比计算是有区别的,区别在于绝对定位的宽高百分比计算是相对于padding box的,也就是说会把padding
大小值计算在内,但是,非绝对定位元素则是相对于content box计算的。
我们可以看一个例子,对比一下:
.box {
height: 160px;
padding: 30px;
box-sizing: border-box;
background-color: #beceeb;
}
.child {
height: 100%;
background-color: #cd0000;
}
.box {
height: 160px;
padding: 30px;
box-sizing: border-box;
background-color: #beceeb;
position: relative;
}
.child {
height: 100%; width: 100%;
background-color: #cd0000;
position: absolute;
}
可以看到,非定位元素的宽高百分比计算不会将padding
计算在内,如图3-25所示。
如果对图3-25所示的结果表示质疑,也可以访问http://demo.cssworld.cn/ 3/2-11.php或者扫下面的二维码。
图3-25 非绝对定位和绝对定位百分比高度对比
我对这两种height:100%
生效方法的评价是:显式高度方法中规中矩,意料之中;绝对定位方法剑走偏锋,支持隐式高度计算,给人意外之喜,但本身脱离文档流,使其仅在某些场景有四两拨千斤的效果,比方说“图片左右半区点击分别上一张图下一张图效果”的布局(见图3-26)。
图3-26所示的效果有专门的演示页面,可以手动输入http://demo.cssworld.cn/3/2-12.php或者扫下面的二维码。
图3-26 图片上下切图布局示意
原理很简单,就是在图片外面包一层具有“包裹性”同时具有定位特性的标签。例如:
.box {
display: inline-block;
position: relative;
}
此时,只要在图片上覆盖两个绝对定位,同时设height:100%
,则无论图片多高,我们的左右半区都能自动和图片高度一模一样,无须任何使用JavaScript的计算。
min-width/max-width
和min-height/max-height二三事说完了width和height
,下面轮到min-width/max-width和min-height/max-height了
,它们有很多共性。比方说,它们都是与尺寸相关的,盒尺寸机制和一些值的渲染规则也是一样的,因此,这部分内容这里就不赘述了,这里只简单讲几点min/max-width/height
和width/height
不一样的地方。
min-width/max-width
在CSS世界中,min-width/max-width
出现的场景一定是自适应布局或者流体布局中,因为,如果是那种width/height
定死的砖头式布局,min-width/max-width
就没有任何出现的价值,因为它们是具有边界行为的属性,所以没有变化自然无法触发,也就没有使用价值。
因此,新人并不会经常使用min-width/max-width
,只有随着CSS技能深入,能够兼顾还原性的同时还兼顾扩展性和适配性之后,min-width/max-width
才会用得越来越多,也才能真正感受到IE7浏览器就支持min/max-width
是多么好的一件事情。
现代桌面显示器分辨率越来越大,960像素网页设计已经显得有些小家碧玉了,但随便搞一个大尺寸(如1400像素)的网页宽度也不合时宜,因为还有很多笔记本电脑用户,此时,一种特定区间内的自适应布局方案就诞生了,网页宽度在1200~1400像素自适应,既满足大屏的大气,又满足笔记本电脑的良好显示,此时,min-width/max-width
就可以大显神威了:
.container {
min-width: 1200px;
max-width: 1400px;
}
对,无须width
设置,直接使用min-width/max-width
。
在公众号的热门文章中,经常会有图片,这些图片都是用户上传产生的,因此尺寸会有大有小,为了避免图片在移动端展示过大影响体验,常常会有下面的max-width
限制:
img {
max-width: 100%;
height: auto!important;
}
height:auto
是必需的,否则,如果原始图片有设定height
,max-width
生效的时候,图片就会被水平压缩。强制height
为auto
可以确保宽度不超出的同时使图片保持原来的比例。但这样也会有体验上的问题,那就是在加载时图片占据高度会从0变成计算高度,图文会有明显的瀑布式下落。
min-width/max-width和min-heigh/max-height
从长相上看明显和width/ height
是一个家族的,总以为属性值、模型都是一样的,但是有一个地方就搞了特殊,那就是初始值。width/height
的默认值是auto
,而min-width/max-width和min-heigh/ max-height
的初始值则要复杂些。这里要分为两部分,分别是max-*
系列和min-*
系列。max-width和max-height
的初始值是none
,min-width和min-height
的初始值是……
虽然MDN和W3C维基的文档上都显示min-width/min-height
的初始值是0
,但是,根据我的分析和测试,所有浏览器中的min-width/min-height
的初始值都是auto
。证据如下:
(1)min-width/height
值为auto
合法。例如,设置:
<body style="min-width:auto;">
结果所有浏览器下:
document.body.style.minWidth; // 结果是auto
说明min-*
支持auto
值,同样,如果是max-width:auto
,结果则是''
,进一步证明min-width/height
值为auto
合法。
(2)数值变化无动画。假设元素的min-width/min-height
的初始值是0
,那么,当我们设置transition
过渡同时改变了min-width/min-height
值,岂不是应该有动画效果?结果:
.box {
transition: min-height .3s;
}
.box:hover {
min-height: 300px;
}
鼠标经过.box
元素,元素突然变高,并无动画效果,但是,如果是下面这样的设置:
.box {
min-height: 0;
transition: min-height .3s;
}
.box:hover {
min-height: 300px;
}
鼠标经过.box
元素,transition
动画效果就出现了。这就证明了,min-height
的初始值不是0
,既然不是0,
那就应该是所有浏览器都支持的auto
。
于是,得到如下结论:min-width/min-height
的初始值是auto
,max-width/max-height
的初始值是none
。max-width/max-height
的初始值为何是none
而不是auto
呢?这个问题的答案其实与下面小节的内容有关。我们不妨举个简单的例子解释一下,已知父元素宽度400像素,子元素设置宽度800像素,假如说max-width
初始值是auto
,那自然使用和width
一样的解析渲染规则,此时max-width
的计算值就应该是父元素的400像素,此时,你就会发现,子元素的800像素直接完蛋了,因为max-width
会覆盖width
。于是,我们的width
永远不能设置为比auto
计算值更大的宽度值了,这显然是有问题的,这就是为什么max-width
初始值是none
的原因。
!important,
超越最大CSS世界中,min-width/max-width和min-height/max-height
属性间,以及与width和height
之间有一套相互覆盖的规则。这套规则用一句比较通俗的话概括就是:超越!important,
超越最大。究竟是什么意思呢?以与宽度相关的属性举例说明。
!important
超越!important
指的是max-width
会覆盖width
,而且这种覆盖不是普通的覆盖,是超级覆盖,覆盖到什么程度呢?大家应该都知道CSS世界中的!important
的权重相当高,在业界,往往会把!important
的权重比成“泰坦尼克”,比直接在元素的style
属性中设置CSS声明还要高,一般用在CSS覆盖JavaScript设置上。但是,就是这么厉害的!important
,直接被max-width
一个浪头就拍沉了。比方说,针对下面的HTML和CSS设置,图片最后呈现的宽度是多少呢?
<img src="1.jpg" style="width:480px!important;">
img { max-width: 256px; }
答案是256px
。style
、!important
通通靠边站!因为max-width
会覆盖width
。
眼见为实,手动输入http://demo.cssworld.cn/3/3-1.php或者扫下面的二维码。结果如图3-27所示。
图3-27 图片宽度256像素
超越最大指的是min-width
覆盖max-width
,此规则发生在min-width
和max-width
冲突的时候。例如,下面这种设置:
.container {
min-width: 1400px;
max-width: 1200px;
}
最小宽度居然比最大宽度设置得还大,此时,两者必定是你死我活的状态。究竟谁死呢?遵循“超越最大”规则(注意不是“后来居上”规则),min-width
活下来,max-width
被忽略,于是,.container
元素表现为至少1400像素宽。
此覆盖规则比较好理解,就不专门演示了。
“展开收起”效果是网页中比较常见的一种交互形式,通常的做法是控制display
属性值在none
和其他值之间切换,虽说功能可以实现,但是效果略显生硬,所以就会有这样的需求——希望元素展开收起时能有明显的高度滑动效果。传统实现可以使用jQuery的slideUp()/slideDown()
方法,但是,在移动端,因为CSS3动画支持良好,所以移动端的JavaScript框架都是没有动画模块的。此时,使用CSS实现动画就成了最佳的技术选型。
我们的第一反应就是使用height
+
overflow:hidden
实现。但是,很多时候,我们展开的元素内容是动态的,换句话说高度是不固定的,因此,height
使用的值是默认的auto
,应该都知道的auto
是个关键字值,并非数值,正如height:100%
的100%
无法和auto
相计算一样,从0px
到auto
也是无法计算的,因此无法形成过渡或动画效果。
因此,下面代码呈现的效果也是生硬地展开和收起:
.element {
height: 0;
overflow: hidden;
transition: height .25s;
}
.element.active {
height: auto; /* 没有transition效果,只是生硬地展开 */
}
难道就没有什么一劳永逸的实现方法吗?有,不妨试试max-height
,CSS代码如下:
.element {
max-height: 0;
overflow: hidden;
transition: max-height .25s;
}
.element.active {
max-height: 666px; /* 一个足够大的最大高度值 */
}
其中展开后的max-height
值,我们只需要设定为保证比展开内容高度大的值就可以,因为max-height
值比height
计算值大的时候,元素的高度就是height
属性的计算高度,在本交互中,也就是height:auto
时候的高度值。于是,一个高度不定的任意元素的展开动画效果就实现了。
眼见为实,手动输入http://demo.cssworld.cn/3/3-2.php或者扫右侧的二维码。
但是,使用此方法也有一点要注意,即虽然说从适用范围讲,max-height
值越大使用场景越多,但是,如果max-height
值太大,在收起的时候可能会有“效果延迟”的问题,比方说,我们展开的元素高度是100像素
,而max-height
是1000像素,动画时间是250 ms,假设我们动画函数是线性的,则前225 ms我们是看不到收起效果的,因为max-height
从1000像素到100像素变化这段时间,元素不会有区域被隐藏,会给人动画延迟225 ms的感觉,相信这不是你想看到的。
因此,我个人建议 max-height
使用足够安全的最小值,这样,收起时即使有延迟,也会因为时间很短,很难被用户察觉,并不会影响体验。
如果纯粹套CSS规范的话,这里标题应该是“内联级元素”(inline-level elements)。但在本书中“内联级元素”全部简称为“内联元素”,原因在第3章开头部分已做说明,不再赘述。
在讲元素的内外盒子的时候,前面曾提到过“外在盒子”有inline
、block
和run-in
三种水平。其中run-in
鲜有人使用,且有淘汰风险,可以忽略;剩下的inline
和block
几乎瓜分了剩下的全部江山,是流体布局的本质所在。从作用上来讲,块级负责结构,内联负责内容。CSS世界是为图文展示而设计的。所谓图文,指图片和文字,是最典型的内联元素。所以,在CSS世界中,内联元素是最为重要的,涉及的CSS属性也非常之多,这些CSS属性往往具有继承特性,混合在一起会导致CSS解析规则非常复杂。这就是内联元素的解析比块级元素解析更难理解的原因——其是多个属性共同作用的结果,需要对内联元素特性,内联盒模型以及当前CSS属性都了解,才能明白其中的原因。
不要担心,从这里开始,我们会慢慢揭开内联世界的层层面纱。
我们先来了解如何辨别内联元素。
首先要明白这一点:“内联元素”的“内联”特指“外在盒子”,和“display
为inline
的元素”不是一个概念!inline-block
和inline-table
都是“内联元素”,因为它们的“外在盒子”都是内联盒子。自然display:inline
的元素也是“内联元素”,那么,<button>
按钮元素是内联元素,因为其display
默认值是inline-block
;<img>
图片元素也是内联元素,因为其display
默认值是inline
等。
就行为表现来看,“内联元素”的典型特征就是可以和文字在一行显示。因此,文字是内联元素,图片是内联元素,按钮是内联元素,输入框、下拉框等原生表单控件也是内联元素。
下面有一个疑问:浮动元素貌似也是可以和文字在一个水平上显示的,是不是浮动元素也是内联级别的呢?不是的。实际上,浮动元素和后面的文字并不在一行显示,浮动元素已经在文档流之外了。证据就是,当后面文字足够多的时候,文字并不是在浮动元素的下面,而是继续在后面。这就说明,浮动元素和后面文字不在一行,只是它们恰好站在了一起而已。真相是,浮动元素会生成“块盒子”,这就是后话了。
本节的内容可谓CSS进阶标志知识点,是入门CSS开发人员和熟练CSS开发人员之间的分水岭,是需要反复拿来看拿来体味的。这里介绍的“内联盒模型”是简易版,但是已经足够,如果大家对完整的概念和名词感兴趣,可以阅读规范文档。
下面是一段很普通的HTML:
<p>这是一行普通的文字,这里有个 <em>em</em> 标签。</p>
看似普通,实际上包含了很多术语和概念,或者换种通俗的说法,包含了很多种盒子。我归结为下面这些盒子。
(1)内容区域(content area)。内容区域指一种围绕文字看不见的盒子,其大小仅受字符本身特性控制,本质上是一个字符盒子(character box);但是有些元素,如图片这样的替换元素,其内容显然不是文字,不存在字符盒子之类的,因此,对于这些元素,内容区域可以看成元素自身。
定义上说内容区域是“看不见的”,这对理解“内容区域”是不利的,好在根据我多年的理解与实践,我们可以把文本选中的背景色区域作为内容区域,例如,如图3-28所示。
图3-28 内容区域示意
这对于解释各种内联相关的行为都非常可行,文本选中区本质上就等同于基本盒尺寸中的content box,都是content
,语义上也说得通。
实际上,内容区域并没有明确的定义,所以将其理解为em盒(em-box,可看成是中文字符占据的1 em高度区域)也是可以的,但是在本书中,为了方便演示和讲解,将其全部理解为文本选中的区域。
在IE和Firefox浏览器下,文字的选中背景总能准确反映内容区域范围,但是Chrome浏览器下,::selection
范围并不总是准确的,例如,和图片混排或者有垂直padding
的时候,范围会明显过大,这一点需要注意。后面行高等章节会利用此选中背景帮助我们理解。
内容区域在解释内联元素的各种行为表现时,出镜率出奇地高,建议大家这里多多留意。
(2)内联盒子(inline box)。“内联盒子”不会让内容成块显示,而是排成一行,这里的“内联盒子”实际指的就是元素的“外在盒子”,用来决定元素是内联还是块级。该盒子又可以细分为“内联盒子”和“匿名内联盒子”两类:
如果外部含内联标签(<span>
、<a>
和<em>
等),则属于“内联盒子”(实线框标注);如果是个光秃秃的文字,则属于“匿名内联盒子”(虚线框标注)。
需要注意的是,并不是所有光秃秃的文字都是“匿名内联盒子”,其还有可能是“匿名块级盒子”,关键要看前后的标签是内联还是块级。
(3)行框盒子(line box)。例如:
< p> 这是一行普通的文字,这里有个 < em>em< /em> 标签。 < /p>
每一行就是一个“行框盒子”(实线框标注),每个“行框盒子”又是由一个一个“内联盒子”组成的。
(4)包含盒子(containing box)。例如:
< p>这是一行普通的文字,这里有个 < em>em< /em> 标签。< /p>
<p>
标签就是一个“包含盒子”(实线框标注),此盒子由一行一行的“行框盒子”组成。
需要补充说明一点,在CSS规范中,并没有“包含盒子”的说法,更准确的称呼应该是“包含块”(containing block)。这里之所以把它称为盒子,一是为了与其他盒子名称统一,二是称为盒子更形象、更容易理解。
“幽灵空白节点”是内联盒模型中非常重要的一个概念,具体指的是:在HTML5文档声明中,内联元素的所有解析和渲染表现就如同每个行框盒子的前面有一个“空白节点”一样。这个“空白节点”永远透明,不占据任何宽度,看不见也无法通过脚本获取,就好像幽灵一样,但又确确实实地存在,表现如同文本节点一样,因此,我称之为“幽灵空白节点”。
注意,这里有一个前提,文档声明必须是HTML5文档声明(HTML代码如下),如果还是很多年前的老声明,则不存在“幽灵空白节点”。
<!doctype html>
<html>
我们可以举一个最简单的例子证明“幽灵空白节点”确实存在, CSS和HTML代码如下:
div {
background-color: #cd0000;
}
span {
display: inline-block;
}
<div><span></span></div>
结果,此<div>
的高度并不是0
,而是如图3-29所示有高度。
图3-29 高度不为0
证明幽灵空白节点的存在(截自Chrome浏览器)
这着实很奇怪,内部的<span>
元素的宽高明明都是0
,标签之间也没有换行符之类的嫌疑,怎么<div>
的高度会是图3-29中所示的18像素
呢?
作祟的就是这里的“幽灵空白节点”,如果我们认为在<span>
元素的前面还有一个宽度为0
的空白字符,是不是一切就解释得通呢?
当然,为何高度是18像素
这里三言两语是解释不清的,可以看后面对line-height
和vertical-align
的深入讲解,这里只是为了证明“幽灵空白节点”确实是存在的。
虽然说“幽灵空白节点”是我自己根据CSS的特性表现起的一个非常形象的名字,但其绝不是空中楼阁、信口胡诌的。规范中实际上对这个“幽灵空白节点”是有提及的,“幽灵空白节点”实际上也是一个盒子,不过是个假想盒,名叫“strut”,中文直译为“支柱”,是一个存在于每个“行框盒子”前面,同时具有该元素的字体和行高属性的0宽度的内联盒。规范中的原文如下:
Each line box starts with a zero-width inline box with the element’s font and line height properties. We call that imaginary box a “strut”.
明白“幽灵空白节点”的存在是理解后续很多内联元素为何会这么表现的基础。
[1] “外在盒子”除了inline- block
,还有run-in
,但Chrome已经放弃对run-in
的支持有一段时间了,因此,本书不对其做分析。