「css基础」你想知道的伪元素内容都在这篇文章里(长文值得收藏)

640?wx_fmt=png

关注前端达人,与你共同进步


640?wx_fmt=jpeg

开篇


最近因为一些网页的需要,需要比较深入的使用了CSS 的「伪元素」( Pseudo Element ),发现原来不只是用用before或after 而已,可以玩的东西还真是不少,所以就来篇文章,把这些比较不常玩的用法归纳整理下,希望对你的日常工作有所帮助。


内容来源:

https://www.oxxostudio.tw/articles/201706/pseudo-element-1.html


https://www.oxxostudio.tw/articles/201706/pseudo-element-2.html

https://www.oxxostudio.tw/articles/201706/pseudo-element-3.html


https://www.oxxostudio.tw/articles/201706/pseudo-element-4.html


作者:oxxostudio


注:由于网站是繁体内容,术语描述和话术与我们有差异的问题,笔者在保证不改变原意的基础上做了调整,文中的内容和例子笔者都做了确认与验证,如有问题,欢迎留言指正。


01

什么是「伪元素」?

「伪元素」之所以称作「伪」,除了英文从「Pseudo」翻译过来之外,就是因为它并不是真正网页里的元素,但行为与表现又和真正网页元素一样,也可以对其使用CSS 操控。


跟伪元素类似的还有「伪类」( Pseudo classes ),在W3C的定义里总共有五个伪元素(其他仍在测试阶段),分别是::before、::after、::first-line、::first-letter和::selection,为了和伪类区分,伪元素使用两个冒号「::」开头,而伪类使用一个冒号「:」开头(像是:hover、:target...等)。


虽然现在的浏览器就算写一个冒号也可以正常运作,不过为了方便区分,用两个冒号还是比较好的,而且不论浏览器是什么,::selection必须是两个冒号才能正常运作。


参考:MDN Pseudo-elements、伪类child和of-type


02

::before 与::after

认识::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;
}

640?wx_fmt=jpeg


实用的content


上述的内容乍看之下很容易理解,比较需要注意的是一定要具备content的属性,就算是只有content:"";都可以,因为没有content的伪元素是不会出现在画面上的,然而content是个很特别的属性,它可以使用attr直接获取内容元素的属性值( attribute ),举例来说,在HTML里有一个超连结,点击后会弹出新视窗并连结至Google:

<a href="https://www.google.com" target="_blank">google</a>

例如下段代码,将会把超连结的href 内容与target 内容,通过伪元素一前一后的显示出来。

a::before{
content: attr(href);
color:red;
}
a::after{
content: attr(target);
color:green;
}

640?wx_fmt=jpeg


此外content内容是可以「相加」的,不过用法不像JavaScript使用+号来相连,而是直接用一个空白键就可以不断的累加下去,以下面的程式码来说,可以在刚刚撷取的超连结文字后方和target属性前方,加入标点符号。

a::before{
content: "( " attr(href) " ) < ";
color:red;
}
a::after{
content: " > ( " attr(target) " ) ";
color:green;
}

640?wx_fmt=jpeg


content 甚至可以使用url 放入图片的功能,下列的程式码会呈现出三张图片。

div::before{
content:url(图片网址) url(图片网址) url(图片网址);
}


640?wx_fmt=jpeg


小技巧:通过调整border的属性,我们可以实现上下左右的三角形,再结合伪元素before,after,content可以绘制多种多边形,笔者在这篇文章有过介绍,感兴趣的可以看看 :《只用1个div,你能用CSS绘制:正3、4、5、6、7、8边形吗?》


content搭配quotes使用


在CSS里有个不常用的属性就是quotes,这是做为定义「括号格式」的属性,也就是如果在一段文字被包住,这段文字的前后就会出现自定义的标签替换(可以是括号、特殊符合、文字等),而且quotes支持多层嵌套,也就是你可以一层层的写下去,以下面这段HTML文字举例:

最外层<q>第一层<q>第二层</q><q>第二层<q>第三层</q></q></q>

quotes 的属性如果只写一层,就会看到只出现一种括号,前后括号使用空白分隔,两组为一个单位,前后可以不同符号。

q{
quotes: ' < ' ' > ';
}

640?wx_fmt=jpeg

如果写了三层,就会看到出现三种括号,也会把文字当作括号使用。

q{
quotes: ' < ' ' > ' ' ya ' ' ya ' ' ( ' ' ) ' ;
}

(请注意开合标签的就近分配原则)

640?wx_fmt=jpeg

同样的道理,我们可以应用在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;
}

640?wx_fmt=jpeg


03

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) '. ';
}

640?wx_fmt=jpeg

通过指定一开始counter-reset 的起始计数值,还有counter-increment累加的递增数值(步长),还可以指定从某个数值开始计数。

div{
counter-reset:num 3;
}
span{
display:block;
}
span::before{
counter-increment:num 2;
content:counter(num) '. ';
}

640?wx_fmt=jpeg

如果要更换数字的样式,也可以透过计数器的第二个设定值list-style-type来更改,下面的例子就是将样式更改为georgian。

div{
counter-reset:num;
}
span{
display:block;
}
span::before{
counter-increment:num;
content:counter(num, georgian) '. ';
}

640?wx_fmt=jpeg


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) '. ';
}

640?wx_fmt=jpeg

如果遇到了层级结构(目录结构),需要一层层的展开( 例如: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;
}

640?wx_fmt=jpeg

通过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;
}

640?wx_fmt=jpeg

了解原理之后,通过::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;
}

640?wx_fmt=jpeg

04

::first-line

::first-line顾名思义就是「第一行」,通过这个伪元素可以轻松指定文字的第一行,需要注意的是::first-line 「不能」作用于display:inline的元素。以下面的例子,html里有一段文字如下所示:

<p> 前端达人公众号,已经有五年的历史啦,目前有几千名前端开发者订阅,公众号的宗旨是:分享当下最实用的前端技术。
关注前端达人,与数千名达人们一起进步!期待你的订阅和关注!
</p>

CSS 只要这样写,页面呈现出来的第一行就会是绿色的,不论视窗如何缩放,只有第一行会是绿色的。

p::first-line{
color:green;
}

640?wx_fmt=jpeg

05

::first-letter

::first-letter顾名思义就是「第一个字」,通过这个伪元素,可以做出许多文章第一个字放大或变色的效果,我们这里就用刚刚上面那段文字为例,把第一个字用下段的CSS 来做变化,就可以看到第一个字放大且变色了的效果。

p::first-letter{
font-weight:bold;
font-size:38px;
color:red;
}

640?wx_fmt=jpeg

虽然把第一个字放大了,但排版上仍然有点乱没有美感,这时你可以加入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;
}

640?wx_fmt=jpeg

不过很有趣的是,在实际应用的过程里,发现「有一些符号」是无法套用::first-letter 的,例如「『 {} [] 都不行,但如果后方加上其他文字或符号,又会跟着一起放大...( 到底是怎样?)

640?wx_fmt=jpeg

经过查询W3C的官网,发现了下面这段话,意思大概就是说网页里面有定义一些所谓「包覆式、点缀式的标点符号」,如果是这些包覆式的标点符号,基本上就无法放大,反而需要搭配其它字符进行使用,因此,在使用第一个字进行特殊变化时,就要注意有这种特殊状况会发生。


参考:https://www.w3.org/TR/CSS21/selector.html#first-letter


640?wx_fmt=jpeg


06

::selection


::selection 是个十分常见的伪元素,它就是负责一段选取文字的效果,以下面这段CSS来说,选取后的文字,就会是深色背景,黄色文字。

p::selection{
color:yellow;
background:#543;
}

640?wx_fmt=jpeg


07

用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 +'<br/>'+b.color;

最后页面呈现的结果,第一段就是原本的div加上红色的伪元素文字,下方第一段是content的内容,紧接着是伪元素的颜色属性。


640?wx_fmt=jpeg


修改伪元素属性


我们可以读取属性值也就一定要尝试修改,不过修改伪元素的属性其实比想像中的难,必须通过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的状态,应该会呈现如下图的样子:

640?wx_fmt=jpeg

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修改的。


640?wx_fmt=jpeg

修改伪元素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');
});

640?wx_fmt=gif

虽然说我们可以通过JavaScript 来操控伪元素,但伪元素终究不是真正的网页元素,也因此操作起来也不如基本操作网页元素DOM 来的简便,所以如果可以,还是尽量用正常的操控模式吧。


640?wx_fmt=gif

关于伪元素的系列文章就介绍到这里,内容很长很长,如果你能看到这里,为你点个赞。虽然说伪元素很好用,但伪元素的内容实际上不存在网页里( 如果打开浏览器的开发者工具,是看不到内容的),所以如果在里头塞了太多的重要的内容,反而会影响到SEO 的成效,因此对于使用伪元素的定位,还是当作「辅助」性质会比较恰当。


精彩推荐

css基础丨一次搞懂CSS 字体单位:px、em、rem 和 %

css基础丨只用1个div,你能用CSS常规属性绘制:正3、4、5、6、7、8边形吗?

CSS 3D 专题丨搞懂 CSS 3D,你必须理解 perspective(视域)这个属性

JS基础丨Array 数组操作方法大全( 含ES6 )

JS小技巧丨随机不重复的ID,模板标签替换,XML与字符串互转,快速取整

CSS 3D 专题丨学习前,你需要搞明白什么是CSS 3D?

css实用手册丨CSS 垂直居中的七种方法,值得收藏

Web Animation API丨用原生JS制作一个图片随机移动的动画

十款热门的Vue.js工具和库

vue基础丨新手入门篇(一)

小技巧丨console的用法,不仅仅只有console.log()

动画基础丨点和直线

太惊艳了,这些画都是CSS的杰作!

css基础丨Transforms 属性在实际项目中如何应用?

css基础丨如何理解transform的matrix()用法

css基础丨如何理解Display的属性:None,Block,Inline,Inline-Block

ES6基础丨let和作用域

ES6基础丨const介绍

ES6基础丨默认参数值

ES6基础丨展开语法(Spread syntax)

ES6基础丨解构赋值(destructuring assignment)

ES6基础丨箭头函数(Arrow functions)

ES6基础丨模板字符串(Template String)

ES6基础丨Set与WeakSet

ES6基础丨Map与WeakMap

ES6基础丨Symbol介绍:独一无二的值

ES6基础丨Object的新方法

ES6基础丨迭代器(iterator)

ES6基础丨生成器(Generator)

ES6基础丨你需要知道的Array数组新方法(上)

数据结构基础丨栈简介(使用ES6)

数据结构基础丨队列简介(使用ES6)

JavaScript基础丨前端不懂它,会再多框架也不过只是会用而已!

JavaScript基础丨你真的了解JavaScript吗?

JavaScript基础丨回调(callback)是什么?

JavaScript基础丨Promise使用指南

JavaScript基础丨深入学习async/await

JS加载慢?谷歌大神带你飞!(文末送电子书)

19年你应该关注这50款前端热门工具(上)

19年你应该关注这50款前端热门工具(中)

19年你应该关注这50款前端热门工具(下)

专注分享当下最实用的前端技术。关注前端达人,与达人一起学习进步!

长按关注"前端达人"

640?wx_fmt=gif

640?wx_fmt=jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值