CSS:深入content
如果你的灵魂进入了另一个人的身体,那么他是他还是你呢?content属性是一个元素的心,当你偷走了它的心,它就不再像它了,而是像一个——替换元素。
替换元素
是否为替换元素是CSS中的另一种分类。元素根据其外在盒子被分为块状元素和内联元素,而根据其内容是否替换分为替换元素和非替换元素。
特性
在我看来,替换元素分为两种:
一种是内容可替换的元素,即img、object、video、iframe和表单元素input、textarea、select等等都是内容可替换的,这里的内容可替换是指它们的内容可以通过改变标签的属性值(如src或value)就可以改变,即:可通过属性替换其内容。
另一种是内容被替换的元素,即原本不是替换元素的普通元素,通过content内容生成技术为其生成了内容,使其的表现几乎完全相同于替换元素,那么它们就是内容被替换的元素。
那么替换元素的特性或者说表现都有哪些?主要有:
- 其内容的外观不受页面上CSS的影响。如input单选框或复选框,它们的一些样式是被固定的,你无法通过写CSS来改变它们的这些样式如背景色、内补间等。
- 拥有自己的默认尺寸。很多替换元素如video、iframe、canvas等在没有明确规定尺寸时,尺寸表现为300×150px,也有img这样的默认尺寸为0,也有像input这样的有其默认的其他尺寸。
- 还有一些与众不同的CSS表现值。比如
vertical-align
默认为基线,而替换元素则默认元素下边缘。
那么问题来了,替换元素与非替换元素是独立于块状元素和内联元素的分类体系,那么替换元素是块状元素还是内联元素?实际上,他们中有块状元素也有内联元素,甚至在不同浏览器中也不一样:
尺寸
尺寸的种类
替换元素的尺寸由内而外分为以下三种:
- 固有尺寸:即替换的内容本身的尺寸,如一张图片的尺寸。
- HTML尺寸:通过HTML原生属性指定的尺寸,如图片的width、height属性,input的size属性,textarea的cols和rows属性。
- CSS尺寸:指使用CSS设定的尺寸,包括了
width
、height
、max/min-width
、max/min-height
等。
这些尺寸的优先级关系是CSS尺寸>HTML尺寸>固有尺寸,即当没有指定HTML尺寸和CSS尺寸的时候,替换元素表现为固有尺寸,当指定了HTML尺寸则表现为HTML尺寸,当指定了CSS尺寸就表现为CSS尺寸。
这里要注意HTML尺寸要和行内样式、外部样式的优先级进行区别,这里的HTML尺寸设定的不是行内样式!因此优先级弱于CSS样式。对于CSS样式,依然是行内样式优先级大于外部样式表。
例外的图片
上面说到,对于video,没有设定尺寸的默认尺寸是300×150px,在认知中似乎图片应该和视频表现一样才对,而图片的默认尺寸却是0,原因是video我们一般不会让它和文字混在一起排版,而图文混排的情况太多了,甚至还会有文字中掺杂着小图标、小表情的情况出现,这时如果图片加载出了问题,或者意外遗落了img标签,未为它设置样式和加载源,那么它在文字中间突兀地出现了一大片300×150px的空白区域,岂不是很离谱?因此img默认尺寸就是0,遗落的img标签就让它看起来消失而不影响原本的文字排版。
可是还有个意外,就是IE浏览器,总是表现得和其他主流浏览器与众不同,它的img标签什么都没有时,会使用一个28×30px的占位图标。
从图片懒加载说起
图片懒加载其实就是如果一个页面上有很多图片,并不在页面打开时一起加载,而是懒懒地等着用户就要滚动到了它的位置,不得不加载了再去请求图片进行加载,像极了每次任务不到DDL就不会去做的你。
图片懒加载是一种网页性能优化的方式,它能极大的提升用户体验,提高打开页面的速度,并且如果用户并没有往下翻页,下面的图片不会被加载,节省了用户的流量。图片懒加载就需要使用一个没有src属性的img标签来占位。
占位img
我们直接用一个裸的img标签来占位即可,同时设置:
1 |
|
之后需要加载的时候使用JS为img标签设置src属性即可。
之所以使用裸的、没有src的标签,是因为很多浏览器只要有src属性就会产生请求,完全不让它产生请求就不可以有src属性,这是最高效的实现方式。
但是这里问题出现了,我们希望元素按照一定宽高占位,没有内容的img的默认尺寸为0,使用CSS尺寸来为其设定尺寸,这在Chrome和IE浏览器下都没有问题,却在Firefox浏览器下失效了。此时要想解决这个兼容性问题,只需要:
1 |
|
对于在这种图片懒加载的场景下,为防止占位img标签宽高失效,可以在这个页面上进行这样的全局重置。
固有尺寸不可改变
由于图片作为替换元素有上述三种尺寸,那么很多人就会认为好像给img标签设置宽高就改变了图片的固有尺寸一样,实际上不是的,图片的固有尺寸是完全取决于图片文件的本来尺寸的,使用HTML和CSS是无法改变的,但是之所以改变img标签的宽高就能够改变图片的尺寸,是因为图片在标签中的填充模式为fill
,即充满标签。
在CSS3中有一个新的属性可以用来修改替换内容的适配方式,即object-fit
,它默认即为fill
,它支持的值有:
fill
:默认,不保证保持原有的比例,内容拉伸填充整个内容容器。contain
:保持原有尺寸比例,内容被缩放以尽可能利用HTML标签的尺寸但又不会超出。cover
:保持原有尺寸比例,但是会尽可能覆盖标签的空间不留下空隙,而内容可能会被裁剪。none
:保留原有元素内容的固有尺寸,内容尺寸将不受控制。scale-down
:保持原有尺寸比例。内容的尺寸与none
或contain
中的一个相同,取决于它们两个之间谁得到的对象尺寸会更小一些。
那么这么一看,你可能想到了那个东西。对,就是背景图片。
背景图片的填充模式也是和上述类似的,而背景图片默认的填充模式就是不拉伸,以固有尺寸显示图片,但是会重复背景图片以填满标签。
实际上,还有一种情况就是在::before
或::after
伪元素中使用content
生成的图片内容,这时候图片就会以固定尺寸来显示,并且你还无法改变它的尺寸:
替换元素的界限
实际上,如果非要纠结替换元素和非替换元素之间的界限,无异于纠结替换元素的准确定义,可在不同的浏览器中,替换元素的定义和表现总会有这样那样的差别。因此这部分内容我强烈建议你只用来了解,不必深究,因为这些东西包含了一些反模式的“奇技淫巧”,应当避免使用的(比如把img标签当span来用)。之所以还是要写这部分内容,是因为稍微了解它们也许在你的代码出现莫名其妙的bug时,能够从中找到一些原因。
典型的替换元素
典型的替换元素自然是公认的像img标签这样的可替换元素,它们生来就是替换元素……吗?这里我们深究的话,如果你正常使用一张图片,它确实是替换元素,可在某些情况下,它也可以是一个无异于span标签的普通元素。这种情况就是前面说的:没有src属性时。
在Firefox浏览器中,如果你使用一个裸的、不带src属性的img标签,它的表现完全就像一个裸的span标签一样,表现上没有丝毫不同。这时你如果再给它设置成display: block;
,那么它的表现和一个div元素又没有任何不同了,就好像它就是一个普普通通的非替换元素一样。
而在Chrome浏览器中(截至写本文时),裸的img标签会表现为像span一样的内联标签,而设置了display: block;
反而会使它回归替换元素的本性,表现为0×0的尺寸,而无法表现得像div标签一样。
还有就是,某些浏览器下没有src但是有alt属性的img标签会显示出一个裂开的图片小图标并显示出alt指定的信息,要想在懒加载没有添加src属性时隐藏掉它,可以这样:
1 |
|
非典型的替换元素
非典型的替换元素就是你认为它生来不是替换元素,但我们使用CSS的content
属性替换了它的内容,替换了之后我们也称它是替换元素了。
为什么这样说呢?实际上你可以尝试给一个div使用content
属性设置一张图片,发现它的表现和一个设置了src属性的img标签没什么两样。甚至上面我们不是说裸的img标签就表现得和一个普普通通的span标签没什么两样了吗?但是我们通过content
给它生成一个图片内容,发现它竟然表现得又和设置了src的img标签一样了!
啊不好意思,实际上说一样了也不完全正确,因为content
属性生成的内容始终有一个特性,即无法选中、复制等操作,就好像生成的东西只是一个虚拟的像一样,无法对这些内容进行操作。就好像之前我们也尝试过,在::before
和::after
伪元素里使用content
生成的文字没法选中,也没法获取,它们不是页面内容的一部分,而是样式的一部分。
因此有了这样的一个例子:
HTML:
1 |
|
CSS:
1 |
|
效果如下:
你看它是一个完全没问题的图片对吗,你可以尝试把它保存到本地(鼠标右键另存为)试一试,看看保存下来的图片。
是不是很有意思?你保存下来的图片竟然和显示的不一样?
这个例子加工一下就成了这样:
1 |
|
效果如下:
当鼠标经过图片时,图片变成了另一张图片,从笑脸变成了笑哭,原理就是这个img标签原本的src指向的是笑脸,而鼠标经过时使用content替换了其中的内容为一张笑哭的表情,你尝试将这张图片保存下来,你会发现笑哭的图片无论如何都无法保存下来,因为它不属于页面内容,只是一个生成的表面的像,真正保存下来的只能是img标签的src属性指向的图片。
content内容生成技术
使用CSS的content属性生成标签内容,我们称其为content内容生成技术,我们可以通过它做到许多非(huā)常(lǐ)有(hú)用(shào)的事情。
字符内容生成
字符内容的生成就很简单了,在::before
和::after
伪元素中我们经常用到(因为这两个伪元素中必须要有content
属性,没有的话元素就不会显示,哪怕我们常常将它设置为空字符串,仅仅是为了让伪元素显示出来)。
此处不再赘述字符生成。
清除浮动
清除浮动我们往往需要多余的标签来设置clear
属性,而使用伪元素加上空的content
可以生成用来清除浮动的伪元素:
1 |
|
辅助对齐
同样是空的content来生成的伪元素,还能够辅助元素对齐。
有如下代码:
HTML:
1 |
|
CSS:
1 |
|
效果如下:
可以看到四个柱子并排放置,而我们希望绘制出柱状图一样的对齐方式,即四根柱子都靠着容器底部对齐,而不是现在这样靠着容器顶部;还要让它们两端对齐,平均分布,而不是像现在这样靠在左边。那么只需要生成这样的::before
和::after
伪元素:
1 |
|
即可达到效果:
我们可以看到是两个伪元素分别撑起了宽和高,但具体的原理我们先按下不表,这不属于本文内容。
图片生成
内容与显示
使用content是可以给普通元素生成图片的,用图片来替换元素原本的内容,那么问题来了,像这样的代码:
HTML:
1 |
|
CSS:
1 |
|
效果如下:
那么问题来了:替换了div中的内容之后,div中原本的内容哪儿去了?
实际上这种方式是兼顾了表现效果和SEO的。SEO为搜索引擎优化,也就是搜索引擎在收录你的网站时,会抓取你的页面上的内容作为网站概要,而抓取的内容主要就是文字。
如果这里完全使用img来代替div标签,将导致搜索引擎抓取不到对应的文字信息,也就无法得到网页的概要;对于屏幕阅读器来说,页面上使用图片来替代文字的内容将无法被阅读,这可能给使用它的人造成困扰。
如果使用内容生成技术,像这样使用图片代替文字的情况下,原本的h1标签内的文字信息依然能被抓取到,即内容还在HTML中,而CSS提供了单独的显示效果,这样能够同时兼顾显示效果和页面内容不丢失。
生成内联图片
content
通过url()
功能符生成图片,而这样并非只能生成外部图片,它还可以生成base64图片,这样的图片是内联在CSS文件中的,因此不会从外部加载,防止了图片从无到有加载过程中的页面抖动。
如下面的CSS即为使用base64地址生成一张图片:
1 |
|
(代码中的地址内容过长,故省略。)
生成的图片如下:
但是注意,这种方式生成图片往往比直接加载原图片占用更多的网络资源。
开启闭合符号生成
可以像这样指定开始/结束符号:
1 |
|
然后使用open-quote
和close-quote
关键字使content
生成开始和结束符号。
1 |
|
属性值内容生成
可以在content
属性中使用attr()
功能符,从属性中获取值并生成内容。如可以为img生成描述信息:
1 |
|
content计数器
content计数器功能非常强大、实用,是content
部分的重中之重。在很多情况下它具有不可替代性,甚至可以实现连JavaScript都不好实现的效果。
counter-reset属性
该属性的作用是重置或初始化一个计数器,你需要指定计数器的名称以及初始从哪个值开始。如果没有指定初始值,它将从0开始。初始值最好不要设置为负数和小数,因为部分浏览器可能不兼容,不兼容的话依然是默认值0。如下面的属性为.counter
元素初始化了一个计数器counter1:
1 |
|
而下面代码是同时指定了两个计数器,并分别从0和1开始:
1 |
|
counter()功能符
该功能符引用一个计数器,使用计数器名称作为参数,这样我们就可以使用上面定义的计数器的值了:
HTML:
1 |
|
CSS:
1 |
|
效果如下:
实际上counter()
功能符还支持第二个参数,即list-style-type
属性所支持的参数:
1 |
|
那么我们可以这样使用大写罗马数字来显示计数器内容:
1 |
|
counter-increment属性
我们已经知道了该如何初始化和显示一个计数器,那么最重要的就是如何让这个计数器开始计数?即如何递增。
此时应当使用counter-increment
属性来实现,该属性需要提供计数器名称作为属性值,并可提供第二个可选的递增步长,不指定递增步长的话默认为1。
在渲染页面元素时,每渲染一次counter-increment
属性,都会增加对应的值,如下面的例子:
HTML:
1 |
|
CSS:
1 |
|
效果如下:
需要注意的一些问题:
counter()
功能符只能用在伪元素中来生成内容,普通元素无效。counter-increment
属性也可以一次性递增多个计数器的值,和初始化计数器的方式一样。- 递增的值可以为负数,这样就实现了递减。
counter()
功能符生成的值是静态的,之后计数器的值改变了并不会影响已生成的值。
counters()功能符
通过counters()
功能符可以实现嵌套计数,实现二级标题。
该功能符需要两个参数,第一个为计数器名称,第二个为多级标题的连接符,如"1.1"
的连接符即为"."
,而"1-1"
的连接符为"-"
。
此时你的小小的眼睛一定充满了大大的疑惑:既然是多级计数器,为什么只有一个计数器名称呢,难道不应该是计数器1对应着第一级标题,然后计数器2对应着第二级标题吗?
这里就要感叹设计者的高明了:如果如你所想,那么有几层标题就得需要几个计数器来单独计数,也不是不行,但是不够灵活,比如说内容是动态加载的,而你实现并不知道有几层标题呢?
所以这里就要说到counter-reset
的本质了——初始化或重置——重置的即是嵌套的计数器,有以下例子:
1 |
|
对于这样的结构,我们可以看出,每当新开一级目录,都进行reset,即CSS是这样的:
1 |
|
效果如下:
这是我使用的是counter()
功能符而非counters()
功能符,可以看到显示结果是按照我们已知的规则进行的:即按照页面渲染顺序,遇到reset就重置,遇到increment就递增。如果我改用counters()
功能符呢?
我们把每一个reset都塞进它的上一级的count里面,即把第一章的两个章节构成的reset塞进第一章的count里面,然后使用counters()
功能符:
HTML:
1 |
|
CSS:
1 |
|
效果如下:
可以看出一个reset就是一个层级,我给每个层级还加了padding-left
便于观察,这时就可以看到完美的二级标题出现了。
这样只要按照规则去嵌套HTML标签,简单的CSS就可以实现出任意层级的多级标题,完美!
此外counters()
功能符也是支持第三个参数用来指定数字的显示样式的,和counter()
功能符的第二个参数一样。
总结
如果你只需要一级计数器,那么你的用来显示content: counter(cnt);
的标签应当是互相无嵌套的,这时每次reset都会重置计数器,每次increment都会递增计数器,计数的顺序按照渲染顺序。
如果你需要多级计数器,那么对于每个显示content: counters(cnt, "-");
的count标签,它的下一级的所有count标签都应当有一个共同的reset父容器,并且这个reset容器应当作为它的直接子标签,下一级的所有count标签又要作为它们reset容器的直接子标签,如此嵌套下去即可。总之reset容器和count标签必须为父子关系,不能以兄弟关系出现,否则必乱套。
混合内容生成
content
是可以生成多个内容的,中间以空格分隔即可。如:
1 |
|