CSS伪元素有哪些?伪元素的妙用技巧
什么是伪元素?
「伪元素」之所以称作「伪」,除了英文从「Pseudo」翻译过来之外,就是因为它并不是真正网页里的元素,但行为与表现又和真正网页元素一样,也可以对其使用 CSS 操控。
跟伪元素类似的还有「伪类」(Pseudo classes),在 W3C 的定义里总共有五个伪元素(其他仍在测试阶段),分别是::before、::after、::first-line、::first-letter 和::selection,为了和伪类区分,伪元素使用两个冒号「::」开头,而伪类使用一个冒号「:」开头(像是:hover、:target…等)。
虽然现在的浏览器就算写一个冒号也可以正常运作,不过为了方便区分,用两个冒号还是比较好的,而且不论浏览器是什么,::selection 必须是两个冒号才能正常运作。
参考:MDN Pseudo-elements、伪类 child 和 of-type
::before 与::after
::before、::after 大概是最常使用的伪元素,两者都是以 display:inline-block 的属性存在,::before 是在原本的元素「之前」加入内容,::after 则是在原本的元素「之后」加入内容,同时伪元素也会「继承」原本元素的属性,如果原本文字是黑色,伪元素的文字也会是黑色。
举例来说,下面这段代码,有一个 div 内容是「大家好,我是 div」,使用::before、::after 之后,会在原本 div 的前后各添加一段文字,并且让这两段文字都呈现红色。
div::before{ content:"我是 before"; color:red; } div::after{ content:"我是 after"; color:red; }
效果图如下:
实用的 content
上述的内容乍看之下很容易理解,比较需要注意的是一定要具备 content 的属性,就算是只有 content:””;都可以,因为没有 content 的伪元素是不会出现在画面上的,然而 content 是个很特别的属性,它可以使用 attr 直接获取内容元素的属性值( attribute ),举例来说,在 HTML 里有一个超连结,点击后会弹出新视窗并连结至 Baidu:
<a href="https://www.baidu.com" target="_blank" rel="noopener noreferrer">google</a>
例如下段代码,将会把超连结的 href 内容与 target 内容,通过伪元素一前一后的显示出来。
a::before{ content: attr(href); color:red; } a::after{ content: attr(target); color:green; }
此外 content 内容是可以「相加」的,不过用法不像 JavaScript 使用+号来相连,而是直接用一个空白键就可以不断的累加下去,以下面的程式码来说,可以在刚刚撷取的超连结文字后方和 target 属性前方,加入标点符号。
a::before{ content: "( " attr(href) " ) < "; color:red; } a::after{ content: " > ( " attr(target) " ) "; color:green; }
content 甚至可以使用 url 放入图片的功能,下列的程式码会呈现出三张图片。
div::before{ content:url(图片网址) url(图片网址) url(图片网址); }
小技巧:通过调整 border 的属性,我们可以实现上下左右的三角形,再结合伪元素 before,after,content 可以绘制多种多边形,感兴趣的可以看看我之前写的一篇文章《css 优雅的画出形状图形》
content 搭配 quotes 使用
在 CSS 里有个不常用的属性就是 quotes,这是做为定义「括号格式」的属性,也就是如果在一段文字被包住,这段文字的前后就会出现自定义的标签替换(可以是括号、特殊符合、文字等),而且 quotes 支持多层嵌套,也就是你可以一层层的写下去,以下面这段 HTML 文字举例:
最外层<q>第一层<q>第二层</q><q>第二层<q>第三层</q></q></q>
quotes 的属性如果只写一层,就会看到只出现一种括号,前后括号使用空白分隔,两组为一个单位,前后可以不同符号。
q{ quotes: ' < ' ' > '; }
如下图:
如果写了三层,就会看到出现三种括号,也会把文字当作括号使用。
q{ quotes: ' < ' ' > ' ' ya ' ' ya ' ' ( ' ' ) ' ; }
(请注意开合标签的就近分配原则)
如图:
同样的道理,我们可以应用在 content 里面,而且通过伪元素::before 和::after 处于前后的预设位置,甚至不用就实现前后括号的效果,以下面这段 HTML 文字举例,把刚刚的 q 全部换成 span:
最外层<span>第一层<span>第二层</span><span>第二层<span>第三层</span></span></span>
CSS 的部分比较特别,在伪元素 content 里使用了 open-quote (启始括号)和 close-quote (结束括号)这两个有趣的值,换句话说 open-quote 对应到,close-quote 对应到,此外也由于括号是在伪元素内,就可以指定不同的颜色或样式了。
span{ quotes: ' < ' ' > ' ' ya ' ' ya ' ' ( ' ' ) ' ; } span::before{ content:open-quote; color:red; } span::after{ content:close-quote; color:#aaa; }
如图:
content 与 counter 实用技巧
counter 基本用法
在 CSS 里头,counter 是个很有意思的功能,最常见得就是如果我们使用 list 清单,样式选择 decimal 十进制,当清单内容变多的时候数字也会随着递增,底层貌似就是使用 counter 的原理,也因为 counter 所产生的数值并不实际存在于网页的元素内,所以如果我们要在清单元素之外使用,就必须透过::before 或::after 的 content 来实现。
counter 最的基本用法一定要有一个父元素和子元素(类似 list 的原理,比如使用 ul 包着 li),所以页面布局会类似下面这段 html:
<div> <span>钢铁侠</span> <span>美国队长</span> <span>雷神索尔</span> </div>在 CSS 里头,先针对 div 父元素使用 counter-reset:num;进行计数器初始化的设置,里面 num 是计数器累以数值计算的设置,接着可以在 span::before 里面看到 counter-increment:num;这一段,这段的作用是把 num 累加上去,预设数值为加 1,接着就通过 content 这个属性显示出来。计数器预设的显示语法为:counter(计数器名称, list-style-type)div{ counter-reset:num; } span{ display:block; } span::before{ counter-increment:num; content:counter(num) '. '; }效果如下:
通过指定一开始 counter-reset 的起始计数值,还有 counter-increment 累加的递增数值(步长),还可以指定从某个数值开始计数。div{ counter-reset:num 3; } span{ display:block; } span::before{ counter-increment:num 2; content:counter(num) '. '; }效果如下:
如果要更换数字的样式,也可以透过计数器的第二个设定值 list-style-type 来更改,下面的例子就是将样式更改为 georgian。div{ counter-reset:num; } span{ display:block; } span::before{ counter-increment:num; content:counter(num, georgian) '. '; }counter 进阶用法
除了指定单一个变数外,counter 也可以同时指定多个变数,例如下面这段 HTML,有三个类别在里面,我分别用 span、i 和 b 来分类。
<div> <span>钢铁侠</span> <span>美国队长</span> <span>雷神索尔</span> <i>神盾局</i> <i>神鬼局</i> <i>神经局</i> <b>九头蛇</b> <b>九头牛</b> <b>九头猪</b> </div>CSS 一开始 counter-reset 可以指定多个计数器,通过一个空白字符进行分隔,如果空白字符后面跟着数字则是起始值,没有数字预设为 1,当这样设定之后,就可以看到不同类别的数字代号就不同。
div{ counter-reset:num numi 2 numb 5; } span, i, b{ display:block; } span::before{ counter-increment:num; content:counter(num) '. '; } i::before{ counter-increment:numi 2; content:counter(numi) '. '; } b::before{ counter-increment:numb 5; content:counter(numb) '. '; }效果如下:
如果遇到了层级结构(目录结构),需要一层层的展开( 例如:1 > 1.1 > 1.1.1 ),采用上述的作法可能就会复杂许多,好在 counter 还提供了另外一个 counters 的功能,目的就是来解决层级结构的麻烦事,在开始前可以先看看通过 ul 和 li 组合的清单布局结构:<ul> <li>第一层 <ul> <li>第二层 <ul> <li>第三层</li> <li>第三层</li> <li>第三层</li> </ul> </li> <li>第二层</li> <li>第二层</li> </ul> </li> <li>第一层</li> <ul> <li>第二层</li> <li>第二层</li> </ul> </ul>传统的清单如果将 list-style 设为 decimal,同样可以具备数字连续的功能,但相对来说要做一些特殊变化就办不到了。
li{ list-style:decimal; }效果如下图:
通过 content 和 counters 的搭配,我们就可以告别预设值的困扰,甚至可以在不使用清单 ul 和 li 的状况下,实现和清单一模一样的效果,举例来说,我们纯粹通过 div 模拟一个清单的布局( 仍然必须是有父元素和子元素的概念),里面的样式 b 就等于是 ul,样式 a 就等于是 li:<div class="a">第一层 <div class="b"> <div class="a">第二层 <div class="b"> <div class="a">第三层</div> <div class="a">第三层</div> <div class="a">第三层</div> </div> </div> <div class="a">第二层</div> <div class="a">第二层</div> </div> </div> <div class="a">第一层 <div class="b"> <div class="a">第二层</div> <div class="a">第二层</div> </div> </div>由于 b 的外层没有东西,所以一开始要把 body 和 b 都进行 counter reset 的动作,接着通过 counters 的使用,让计数器的数值可以一个接着一个放进去,如此一来就可以做到原本清单不容易实现的效果了。
counters 使用语法:counters(计数器名称, 分隔符, list-style-type)
body, .b{ counter-reset:c; } .a::before{ content:counters(c, ".") ":"; counter-increment:c; } div{ margin-left:10px; }效果如下:
了解原理之后,通过::before 和::after 的交互应用,就可以做出颇具特色的清单效果。body, .b{ counter-reset:c; } .a{ box-sizing:border-box; position:relative; line-height:40px; } .a .a{ padding-left:30px; } .a::after{ content:''; box-sizing:border-box; display:inline-block; position:absolute; z-index:-1; top:0; left:0; width:100%; height:40px; margin-left:30px; box-shadow:inset 0 2px #666; background:#eee; } .a::before{ content:counter(c, upper-roman); counter-increment:c; display:inline-block; width:30px; height:40px; background:#666; color:#fff; text-align:center; margin-right:5px; }效果如下图:
::first-line
::first-line 顾名思义就是「第一行」,通过这个伪元素可以轻松指定文字的第一行,需要注意的是::first-line 「不能」作用于 display:inline 的元素。以下面的例子,html 里有一段文字如下所示:
<p> 好前端公众号,已经有五年的历史啦,目前有几千名前端开发者订阅,公众号的宗旨是:分享当下最实用的前端技术。 关注好前端,与数千名达人们一起进步!期待你的订阅和关注! </p>CSS 只要这样写,页面呈现出来的第一行就会是绿色的,不论视窗如何缩放,只有第一行会是绿色的。
p::first-line{ color:green; }效果如下图:
::first-letter
::first-letter 顾名思义就是「第一个字」,通过这个伪元素,可以做出许多文章第一个字放大或变色的效果,我们这里就用刚刚上面那段文字为例,把第一个字用下段的 CSS 来做变化,就可以看到第一个字放大且变色了的效果。
p::first-letter{ font-weight:bold; font-size:38px; color:red; }效果如下图:
虽然把第一个字放大了,但排版上仍然有点乱没有美感,这时你可以加入 line-height、float 或 padding 等属性进行修正,经过修正后,你会惊喜的发现很像报纸杂志会出经常用的效果(第一个字会跨行显示)。p::first-letter{ font-weight:bold; font-size:38px; color:red; line-height:26px; float:left; padding:10px 5px 0 0; }效果如下图:
不过很有趣的是,在实际应用的过程里,发现「有一些符号」是无法套用::first-letter 的,例如「『 {} [] 都不行,但如果后方加上其他文字或符号,又会跟着一起放大...( 到底是怎样?)
经过查询 W3C 的官网,发现了下面这段话,意思大概就是说网页里面有定义一些所谓「包覆式、点缀式的标点符号」,如果是这些包覆式的标点符号,基本上就无法放大,反而需要搭配其它字符进行使用,因此,在使用第一个字进行特殊变化时,就要注意有这种特殊状况会发生。参考:https://www.w3.org/TR/CSS21/selector.html#first-letter
::selection
::selection 是个十分常见的伪元素,它就是负责一段选取文字的效果,以下面这段 CSS 来说,选取后的文字,就会是深色背景,黄色文字。
p::selection{ color:yellow; background:#543; }效果如下图:
用 JavaScript 操控伪元素
虽然我们能用 CSS 操控伪元素,但因为伪元素不存在于网页元素内,所以无法通过 JavaScript 常规操控 DOM 的方式来修改或控制,不过 JavaScript 身为一个神通广大的编程语言,仍然是有方法可以办到的。
读取伪元素属性
一般来说使用 JavaScript 读取某个元素 DOM 里的属性不难,但相对来说要读取一个不存在网页里的元素就不容易,如果要读取伪元素属性,可以通过 getComputedStyle 来获得,getComputedStyle 是个可以获取当前元素「所有的 CSS 属性值」,读取后会返回一个 Object CSSStyleDeclaration,而这个属性是只读的,无法进行修改。
使用方法:window.getComputedStyle('元素', '伪元素')
举例来说 html 放入一个 div 以及一个 span,待会会用这个 span 来显示 div 的::before 属性。
<div id="d">我是 div</div> <span id="s"></span>CSS 的部分指定伪元素的 content 和 color。
#d::before{ content:'伪元素的 content '; color:red; }JavaScript 使用 window.getComputedStyle(d,'::before')获取 div 里头伪元素使用的 style,然后显示在 span 里面。
var d = document.getElementById('d'); var s = document.getElementById('s'); var b = window.getComputedStyle(d,'::before'); s.innerHTML = b.content +'
'+b.color;最后页面呈现的结果,第一段就是原本的 div 加上红色的伪元素文字,下方第一段是 content 的内容,紧接着是伪元素的颜色属性。
修改伪元素属性
我们可以读取属性值也就一定要尝试修改,不过修改伪元素的属性其实比想像中的难,必须通过 insertRule 这个方法在指定的 style 里插入「预设的规则」,让这个规则去影响伪元素的属性表现。
用法:style 标签元素.insertRule(样式规则, 0)
举例来说我们的网页布局如下,一开始开头的部分有两组 style,第一组是我们赋予元素的样式属性,第二组则是要来定义规则的 style,因为要加入规则,所以让第二组 style 有一个 id。至于 html 就放入一个 div。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>JS Bin</title> <style> #d::before{ content:'伪元素的 content '; color:red; } </style> <style id="css"></style> </head> <body> <div id="d">我是 div</div> </body> </html>在完全没有编写 JavaScript 的状态,应该会呈现如下图的样子:
JavaScript 开声明一个变量 css,通过 id 获取 style,然后使用在指定一个变量给 css.sheet,就可以通过 insertRule 的方法修改了。需要注意的是,由于规则加入时会放在整串 style 的开头(第二个值预设 0 ),所以纯粹使用一个#d 是无法覆盖原本的属性( CSS 权重问题),所以这边使用#d#d 两次,就可以在权重上压过原本的属性。(当然如果要用!important 也是可以)var css = document.getElementById('css'); var d = document.getElementById('d'); var c = css.sheet; c.insertRule("#d#d::before{content:'我是修改的 content ';}", 0); c.insertRule("#d#d::before{color:blue;}", 0);如此一来,呈现出来的效果就是通过 JavaScript 修改的。
修改伪元素 content
我们知道::before 和::after 的 content 可以通过 attr 获取父元素的属性,因此通过改变这个属性,就能间接连带改变 content 的内容,举例来说有个 div,我们指定它的 data-text="我是预设文字",然后放两个按钮,期望点选不同的按钮,会更换 content 不同的内容。
<button id="b1">显示 ABC</button> <button id="b2">显示 123</button> <div data-text="我是预设文字">我是 div</div>接着设定 CSS,关键在使用 content 的 attr,让伪元素直接显示父元素属性的内容。
button{ font-size:16px; } div{ margin:10px; font-size:20px; } div::before{ content: attr(data-text) ','; }最后就是 JavaScript 的部分,通过 setAttribute 更改 div 的属性,就会看到 content 的内容修改了。
var b1 = document.getElementById('b1'); var b2 = document.getElementById('b2'); var d = document.querySelector('div'); b1.addEventListener('click',function(){ d.setAttribute('data-text','ABC'); }); b2.addEventListener('click',function(){ d.setAttribute('data-text','123'); });
虽然说我们可以通过 JavaScript 来操控伪元素,但伪元素终究不是真正的网页元素,也因此操作起来也不如基本操作网页元素 DOM 来的简便,所以如果可以,还是尽量用正常的操控模式吧。结束语
关于伪元素的系列文章就介绍到这里,内容很长很长,如果你能看到这里,为你点个赞。虽然说伪元素很好用,但伪元素的内容实际上不存在网页里( 如果打开浏览器的开发者工具,是看不到内容的),所以如果在里头塞了太多的重要的内容,反而会影响到 SEO 的成效,因此对于使用伪元素的定位,还是当作「辅助」性质会比较恰当。
码云笔记 » CSS伪元素有哪些?伪元素的妙用技巧