只用一个 HTML 元素可以写出多少形状?——伪元素篇(上)

只用一个 div 元素,我们已经通过四个篇章写了很多形状。

首先,我们通过对这个 div 的宽度与高度的直接控制,轻松写出矩形和正方形,并结合 transform 的 skew 方法写出了平行四边形与菱形。

其次,我们通过对边框的灵活运用,与宽度和高度相结合,成功写出了各种各样的三角形和梯形。

再次,我们结合圆角边框的灵活使用,写出了圆形与椭圆形等由弧形组成的形状,也可以和直线配合写出扇形或者吃豆人等个性形状。

最后,我们通过对阴影属性的灵活控制,我们可以使用类似于控制每一个像素点的方式写出更加复杂的形状。

同时,阴影属性的引入,我们在理论上已经可以写出一切形状,甚至只用一个 div 元素就可以通过控制每一个像素点画出一张复杂图片。

然而,我们在上一个篇章,一共使用了 30 个阴影才写出了一个相对简单的太空入侵者的图案。并且每一个阴影都是和元素本身的形状是一样的,做不到每一个元素在形状方面个性化。

那么今天,我们就使用伪元素,用更加灵活轻松的方式来写出各种各样的形状吧!


一、伪元素

伪元素是一个附加至选择器末的关键词,允许我们对被选择元素的特定部分修改样式。

截止到今天,一共有 15 个伪元素:

  • ::before: 创建一个伪元素,作为所选元素的第一个子元素。

  • ::after: 创建一个伪元素,作为所选元素的最后一个子元素。

  • ::first-letter: 将样式应用于区块容器第一行的第一个字母,但仅当其前面没有其他内容(例如图像或行内表格)时才有效。

  • ::first-line: 在某块级元素的第一行应用样式。

  • ::placeholder: 表示 <input> 或 <textarea> 元素中的占位文本。

  • ::selection: 应用于文档中被用户高亮的部分(比如使用鼠标或其他选择设备选中的部分)。

  • ::cue: 匹配所选元素中的 WebVTT 提示。

  • ::file-selector-button: 代表 type="file" 的 <input> 的按钮。

  • ::marker: 匹配列表的标记框(通常为一个符号或数字)。

  • ::part(): 表示在阴影树中任何匹配 part 属性的元素。

  • ::slotted(): 用于选定那些被放在 HTML 模板中的元素。

  • ::backdrop: 在任何处于全屏模式的元素下的即刻渲染的盒子。

  • ::grammar-error: 应用于浏览器标识为语法错误的文本段。

  • ::spelling-error: 表示浏览器标记为不正确拼写的文本段。

  • ::target-text: 代表了浏览器在支持文本片段技术时所滚动到的文字。

说明:

  1. ::backdrop、::grammar-error、::spelling-error、::target-text 四个为实验性技术,在 W3C 确认定稿之前,我们都继续做观望,而不做过多展开;

  2. ::cue、::part()、::slotted() 三个伪元素比较冷门,实际开发中几乎用不上,这里也不做展开了;

  3. ::file-selector-button 和 ::marker 两个伪元素在实际开发中用的比较少,就只做简单介绍了。

1. before 和 after

::before 和 ::after 两个伪元素,分别作为所选元素的第一个子元素和最后一个子元素,通常用于为具有 content 属性的元素添加修饰内容。默认情况下,它是行向布局的。

css 复制代码
div::before {
  content: '';
}
div::after {
  content: '';
}

可以看到,伪元素的使用方法是附加在选择器的末端,这部分的样式仅仅控制被该伪元素指定的部分内容的样式。

代码中,我们写了 ::before 和 ::after 两个伪元素,这两个伪元素需要在 CSS 中使用 content 属性激活。

通过浏览器的控制台查看渲染后的 DOM 结构,看到咱们的 div 元素中多了 ::before 和 ::after 两个伪元素。

为了看的足够清晰,我们给这个 div 元素添加一些文字信息:

html 复制代码
<div>这里是 div 标签中的内容</div>

再次检查浏览器的控制台可以看到渲染后的 DOM 结构如下:

接下来,我们再在两个伪元素的 content 属性中都添加一些文字信息:

css 复制代码
div::before {
  content: '这里是 before 中的内容';
}
div::after {
  content: '这里是 after 中的内容';
}

现在我们再次检查浏览器的控制台,我们惊讶的看到渲染后的 DOM 结构依然如下:

不同的是,在浏览器主窗口中呈现出来的显示效果是按照顺序显示出来的:

当我们在浏览器的控制台中选中其中一个伪元素,在浏览器主窗口中也会标识出对应的元素信息,同时我们可以看到该伪元素的 CSS 控制信息:

现在我们尝试把这两个伪元素都控制一下样式:

css 复制代码
div::before {
  display: block;
  color: red;
  content: '这里是 before 中的内容';
}
div::after {
  display: block;
  color: blue;
  content: '这里是 after 中的内容';
}

我们大胆的控制这两个伪元素,将其设置为块级元素(最新叫法为块盒),并且分别设置文字颜色为红色与蓝色。得到效果如下:

还是一样的,我们在浏览器的控制台中选中其中一个伪元素,然后观察在浏览器主窗口中标识出的对应元素信息,以及该伪元素的 CSS 控制信息:

很明显,伪元素完全受到 css 控制,彷佛就是一个直接写在 html 中的元素。

这里,我们可以得到一如下结论:

  • ::before 伪元素永远是元素中的第一个子元素,::after 伪元素永远是元素中的最后一个子元素,两个伪元素均需要 content 属性将其激活,伪元素的内容放在 content 属性中;

  • ::before 和 ::after 两个伪元素默认为行内元素(最新叫法为行盒),可以通过 CSS 进行任意控制,就相当于写在元素中的两个 HTML 子标签。

::before 和 ::after 两个伪元素是我们画各种形状的篇章中,使用最多的两个伪元素,也是实际开发中使用最多的伪元素。在后面画形状的文中,我们还会不断使用。

这里,我们先对别的伪元素做一个简单的介绍。

2. first-letter

::first-letter 伪元素将样式应用于区块容器第一行的第一个字母,但仅当其前面没有其他内容(例如图像或行内表格)时才有效。

咱们在 div 元素中放入一段文字:

html 复制代码
<div>将梦想悬挂于枝头,在夏季的丰盛中饱满,绽放;为梦想披星戴月,刷新生命的温度。那些因梦想蜕变了的灵魂,历经时光坎坷,痛苦挣扎,依然在枝头放歌,温暖了生命如花的影子。前世,储蓄梦想;今生,演绎铿锵。</div>

为了显示效果,控制 div 的宽度为 500px,并给一个淡蓝色的背景。

css 复制代码
width: 500px;
background: lightblue;

现在显示一段正常的文字:

现在我们给这个 div 添加上 ::first-letter 伪元素:

css 复制代码
div::first-letter {
  font-size: 72px;
  color: red;
}

这里,我们设置 ::first-letter 伪元素的样式为字体大小为 72px,文字颜色为红色。得到如下效果:

明显的看到,这段文字的第一个字,其显示样式发生了变化。并且,在浏览器的控制台中是无法找到的,而且伪元素的样式控制中,也不需要 content 属性来进行激活。

3. first-line

::first-line 伪元素将样式应用于某块级元素的第一行应用样式。

和 ::first-letter 伪元素中的样例一样,我们仅仅换成 ::first-line 伪元素:

css 复制代码
div::first-line {
  font-size: 72px;
  color: red;
}

得到的效果如下:

明显的看到,这段文字的第一行,其显示样式发生了变化。

4. selection

::selection 伪元素应用于文档中被用户高亮的部分(比如使用鼠标或其他选择设备选中的部分)。

和 ::first-letter 伪元素中的样例一样,我们仅仅换成 ::selection 伪元素:

css 复制代码
div::selection {
  font-size: 72px;
  color: red;
}

得到的效果如下:

我们可以看到,选中的文字呈现了红色,但是文字大小不变。这是因为,在 ::selection 伪元素中只能使用 color、background-color、cursor、caret-color、outline、text-decoration、text-emphasis-color、text-shadow 这八个控制文字的 CSS 属性,其它 CSS 属性会被自动忽略。

注意:background-image 会如同其他属性一样被忽略。

5. placeholder

::placeholder 伪元素表示 <input> 或 <textarea> 元素中的占位文本。

我们在 HTML 中写入一个 input 元素:

html 复制代码
<input type="text" placeholder="这里是我们的占位符!" />

我们通过 placeholder 属性设置了占位符,默认显示效果如下:

然后,我们使用 ::placeholder 伪元素改变占位符中的文字颜色为红色:

css 复制代码
input::placeholder {
  color: red;
}

这样就可以控制占位符的文字样式了。

6. file-selector-button

::file-selector-button 伪元素代表 type="file" 的 <input> 的按钮。

我们在 HTML 中写入一个 type="file" 的 input 元素:

css 复制代码
<input type="file" />

我们给这个 input 设置文字颜色为蓝色:

css 复制代码
input {
  color: blue;
}

显示效果如下:

可以看到,我们控制的文字颜色是文件名部分的文字颜色,按钮中的文字颜色还是默认的黑色。

然后,我们使用 ::file-selector-button 伪元素改变按钮中的文字颜色为红色:

css 复制代码
input::file-selector-button {
  color: red;
}

这样就可以控制按钮的文字样式了。

7. marker

::marker 伪元素匹配列表的标记框(通常为一个符号或数字)。

我们在 HTML 中写入一个无序列表:

html 复制代码
<ul>
  <li>造纸术</li>
  <li>印刷术</li>
  <li>指南针</li>
  <li>火药</li>
  <li>杂交水稻</li>
</ul>

默认效果如下:

可以看到,无序列表中每一项使用的默认标记框是一个黑点,现在我们想要让其变成自定义的标记框,直接使用 ::marker 伪元素:

css 复制代码
li::marker {
  content: '→';
}

在 ::marker 伪元素中,我们也是使用 content 属性对自定义标记框进行声明,效果如下:

无论是无序列表还是有序列表,列表项使用的都是 li 标签。熟悉推广的宝子们都知道,li 标签对 SEO 非常不友好。所以我们写列表的时候,更多使用的是 dl 列表。

于是,我们把 HTML 中的列表换成 dl 列表:

html 复制代码
<dl>
  <dt>我国五大发明</dt>
  <dd>造纸术</dd>
  <dd>印刷术</dd>
  <dd>指南针</dd>
  <dd>火药</dd>
  <dd>杂交水稻</dd>
  <dt>我国五大淡水湖</dt>
  <dd>鄱阳湖</dd>
  <dd>洞庭湖</dd>
  <dd>太湖</dd>
  <dd>洪泽湖</dd>
  <dd>巢湖</dd>
  <dt>五岳</dt>
  <dd>泰山</dd>
  <dd>衡山</dd>
  <dd>华山</dd>
  <dd>恒山</dd>
  <dd>嵩山</dd>
</dl>

dl 列表中,可以使用 dt 标签定义列表标题,然后使用 dd 标签定义列表项,效果如下:

然后,我们直接对 dd 标签使用 ::marker 伪元素:

css 复制代码
dd::marker {
  content: '→';
}

结果我们惊讶的发现,没有起效果:

这是因为,::marker 伪元素作用在任何设置了 display: list-item 的元素或伪元素上,例如 li 和 summary 元素。而 dd 元素默认情况下是没有设置 display: list-item 属性的,于是我们手动给 dd 元素设置 display: list-item 属性:

css 复制代码
dd {
  display: list-item;
}

这样一来,我们的 ::marker 伪元素就生效了:

最后要说的是,::marker 伪元素不一定非要使用 content 属性,对于有序列表和无序列表来说,由于默认自带列表的标记框,于是我们可以直接控制其显示样式:

css 复制代码
li::marker {
  font-size: 72px;
  color: red;
}

如此这般,我们直接控制标记框的样式,效果如下:

在将 ::marker 作为选择器的规则中,只能使用某些 CSS 属性:

  • 所有的字体属性

  • white-space 属性;

  • color 属性;

  • text-combine-upright、unicode-bidi 和 direction 属性;

  • content 属性;

  • 所有的 animation 和 transition 属性。

说明: 规范指出,将来可能会支持其他 CSS 属性,让我们拭目以待吧!


二、正五边形

还记得我在欧洲杯的时候写过一篇用纯前端写一个足球的文章。

文章中,我们已经明确分析过,要写一个正五边形,只需要写一个等腰梯形和一个等腰三角形即可:

通过计算,得到如下数值:

先写一个上底长下底短的等腰梯形:

css 复制代码
width: 200px;
height: 0;
border-top: 190.35px solid red;
border-right: 61.8px solid transparent;
border-left: 61.8px solid transparent;

然后使用 ::before 伪元素写一个等腰三角形,并通过定位控制到对应的位置:

css 复制代码
div::before {
  position: absolute;
  top: -285px;
  left: -61.8px;
  display: block;
  width: 0;
  height: 0;
  border-right: 161.8px solid transparent;
  border-bottom: 95.1px solid red;
  border-left: 161.8px solid transparent;
  content: '';
}

于是,一个正五边形就写出来了:

之所以在这里重申了正五边形的写法,主要目的是要阐述几个观点:

  1. 在伪元素中,::before 和 ::after 两个伪元素,相当于两个子标签,使用 content 属性激活之后,可以完全当作两个 span 标签使用;

  2. 结合我们前面的四个篇章,相当于是拥有了三个标签,每一个标签都可以写出前面四个篇章中的任何一个形状,三个形状相结合之后,又可以得到更多的形状;

  3. ::before 和 ::after 两个伪元素是原元素的子标签,所以只需要通过对个元素设置一个非 static 的定位,就可以直接对这两个伪元素进行非常容易的定位。

关于定位的知识,我曾经在用纯前端写一个足球中做过讲解。

  • position 属性是控制元素定位的,默认值为 static。

  • 若一个元素的 position 属性值为绝对定位(absolute),即可通过 top、bottom 两个属性控制该元素的竖直偏移,也可以通过 right、left 两个属性控制该元素的水平偏移。

  • 若没有设置 top、bottom 两个属性,则竖直方向的偏移和 position 的值为 static 显示的一样;若没有设置 right、left 两个属性,则水平方向的偏移和 position 的值为 static 显示的一样。

  • 无论水平方向还是竖直方向,只要设置了偏移属性,那么就需要看该元素的父元素的 position 是什么值。若父元素的 position 不是 static,那么就会以该父元素的容器范围为基准开始偏移;若父元素的 position 是 static,那么就会无视该父元素的管理范围,继续向上寻找"爷爷"元素,直到找到 position 不是 static 的元素,或者直接以 body 元素为基准。

如此,通过再次画正五边形,顺便带领大家复习了定位的知识,也阐述了使用伪元素画出更多形状的方法。

那么下一篇章,我们就引入一些例子来实战一下吧!让我们敬请期待!

关注"临界程序员",为您送上更多精彩内容!

相关推荐
喵叔哟8 分钟前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特1 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解1 小时前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
Mr.咕咕1 小时前
Django 搭建数据管理web——商品管理
前端·python·django