只用一个 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 元素为基准。

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

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

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

相关推荐
腾讯TNTWeb前端团队2 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰5 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪5 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪5 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy6 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom7 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom7 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom7 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom7 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试