前言

前段时间笔者收到可恶丑陋的小册姐姐的邀请,做了人生首次直播分享。分享主题是《玩转CSS的艺术之美》,跟笔者在9月底公布的掘金小册同名。

9月底公布的玩转CSS的艺术之美,首日预售就达到709本,预售仅三日就破1000本。这也让笔者感到诧异,没想到CSS技术还是那么受倔友们的欢送,让笔者感觉熬夜半年写这本小册还是值得的,毕竟能将本人的学习心路分享进来,让更多同学学到更多货色,也是一件值得开心的事件。

因为首次做直播分享,感觉比拟缓和,家里网络不是特地好,还有其余起因,导致认真筹备的内容未在意料工夫内实现分享,因而通过本文将来不及分享的内容整理出来。

目录

对分享内容感兴趣的同学可关注笔者的公众号IQ前端,回复CSSPPT下载分享PPT。分享内容蕴含历史背景概念原理开发技巧三节。第一二节比拟无聊,可自行查看PPT,在此就不多说了。次要是第三节的干货,是笔者认真筹备了好几天的内容,每个主题都会有对应的源码及其成果。

笔者抉择了一些罕用甚至有些小册都未提及到的干货作为分享内容,置信这些内容能帮忙同学们在短期内晋升CSS编码素质,实现一些看似只能由JS能力实现的成果。

  • 神奇的选择器
  • 浅谈布局那些事
  • 绘制三角的原理
  • 完满极致的变量
  • 添枝加叶的伪元素
  • 灵便多变的障眼法
  • 动向不到的内容插入
  • 无所不能的模仿点击事件
筹备工作

整个分享过程不搞那些乌七八糟的环境搭建。既然只玩CSS,那只有html文件css文件就足够了。另外还需一个浏览器Chrome和一个编辑器VSCode

VSCode还需装置Live Sass CompilerLive Server两个插件。Live Sass Compiler用于实时编译sass/scss文件css文件Live Server用于启动具备实时刷新性能的本地开发服务器,以解决动态页面和动静页面。

新建index.htmlindex.scss。为了使各大浏览器默认款式统一,还需引入一个磨平浏览器默认款式的css文件,同学们可下载笔者写好的reset.css到本地目录里。

<!doctype html><html><head>    <meta charset="utf-8">    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, minimum-scale=1, maximum-scale=1">    <title>Hello CSS</title>    <link rel="stylesheet" href="./reset.css">    <link rel="stylesheet" href="./index.css"></head><body class="flex-ct-x">    <!-- ... --></body></html>
body {    overflow: hidden;    height: 100vh;}

上述文件骨架实现后,关上index.scss,按F1Cmd + Shift + P关上命令面板,输出Watch Sass监听index.scss并生成index.css,再输出Open With Live Server启动本地开发服务器并关上浏览器。到此为止就实现了所有筹备工作了。

神奇的选择器

很多CSS编码习惯都是清一色的类而无相应的选择器,层层嵌套的标签都蕴含至多一个类。选择器比照起来性能上的确没后者那么好,但现在浏览器对于CSS的解析速度已失去大大的晋升,齐全可疏忽选择器那丁点的性能问题。

可是CSS模块泛滥,顺次推出的选择器也很多。若无特地办法记熟这些选择器对应的性能,也很难将选择器施展到最大作用。玩转CSS的最要害一步是能相熟大部分选择器及其性能与应用场景

笔者依据选择器的性能划分出八大类,每个类别的选择器都能在一个应用场景中相互组合,记熟这些类别的选择器,置信就能将选择器施展到最大作用。以下选择器的罕用选项里若打勾可强行记熟,这些选择器都是笔者久经沙场而标记进去且认为是最好用的选择器。

根底选择器
选择器别名阐明版本罕用
tag标签选择器指定类型的标签1
#idID选择器指定身份的标签1
.class类选择器指定类名的标签1
*通配选择器所有类型的标签2
档次选择器
选择器别名阐明版本罕用
elemP elemC后辈选择器元素的后辈元素1
elemP>elemC子代选择器元素的子代元素2
elem1+elem2相邻同胞选择器元素相邻的同胞元素2
elem1~elem2通用同胞选择器元素前面的同胞元素3
汇合选择器
选择器别名阐明版本罕用
elem1,elem2并集选择器多个指定的元素1
elem.class交加选择器指定类名的元素1
条件选择器
选择器阐明版本罕用
:lang指定标记语言的元素2×
:dir()指定编写方向的元素4×
:has蕴含指定元素的元素4×
:is指定条件的元素4×
:not非指定条件的元素4
:where指定条件的元素4×
:scope指定元素作为参考点4×
:any-link所有蕴含href链接元素4×
:local-link所有蕴含href且属于相对地址的链接元素4×
状态选择器
选择器阐明版本罕用
:active鼠标激活的元素1×
:hover鼠标悬浮的元素1
:link未拜访的链接元素1×
:visited已拜访的链接元素1×
:target以后锚点的元素3×
:focus输出聚焦的表单元素2
:required输出必填的表单元素3
:valid输出非法的表单元素3
:invalid输出非法的表单元素3
:in-range输出范畴以内的表单元素3×
:out-of-range输出范畴以外的表单元素3×
:checked选项选中的表单元素3
:optional选项可选的表单元素3×
:enabled事件启用的表单元素3×
:disabled事件禁用的表单元素3
:read-only只读的表单元素3×
:read-write可读可写的表单元素3×
:target-within外部锚点元素处于激活状态的元素4×
:focus-within外部表单元素处于聚焦状态的元素4
:focus-visible输出聚焦的表单元素4×
:blank输出为空的表单元素4×
:user-invalid输出非法的表单元素4×
:indeterminate选项未定的表单元素4×
:placeholder-shown占位显示的表单元素4
:current()浏览中的元素4×
:past()已浏览的元素4×
:future()未浏览的元素4×
:playing开始播放的媒体元素4×
:paused暂停播放的媒体元素4×
构造选择器
选择器阐明版本罕用
:root文档的根元素3×
:empty无子元素的元素3
:nth-child(n)元素中指定顺序索引的元素3
:nth-last-child(n)元素中指定逆序索引的元素3×
:first-child元素中为首的元素2
:last-child元素中为尾的元素3
:only-child父元素仅有该元素的元素3
:nth-of-type(n)标签中指定顺序索引的标签3
:nth-last-of-type(n)标签中指定逆序索引的标签3×
:first-of-type标签中为首的标签3
:last-of-type标签中为尾标签3
:only-of-type父元素仅有该标签的标签3
属性选择器
选择器阐明版本罕用
[attr]指定属性的元素2
[attr=val]属性等于指定值的元素2
[attr*=val]属性蕴含指定值的元素3
[attr^=val]属性以指定值结尾的元素3
[attr$=val]属性以指定值结尾的元素3
[attr~=val]属性蕴含指定值(残缺单词)的元素(不举荐应用)2×
`[attr\=val]`属性以指定值(残缺单词)结尾的元素(不举荐应用)2×
伪元素
选择器阐明版本罕用
::before在元素前插入的内容2
::after在元素后插入的内容2
::first-letter元素的首字母1×
::first-line元素的首行1×
::selection鼠标选中的元素3×
::backdrop全屏模式的元素4×
::placeholder表单元素的占位4

选择器真正的用途不仅仅是阐明选项里的形容,更多是搭配起来能起到的最大作用。这些选择器组成的选择器零碎是整个CSS体系里的外围,应用选择器能带来以下益处。

  • 清晰易读:对于那些构造与行为拆散的写法,应用sass/less编写属性时构造会更清晰易读,缩小很多无用或少用的类,放弃css文件的整洁性和观赏性
  • 确保统一:缩小批改类而有可能导致款式生效的问题,有时批改类但未确保HTML和CSS的统一而导致款式生效
  • 剔除累赘:缩小无实质性应用的类,例如很多层嵌套的标签,这些标签可能只应用到一个属性,就没必要新建类关联
  • 高效晦涩:应用选择器可实现一些看似只能由JS能力实现的成果,既缩小代码量也缩小JS对DOM的操作,使得交互成果更晦涩

浅谈布局那些事

把握一些罕用布局是一个前端必不可少的技能。养成看设计图就能大略布局出整体布局的前提是必须相熟这些罕用布局的特点与结构。已经需联合很多属性能力实现一个布局,现在在古代属性的加持下能更好地疾速实现各种布局,节约更多工夫去做更重要的事件。

全屏布局

经典的全屏布局顶部底部主体三局部组成,其特点为三局部左右满屏拉伸顶部底部高度固定主体高度自适应。该布局很常见,也是大部分Web利用主体的支流布局。通常应用<header><footer><main>三个标签语义化排版,<main>内还可插入<aside>侧栏或其余语义化标签。

<div class="fullscreen-layout">    <header></header>    <main></main>    <footer></footer></div>
position + left/right/top/bottom

三局部对立申明left:0right:0将其左右满屏拉伸;顶部和底部别离申明top:0bottom:0将其吸顶和吸底,并申明俩高度为固定值;将主体的topbottom别离申明为顶部高度和底部高度。通过相对定位的形式将三局部固定在特定地位,使其互不影响。

.fullscreen-layout {    position: relative;    width: 400px;    height: 400px;    header,    footer,    main {        position: absolute;        left: 0;        right: 0;    }    header {        top: 0;        height: 50px;        background-color: #f66;    }    footer {        bottom: 0;        height: 50px;        background-color: #66f;    }    main {        top: 50px;        bottom: 50px;        background-color: #3c9;    }}
flex

应用flex实现会更简洁。display:flex默认会令子节点横向排列,需申明flex-direction:column扭转子节点排列方向为纵向排列;顶部和底部高度固定,所以主体需申明flex:1让高度自适应。

.fullscreen-layout {    display: flex;    flex-direction: column;    width: 400px;    height: 400px;    header {        height: 50px;        background-color: #f66;    }    footer {        height: 50px;        background-color: #66f;    }    main {        flex: 1;        background-color: #3c9;    }}

<main>需体现成可滚动状态,千万不要申明overflow:auto让容器自适应滚动,这样做有可能因为其余格式化上下文的影响而导致自适应滚动生效或产生其余未知成果。需在<main>内插入一个<div>并申明如下。

div {    overflow: hidden;    height: 100%;}
多列布局
两列布局

经典的两列布局左右两列组成,其特点为一列宽度固定另一列宽度自适应两列高度固定且相等。以下以左列宽度固定和右列宽度自适应为例,反之同理。

<div class="two-column-layout">    <div class="left"></div>    <div class="right"></div></div>
float + margin-left/right

左列申明float:left和固定宽度,因为float使节点脱流,右列需申明margin-left为左列宽度,以保障两列不会重叠。

.two-column-layout {    width: 400px;    height: 400px;    .left {        float: left;        width: 100px;        height: 100%;        background-color: #f66;    }    .right {        margin-left: 100px;        height: 100%;        background-color: #66f;    }}
overflow + float

左列申明同上,右列申明overflow:hidden使其造成BFC区域与外界隔离。BFC相干详情请查看小册第4章盒模型。

.two-column-layout {    width: 400px;    height: 400px;    .left {        float: left;        width: 100px;        height: 100%;        background-color: #f66;    }    .right {        overflow: hidden;        height: 100%;        background-color: #66f;    }}
flex

应用flex实现会更简洁。左列申明固定宽度,右列申明flex:1自适应宽度。

.two-column-layout {    display: flex;    width: 400px;    height: 400px;    .left {        width: 100px;        background-color: #f66;    }    .right {        flex: 1;        background-color: #66f;    }}
三列布局

经典的三列布局左中右三列组成,其特点为间断两列宽度固定残余一列宽度自适应三列高度固定且相等。以下以左中列宽度固定和右列宽度自适应为例,反之同理。整体的实现原理与上述两列布局统一。

<div class="three-column-layout">    <div class="left"></div>    <div class="center"></div>    <div class="right"></div></div>

为了让右列宽度自适应计算,就不应用float + margin-left的形式了,若应用margin-left还得联合左中列宽度计算。

overflow + float
.three-column-layout {    width: 400px;    height: 400px;    .left {        float: left;        width: 50px;        height: 100%;        background-color: #f66;    }    .center {        float: left;        width: 100px;        height: 100%;        background-color: #66f;    }    .right {        overflow: hidden;        height: 100%;        background-color: #3c9;    }}
flex
.three-column-layout {    display: flex;    width: 400px;    height: 400px;    .left {        width: 50px;        background-color: #f66;    }    .center {        width: 100px;        background-color: #66f;    }    .right {        flex: 1;        background-color: #3c9;    }}
圣杯布局与双飞翼布局

经典的圣杯布局双飞翼布局都是由左中右三列组成,其特点为左右两列宽度固定两头一列宽度自适应三列高度固定且相等。其实也是上述两列布局和三列布局的变体,整体的实现原理与上述N列布局统一,可能就是一些细节需注意。

圣杯布局双飞翼布局在大体雷同下也存在一点不同,区别在于双飞翼布局两头列需插入一个子节点。在惯例的实现形式中也是在这个两头列里做文章,如何使两头列内容不被左右列遮挡

  • 雷同

    • 两头列放首位且申明其宽高占满父节点
    • 被挤出的左右列应用floatmargin负值将其拉回与两头列处在同一水平线上
  • 不同

    • 圣杯布局:父节点申明padding为左右列留出空位,将左右列固定在空位上
    • 双飞翼布局:两头列插入子节点并申明margin为左右列让出空位,将左右列固定在空位上

圣杯布局float + margin-left/right + padding-left/right

因为浮动节点在地位上不能高于后面或平级的非浮动节点,否则会导致浮动节点下沉。因而在编写HTML构造时,将两头列节点挪到右列节点前面。

<div class="grail-layout-x">    <div class="left"></div>    <div class="right"></div>    <div class="center"></div></div>
.grail-layout-x {    padding: 0 100px;    width: 400px;    height: 400px;    .left {        float: left;        margin-left: -100px;        width: 100px;        height: 100%;        background-color: #f66;    }    .right {        float: right;        margin-right: -100px;        width: 100px;        height: 100%;        background-color: #66f;    }    .center {        height: 100%;        background-color: #3c9;    }}
双飞翼布局float + margin-left/right

HTML构造大体同上,只是在两头列里插入一个子节点<div>。依据两者区别,CSS申明会与上述圣杯布局有一点点出入,可察看比照找出不同中央。

<div class="grail-layout-y">    <div class="left"></div>    <div class="right"></div>    <div class="center">        <div></div>    </div></div>
.grail-layout-y {    width: 400px;    height: 400px;    .left {        float: left;        width: 100px;        height: 100%;        background-color: #f66;    }    .right {        float: right;        width: 100px;        height: 100%;        background-color: #66f;    }    .center {        margin: 0 100px;        height: 100%;        background-color: #3c9;    }}
圣杯布局/双飞翼布局flex

应用flex实现圣杯布局/双飞翼布局可疏忽上述剖析,左右两列宽度固定,两头列宽度自适应。

<div class="grail-layout">    <div class="left"></div>    <div class="center"></div>    <div class="right"></div></div>
.grail-layout {    display: flex;    width: 400px;    height: 400px;    .left {        width: 100px;        background-color: #f66;    }    .center {        flex: 1;        background-color: #3c9;    }    .right {        width: 100px;        background-color: #66f;    }}
均分布局

经典的均分布局多列组成,其特点为每列宽度相等每列高度固定且相等。总体来说也是最简略的经典布局,因为每列宽度相等,所以很易找到适合的形式解决。

<div class="average-layout">    <div class="one"></div>    <div class="two"></div>    <div class="three"></div>    <div class="four"></div></div>
.one {    background-color: #f66;}.two {    background-color: #66f;}.three {    background-color: #f90;}.four {    background-color: #09f;}
float + width

每列宽度申明为相等的百分比,若有4列则声明width:25%。N列就用公式100 / n求出最终百分比宽度,记得保留2位小数,懒人还可用width:calc(100% / n)主动计算呢。

.average-layout {    width: 400px;    height: 400px;    div {        float: left;        width: 25%;        height: 100%;    }}
flex

应用flex实现会更简洁。节点申明display:flex后,生成的FFC容器里所有子节点的高度都相等,因为容器的align-items默认为stretch,所有子节点将占满整个容器的高度。每列申明flex:1自适应宽度。

.average-layout {    display: flex;    width: 400px;    height: 400px;    div {        flex: 1;    }}
居中布局

居中布局父容器若干个子容器组成,子容器在父容器中横向排列或竖向排列且呈程度居中或垂直居中。居中布局是一个很经典的问题,所以笔者在小册中列举了所有居中布局形式,详情请查看小册第6章布局形式。

在此间接上一个目前最简略最高效的居中形式。display:flexmargin:auto的强行组合,同学们自行领会。

<div class="center-layout">    <div></div></div>
.center-layout {    display: flex;    width: 400px;    height: 400px;    background-color: #f66;    div {        margin: auto;        width: 100px;        height: 100px;        background-color: #66f;    }}

绘制三角的原理

盒模型从实践上来说是一个规范的矩形,很难将其联想到基于盒模型绘制一个三角形。当然存在一个叫clip-path的属性,可绘制三角形,鉴于其兼容性较差通常不会大范畴应用它绘制三角形。

很多同学都会基于盒模型编写三角形,但大部分都是复制粘贴的操作。从原理上正确理解其成因,能力无需复制粘贴就能得心应手地绘制各种三角形。以下从零到一相熟一次绘制三角形的原理。

绘制一个边框别离为四种色彩的正方形。

<div class="triangle"></div>
.triangle {    border: 50px solid;    border-left-color: #f66;    border-right-color: #66f;    border-top-color: #f90;    border-bottom-color: #09f;    width: 200px;    height: 200px;}

别离将widthheight累减到0,发现正方形由四个不同色彩的等腰三角形组成。

.triangle {    border: 50px solid;    border-left-color: #f66;    border-right-color: #66f;    border-top-color: #f90;    border-bottom-color: #09f;    width: 0;    height: 0;}

尝试将左边框色彩申明为通明,会发现左边框暗藏起来。

.triangle {    border: 50px solid;    border-left-color: #f66;    border-right-color: transparent;    border-top-color: #f90;    border-bottom-color: #09f;    width: 0;    height: 0;}

同样原理,将上边框色彩和下边框色彩同时申明为通明,就会失去一个指向左边的三角形。

.triangle {    border: 50px solid;    border-left-color: #f66;    border-right-color: transparent;    border-top-color: transparent;    border-bottom-color: transparent;    width: 0;    height: 0;}

可简写成以下代码。仔细的同学可能还会发现三角形的宽是高的2倍,而高正好是边框宽度border-width。从中可得出一个技巧:若绘制三角形的方向为左右高低,则将四条边框色彩申明为通明且将指定方向的反方向的边框着色,即可失去所需方向的三角形

.triangle {    border: 50px solid transparent;    border-left-color: #f66;    width: 0;    height: 0;}

若绘制左上角、左下角、右上角或右下角的三角形,应用上述技巧就无奈实现了。可略微变通思维,其实指向左上角的三角形是由左边框和上边框组成,其余三角形也是如此。

.triangle {    border: 50px solid transparent;    border-left-color: #f66;    border-top-color: #f66;    width: 0;    height: 0;}

基于上述原理,可得心应手绘制出左右高低、左上角、左下角、右上角和右下角的三角形了,再联合相对定位(position/left/right/top/bottom)、边距(margin/margin-*)或变换(transform)调整地位即可。

.triangle {    border: 50px solid transparent;    width: 0;    height: 0;    &.left {        border-right-color: #f66;    }    &.right {        border-left-color: #f66;    }    &.top {        border-bottom-color: #f66;    }    &.bottom {        border-top-color: #f66;    }    &.left-top {        border-left-color: #f66;        border-top-color: #f66;    }    &.left-bottom {        border-left-color: #f66;        border-bottom-color: #f66;    }    &.right-top {        border-right-color: #f66;        border-top-color: #f66;    }    &.right-bottom {        border-right-color: #f66;        border-bottom-color: #f66;    }}

完满极致的变量

变量又名自定义属性,指可在整个文档中重复使用的值。它由自定义属性--var和函数var()组成,var()用于援用自定义属性。应用变量能带来以下益处。

  • 缩小款式代码的重复性
  • 减少款式代码的扩展性
  • 进步款式代码的灵活性
  • 增多一种CSS与JS的通信形式
  • 不必深层遍历DOM扭转某个款式

同时变量也是浏览器原生个性,无需通过任何转译可间接运行,也是DOM对象,极大便当了CSS与JS间的分割。变量除了具备简洁性和复用性,在重构组件款式时能让代码更易管制,同时还暗藏了一个弱小的技巧,那就是与calc()联合应用。

看看一个简略的例子。一个条形加载条通常由几条线条组成,每条线条对应一个存在不同时延的雷同动画,通过时间差运行雷同动画,从而产生加载成果。预计大部分同学可能会把代码编写成以下模式。

<ul class="strip-loading">    <li></li>    <li></li>    <li></li>    <li></li>    <li></li>    <li></li></ul>
.strip-loading {    display: flex;    justify-content: center;    align-items: center;    width: 200px;    height: 200px;    li {        border-radius: 3px;        width: 6px;        height: 30px;        background-color: #f66;        animation: beat 1s ease-in-out infinite;        & + li {            margin-left: 5px;        }        &:nth-child(2) {            animation-delay: 200ms;        }        &:nth-child(3) {            animation-delay: 400ms;        }        &:nth-child(4) {            animation-delay: 600ms;        }        &:nth-child(5) {            animation-delay: 800ms;        }        &:nth-child(6) {            animation-delay: 1s;        }    }}@keyframes beat {    0%,    100% {        transform: scaleY(1);    }    50% {        transform: scaleY(.5);    }}

剖析代码发现,每个<li>只是animation-delay不同,其余代码则完全相同,换成其余相似的List汇合,那岂不是有10个<li>就写10个:nth-child(n)。显然这种形式不灵便也不易封装成组件,若能像JS那样封装成一个函数,并依据参数输入不同款式成果,那就更棒了。

对于HTML局部的批改,让每个<li>领有一个本人作用域下的变量。对于CSS局部的批改,就需剖析哪些属性是随着index递增而产生法则变动的,对法则变动的局部应用变量表达式代替即可。当然以下<li style="--line-index: n;"></li>可用React JSXVue Template的遍历语法编写。

<ul class="strip-loading">    <li style="--line-index: 1;"></li>    <li style="--line-index: 2;"></li>    <li style="--line-index: 3;"></li>    <li style="--line-index: 4;"></li>    <li style="--line-index: 5;"></li>    <li style="--line-index: 6;"></li></ul>
.strip-loading {    display: flex;    justify-content: center;    align-items: center;    width: 200px;    height: 200px;    li {        --time: calc((var(--line-index) - 1) * 200ms);        border-radius: 3px;        width: 6px;        height: 30px;        background-color: #f66;        animation: beat 1.5s ease-in-out var(--time) infinite;        & + li {            margin-left: 5px;        }    }}@keyframes beat {    0%,    100% {        transform: scaleY(1);    }    50% {        transform: scaleY(.5);    }}

代码中的变量--line-index--time使每个<li>领有一个属于本人的作用域。例如第二个<li>--line-index的值为2,--time的计算值为200ms,换成第三个<li>后这两个值又会不同了。这就是变量的作用范畴所致(在以后节点块作用域及其子节点块作用域下无效)。

calc(var(--line-index) * 200ms)就像一个JS函数,在以后节点的作用域上读取相应的变量,从而计算出具体数值并交由浏览器初始化。从中可得出一个技巧:List汇合里具备与索引递增相干的属性值都可用变量与calc()联合应用生成进去

还记得小学时代学习圆周率的场景吗,已经有学者将一个圆形划分为很多很小的矩形,若这些矩形划分得足够细,那么也可拼在一起变成一个圆形。

将圆形划分为360个小矩形且每个矩形绝对于父容器相对定位,申明transform-origincenter bottom将小矩形的变换基准变更为最底部最两头,每个小矩形依照递增角度顺时针旋转N度,就会造成一个圆形。此时依照递增角度调整小矩形的背景色相,就会看到意想不到的突变成果了。

  • 每个小矩形的递增角度:--:calc(var(--line-index) / var(--line-count) * 1turn)
  • 每个小矩形的背景色相:filter:hue-rotate(var(--))
  • 每个小矩形的旋转角度:transform:rotate(var(--))

若将小矩形的尺寸和数量设置更细更多,整体的突变成果就会更平均。

<ul class="gradient-circular" style="--line-count: 360;">    <li style="--line-index: 1;"></li>    ...    <li style="--line-index: 360;"></li>    <!-- 360个<li>,可用模板语法生成  --></ul>
.gradient-circular {    position: relative;    width: 4px;    height: 200px;    li {        --: calc(var(--line-index) / var(--line-count) * 1turn);        position: absolute;        left: 0;        top: 0;        width: 100%;        height: 100%;        background-color: #3c9;        filter: hue-rotate(var(--));        transform-origin: center bottom;        transform: rotate(var(--));    }}

添枝加叶的伪元素

有时为了实现某个成果而往页面里重复增加标签变得很繁琐,增加太多标签反而不好解决而变得难以保护。此时会引入伪元素这个概念解决上述问题。正是伪元素能解决一些可不增加其余标签而起到占位作用,笔者才称伪元素“添枝加叶”

上述选择器分类有提及伪元素,广义上来说选择器除了伪元素,其余都是伪类伪元素伪类尽管都是选择器,但它们还是存在一丝丝的差异。

  • 伪元素通常是一些实体选择器,抉择满足指定条件的DOM,例如::selection::first-letter::first-line
  • 伪类通常是一些状态选择器,抉择处于特定状态的DOM,例如:hover:focus:checked

伪元素指页面里不存在的元素。伪元素在HTML代码里未声明却能失常显示,在页面渲染时看到这些原本不存在的元素施展着重要作用。:before:after是两个很重要的伪元素,早在CSS2就呈现了。

起初伪元素的前缀应用单冒号语法。随着CSS改革,伪元素的前缀被批改成双冒号语法:before/:after从此变成::before/::after,用来辨别伪类,未提及的伪元素同理。若兼容低版本浏览器,还需应用:before:after

两者最次要的区别就是伪类应用单冒号语法伪元素应用双冒号语法。当然笔者还是提倡同学们应用单冒号语法标记伪类,应用双冒号语法标记伪元素,这样在代码模式上就能一眼辨别进去。

::before::after的应用场景很多,也是笔者着重钻研的技巧之一。::before/::after必须联合content应用,通常用作润饰节点,为节点插入一些多余的货色,但又不想内嵌一些其余标签。若插入2个以下(蕴含2个)的润饰,倡议应用::before/::after

说时迟那时快,立马联合上述绘制三角形的原理绘制一个罕用的气泡对话框,圆滚滚的身子带上一个三角形的尾巴。气泡对话框的身板就是一个圆角矩形,可用<div>间接绘制,小尾巴是一个三角形,可用::after占位并绘制。这样就无需在<div>里增加一个<i>绘制小尾巴了。

<div class="bubble-box">iCSS</div>
.bubble-box {    position: relative;    border-radius: 5px;    width: 200px;    height: 50px;    background-color: #f90;    line-height: 50px;    text-align: center;    font-size: 20px;    color: #fff;    &::after {        position: absolute;        left: 100%;        top: 50%;        margin-top: -5px;        border: 5px solid transparent;        border-left-color: #f90;        content: "";    }}

从中可得出一个技巧:若为节点做一些润饰却不想插入其余标签,可用::before和::after代替,但实用于2个占位以下。其实这个也不算什么特地技巧,只是很多同学都不会去留神这种用法,有需要都是间接增加标签。兴许以下提及的障眼法内容插入会让同学们对伪元素另眼相看。

灵便多变的障眼法

上述应用::after简略地绘制气泡对话框的尾巴,然而简单一点的带边框气泡对话框是否也应用伪元素绘制呢。看到这里先不要往下看代码,自行思考1分钟想想实现办法。

答案当然是可行的。以下是整个带边框气泡对话框的拆解,整体由三局部组成:带边框圆角矩形、彩色三角形、橙色三角形。先将两个三角形错位叠加生成一个箭头状的图形,再将该图形叠加到带边框圆角矩形的左边,最初将彩色三角形着色成红色,就能失去上图的带边框气泡对话框了。

<div class="bubble-empty-box">iCSS</div>
.bubble-empty-box {    position: relative;    border: 2px solid #f90;    border-radius: 5px;    width: 200px;    height: 50px;    line-height: 46px;    text-align: center;    font-size: 20px;    color: #f90;    &::before {        position: absolute;        left: 100%;        top: 50%;        margin: -5px 0 0 2px;        border: 5px solid transparent;        border-left-color: #f90;        content: "";    }    &::after {        position: absolute;        left: 100%;        top: 50%;        margin-top: -4px;        border: 4px solid transparent;        border-left-color: #fff;        content: "";    }}

整体实现思路就是一种障眼法,正确来说就是将图形错位叠加产生另一种成果,在平面设计中叫做占位叠加。有了这种设计思维,其实能应用CSS发明出很多动向不到的障眼法成果。

当你遇见心仪妹纸时,心里噗通噗通地跳动,此时此刻可用纯CSS描述你的情绪。应用单个<div>联合::before::after,通过错位叠加的形式生成一个心形。在叠加前看看以下图形,是不是发现很像米老鼠呢。

  • 申明<div>形态为正方形并以核心顺时针旋转45deg
  • 申明::before::after继承<div>尺寸并别离相对定位到左上角和右上角
  • 申明::before::after的圆角率为100%

<div class="heart-shape"></div>
.heart-shape {    position: relative;    width: 200px;    height: 200px;    background-color: #f66;    transform: rotate(45deg);    &::before,    &::after {        position: absolute;        left: 0;        top: 0;        border-radius: 100%;        width: 100%;        height: 100%;        background-color: #f66;        content: "";    }    &::before {        transform: translateX(-50%);    }    &::after {        transform: translateY(-50%);    }}

最初奇妙利用transform::before::after平移到相应地位产生叠加错觉。这时别离对::before::after着色,看看其中的神秘。

在这个根底上来一个更高级的玩法,增加突变成果让心形变得更么么哒。

  • 申明<div>从上到下(实际效果是从右上角到左下角)突变着色
  • 因为::before从旋转后的<div>X轴往左平移过来,所以其着色成果与<div>统一
  • 因为::after从旋转后的<div>Y轴往上平移过来,所以其中线地位突变着色必须与<div>顶部突变着色的色彩统一(具体往下剖析)

整体突变成果的重点在::after上,因为::after下半部叠加在<div>上,所以下半部色彩必须通明,上半部底部(中线地位)突变着色必须与<div>顶部突变着色的色彩统一,这样能力做到无缝连接。通过Windows零碎MacOS零碎的测试,在Windows零碎下的通明突变地位需在51%的中央开始,这与屏幕设施的分辨率和广色域无关。

最初为了让突变心形看起来更具立体感,给它绘制个暗影吧。若感觉这个突变动感心形很美,可顺手转发给女友哇!

<div class="gradient-heart-shape"></div>
.gradient-heart-shape {    position: relative;    width: 200px;    height: 200px;    background-image: linear-gradient(to bottom, #09f, #f66);    box-shadow: 0 0 20px rgba(#000, .8);    transform: rotate(45deg);    &::before,    &::after {        position: absolute;        left: 0;        top: 0;        border-radius: 100%;        width: 100%;        height: 100%;        content: "";    }    &::before {        background-image: linear-gradient(to bottom, #09f, #f66);        transform: translateX(-50%);    }    &::after {        background-image: linear-gradient(to bottom, #3c9, #09f 50%, transparent 50%, transparent);        transform: translateY(-50%);    }}

动向不到的内容插入

上述提到::before/::after必须联合content应用,那么content就真的只能插入一般字符串吗?content何止这么简略,以下推广几种少见但弱小的内容插入技巧。通过这几种技巧,就能很不便地将读取到的数据动静插入到::before::after中。

  • 内容拼接
  • 联合attr()应用
  • 联合变量计数器应用
内容拼接

惯例操作是content:"CSS",也可拼接多个字符串,有些同学可能第一工夫想起content:"Hello "+"CSS"。托付,这不是JS而是CSS,CSS字符串拼接当然有本人的规定。CSS字符串拼接既不能应用+相连也可不必空格距离。

.elem {    content: "Hello ""CSS"; // 等价于"Hello " "CSS"    content: "Hello" attr(data-name); // 与attr()拼接    content: counter(progress) "%"; // 与counter()拼接}
联合attr()应用

attr()是一个被疏忽的选择器,它有着弱小的属性捕捉性能。有这么一个场景,一个数据汇合需遍历到每个DOM上并把某个字段插入到其::after上。这该怎么办,如同95%的同学都不会应用JS获取节点的::before::after。这时attr()就派上用场了。

<li v-for="v in list" :key="v.id" :data-name="v.name">
li::after {    content: attr(data-name);}

一行CSS代码搞掂,还用什么JS去获取节点的::after呢。当然contentattr()的应用场景不止那一点。

:hover作用于鼠标悬浮的节点,是一个很好用的选择器。在特定场景可代替mouseentermouseleave两个鼠标事件,加上transtion让节点的动画更丝滑。联合attr()有一个很好用的场景,就是鼠标悬浮在某个节点上显示提醒浮层,提醒浮层里蕴含着该动作的文本。

  • 给节点标记一个用户属性data-*
  • 当鼠标悬浮在该节点上触发:hover
  • 通过attr()获取data-*的内容
  • data-*的内容赋值到伪元素content

<ul class="hover-tips">    <li data-name="姨妈红"></li>    <li data-name="基佬紫"></li>    <li data-name="箩底橙"></li>    <li data-name="姣婆蓝"></li>    <li data-name="大粪青"></li>    <li data-name="原谅绿"></li></ul>
$color-list: #f66 #66f #f90 #09f #9c3 #3c9;.hover-tips {    display: flex;    justify-content: space-between;    width: 200px;    li {        position: relative;        padding: 2px;        border: 2px solid transparent;        border-radius: 100%;        width: 24px;        height: 24px;        background-clip: content-box;        cursor: pointer;        transition: all 300ms;        &::before,        &::after {            position: absolute;            left: 50%;            bottom: 100%;            opacity: 0;            transform: translate3d(0, -30px, 0);            transition: all 300ms;        }        &::before {            margin: 0 0 12px -35px;            border-radius: 5px;            width: 70px;            height: 30px;            background-color: rgba(#000, .5);            line-height: 30px;            text-align: center;            color: #fff;            content: attr(data-name);        }        &::after {            margin-left: -6px;            border: 6px solid transparent;            border-top-color: rgba(#000, .5);            width: 0;            height: 0;            content: "";        }        @each $color in $color-list {            $index: index($color-list, $color);            &:nth-child(#{$index}) {                background-color: $color;                &:hover {                    border-color: $color;                }            }        }        &:hover {            &::before,            &::after {                opacity: 1;                transform: translate3d(0, 0, 0);            }        }    }}
联合变量计数器应用

当初来玩高级一点的货色,先不做任何铺垫,接着往下看即可,反正就是content联合变量计数器的应用场景。

笔者想做一个实时显示进度的悬浮球,跟着笔者一起敲代码吧。先画一个绿油油的波波。

<div class="state-ball">    <div class="wave"></div></div>
.state-ball {    overflow: hidden;    position: relative;    padding: 5px;    border: 3px solid #3c9;    border-radius: 100%;    width: 150px;    height: 150px;    background-color: #fff;    .wave {        position: relative;        border-radius: 100%;        width: 100%;        height: 100%;        background-image: linear-gradient(to bottom, #af8 13%, #3c9 91%);    }}

进度通常都是从底部往顶部逐步晋升,可用::before绘制一个圆形遮罩层,进度变动时将遮罩层始终往上晋升产生障眼成果。晋升过程可用相对定位将遮罩层固定在底部,通过调整margin-bottom平移遮罩层。

为了不便演示,正文父容器的overflow:hidden,通过Chrome Devtools微调margin-bottom看看整体成果。后续记得将overflow:hidden申明回来。

.state-ball {    // overflow: hidden;    // ...    &::before {        position: absolute;        left: 50%;        bottom: 5px;        z-index: 9;        margin-left: -100px;        margin-bottom: 0;        border-radius: 100%;        width: 200px;        height: 200px;        background-color: #09f;        content: "";    }    // ...}

为了让晋升过程出现动态效果,调整::before的背景色彩和圆角率并追加一个旋转动画。

.state-ball {    // ...    &::before {        position: absolute;        left: 50%;        bottom: 5px;        z-index: 9;        margin-left: -100px;        margin-bottom: 0;        border-radius: 45%;        width: 200px;        height: 200px;        background-color: rgba(#fff, .5);        content: "";        animation: rotate 10s linear -5s infinite;    }    // ...}@keyframes rotate {    to {        transform: rotate(1turn);    }}

为了让波浪出现平面成果,追加::after占位并申明整体款式与::before统一,在背景色彩、圆角率和动画时延上略有差别即可。另外申明::aftermargin-bottom略微比::before高一点,这样在旋转过程中能让波浪产生动静的平面成果。

在晋升过程中,两个遮罩层位移间隔应该是统一的,所以可用变量计算公式示意且::after::before10px。在这里有个值得注意的中央,若变量联合calc()应用,其后果必须带上单位,以这两条公式为例,其变量初始值必须为--offset:0px,不能为--offset:0

  • ::beforemargin-bottom:var(--offset)
  • ::aftermargin-bottom:calc(var(--offset) + 10px)

<div class="state-ball" style="--offset: 0px;">    <div class="wave"></div></div>
.state-ball {    // ...    &::before,    &::after {        position: absolute;        left: 50%;        bottom: 5px;        z-index: 9;        margin-left: -100px;        width: 200px;        height: 200px;        content: "";    }    &::before {        margin-bottom: var(--offset);        border-radius: 45%;        background-color: rgba(#fff, .5);        animation: rotate 10s linear -5s infinite;    }    &::after {        margin-bottom: calc(var(--offset) + 10px);        border-radius: 40%;        background-color: rgba(#fff, .8);        animation: rotate 15s infinite;    }    // ...}// ...

到此再优化一些细节,通过Chrome Devtools查看.wave得悉其尺寸为134x134,每次往上平移两个伪元素只能1px那样递增。当初想将其平移100次就能填充整个球体,那么就需依照134/100这个比例革新变量计算公式。

--offset申明为--offset:0,取值区间在0~100而不是0px~100px

  • ::beforemargin-bottom:calc(var(--offset) * 1.34px)
  • ::aftermargin-bottom:calc(var(--offset) * 1.34px + 10px)

<div class="state-ball" style="--offset: 0;">    <div class="wave"></div></div>
.state-ball {    // ...    &::before {        margin-bottom: calc(var(--offset) * 1.34px)        // ...    }    &::after {        margin-bottom: calc(var(--offset) * 1.34px + 10px);        // ...    }    // ...}// ...

当初已把位移间隔管制在0~100的比例了,那么剩下步骤就是追加一个<div>,应用其content寄存在offset实时显示进度了。

<div class="state-ball" style="--offset: 0;">    <div class="wave"></div>    <div class="progress"></div></div>
.state-ball {    // ...    .progress::after {        display: flex;        position: absolute;        left: 0;        top: 0;        z-index: 99;        justify-content: center;        align-items: center;        width: 100%;        height: 100%;        font-weight: bold;        font-size: 16px;        color: #09f;        content: var(--offset) "%";    }}// ...

可是发现无任何文本成果。状况是这样的,若变量是字符串类型可间接显示,若变量是数值类型则需借助counter()显示。而counter()还需应用counter-reset初始默认值,CSS计数器怎么用在这里就不解说了,感兴趣的同学可自行百度。

整体革新工程就这样实现了,残缺代码如下。最初通过JS操作变量--offset就能实时扭转进度了。

<div class="state-ball" style="--offset: 0;">    <div class="wave"></div>    <div class="progress"></div></div>
.state-ball {    overflow: hidden;    position: relative;    padding: 5px;    border: 3px solid #3c9;    border-radius: 100%;    width: 150px;    height: 150px;    background-color: #fff;    &::before,    &::after {        position: absolute;        left: 50%;        bottom: 5px;        z-index: 9;        margin-left: -100px;        width: 200px;        height: 200px;        content: "";    }    &::before {        margin-bottom: calc(var(--offset) * 1.34px);        border-radius: 45%;        background-color: rgba(#fff, .5);        animation: rotate 10s linear -5s infinite;    }    &::after {        margin-bottom: calc(var(--offset) * 1.34px + 10px);        border-radius: 40%;        background-color: rgba(#fff, .8);        animation: rotate 15s infinite;    }    .wave {        position: relative;        border-radius: 100%;        width: 100%;        height: 100%;        background-image: linear-gradient(to bottom, #af8 13%, #3c9 91%);    }    .progress::after {        display: flex;        position: absolute;        left: 0;        top: 0;        z-index: 99;        justify-content: center;        align-items: center;        width: 100%;        height: 100%;        font-weight: bold;        font-size: 16px;        color: #09f;        content: counter(progress) "%";        counter-reset: progress var(--offset);    }}@keyframes rotate {    to {        transform: rotate(1turn);    }}

无所不能的模仿点击事件

:checked作用于选项选中的表单节点,当<input>type设置成radiocheckbox时可用。很多同学都会应用input:checked + div {}input:checked ~ div {}的操作模仿鼠标点击事件。要让input:checked + div {}input:checked ~ div {}起效,其HTML构造必须像以下那样。

<input type="radio"><div></div>

这样就无奈拆散构造与行为了,导致CSS必须跟着HTML走,只能应用相对定位将<input>固定到指定地位。应用<label>绑定<input>可将<input>的鼠标抉择事件转移到<label>上,由<label>管制选中状态。那么HTML构造可改为以下那样,此时的<input>可设置hidden暗藏起来,不参加任何排版。

<input type="radio" id="btn" hidden><div>    <label for="btn"></div>

<input>应用id<label>应用for关联起来,而hidden使<input>暗藏起来,不占用页面任何地位,此时<label>搁置在页面任何地位都行。

input:checked + div {}input:checked ~ div {}

有了这样的思路,就很易实现一个纯CSS标签导航了。

<div class="tab-navbar">    <input id="tab1" type="radio" name="tabs" hidden checked>    <input id="tab2" type="radio" name="tabs" hidden>    <input id="tab3" type="radio" name="tabs" hidden>    <input id="tab4" type="radio" name="tabs" hidden>    <nav>        <label for="tab1">题目1</label>        <label for="tab2">题目2</label>        <label for="tab3">题目3</label>        <label for="tab4">题目4</label>    </nav>    <main>        <ul>            <li>内容1</li>            <li>内容2</li>            <li>内容3</li>            <li>内容4</li>        </ul>    </main></div>
.active {    background-color: #3c9;    color: #fff;}.tab-navbar {    display: flex;    overflow: hidden;    flex-direction: column-reverse;    border-radius: 10px;    width: 300px;    height: 400px;    input {        &:nth-child(1):checked {            & ~ nav label:nth-child(1) {                @extend .active;            }            & ~ main ul {                background-color: #f66;                transform: translate3d(0, 0, 0);            }        }        &:nth-child(2):checked {            & ~ nav label:nth-child(2) {                @extend .active;            }            & ~ main ul {                background-color: #66f;                transform: translate3d(-25%, 0, 0);            }        }        &:nth-child(3):checked {            & ~ nav label:nth-child(3) {                @extend .active;            }            & ~ main ul {                background-color: #f90;                transform: translate3d(-50%, 0, 0);            }        }        &:nth-child(4):checked {            & ~ nav label:nth-child(4) {                @extend .active;            }            & ~ main ul {                background-color: #09f;                transform: translate3d(-75%, 0, 0);            }        }    }    nav {        display: flex;        height: 40px;        background-color: #f0f0f0;        line-height: 40px;        text-align: center;        label {            flex: 1;            cursor: pointer;            transition: all 300ms;        }    }    main {        flex: 1;        ul {            display: flex;            flex-wrap: nowrap;            width: 400%;            height: 100%;            transition: all 300ms;        }        li {            display: flex;            justify-content: center;            align-items: center;            flex: 1;            font-weight: bold;            font-size: 20px;            color: #fff;        }    }}

笔者已经发表过一篇《纯CSS收费让掘金社区领有暗黑模式切换性能》,探讨了:checked+/~filter的玩法,详情请查看原文,在此就不啰嗦了。

总结

来不及分享的内容,就用文章叙述完,那天看直播的掘友们,让你们久等了。这几年花了很多工夫钻研CSS,兴许写完本文就要对CSS告一段落了。尽管花了很多工夫钻研CSS,但也公布了几篇爆款CSS文章和一本CSS掘金小册,也算是留下了这几年的CSS学习成绩了。

喜爱做的事件总不想留什么遗憾。接下来也要将钻研方向转移到JS上了,还是会像钻研CSS那样认真钻研JS的性能优化设计模式数据算法三大装逼套件。冀望在2021年能有新的冲破吧,也感激掘金社区让我学习到他人的常识和他人学习到我的常识。

分享源码寄存在笔者的Github上,有须要的同学可拷贝一份。还有就是笔者向可恶丑陋的小册姐姐要了100份玩转CSS的艺术之美六折优惠码WmOrR0hR,对该小册感兴趣的同学可理解一下哟!

CSS是一门天马行空的语言,说它简略也行说它艰难也行。想理解更多纯CSS特效,可回看笔者往期文章,也可浏览笔者集体官网的纯CSS特效专辑,保障满足你的眼球。

  • 灵活运用CSS开发技巧:4200+点赞量,125k+浏览量
  • 妙用CSS变量,让你的CSS变得更心动:500+点赞量,15k+浏览量
  • 纯CSS收费让掘金社区领有暗黑模式切换性能:100+点赞量,5k+浏览量

结语

❤️关注+点赞+珍藏+评论+转发❤️,原创不易,激励笔者创作更多高质量文章

关注公众号IQ前端,一个专一于CSS/JS开发技巧的前端公众号,更多前端小干货等着你喔