从简单实例入手css动画与特效(一)

注意:本文章所有阶段皆有demo预览,可见每章最后一节“源码及预览”。

本篇博文主要是通过一些实例来学习用css实现一些动画及特殊效果。

在以下的内容中,可能会用到以下知识点: transformtransitionanimation、景深等。首先简单介绍一下这些知识点。然后在后面的实例中会详细的介绍这些知识点。

知识点

transform

transform可以对元素进行移动、旋转、缩放、拉伸等转换。具体的用法可以参照2D转换3D转换

transition

transition用来给元素的变换添加一些效果。具体的用法可以参照过渡

animation

animation用来给元素添加动画。使用 @keyframes 规则。具体的用法可以参照动画

景深

景深就是肉眼距离屏幕的距离,将当前容器变成3D的,让本来的皮影戏变成舞台剧。景深设置的越大,元素离我们越远,反之则是离我们越近。用法如下。

<div class="box">
<div></div>
</div>
<style>
.box {
perspective: 500px;
}
</style>

浏览器坐标轴

这里以一个正方形的div为例。当一个元素进行一些2D或者3D变换时,作用的坐标轴的中心点就是这个元素的中心。需要注意的是,当元素进行变换时,其坐标轴会跟随移动,始终保持在元素中心。

浏览器坐标轴

一些实例

旋转的正方体

舞台

首先我们给正方体搭建一个舞台,让它有机会可以展示自己。

<div class="container">
<div class="cube-box">
</div>
</div>

搭建舞台的核心是设置景深,以及让子元素在3D空间内变换,因为正方体是一个三维图案,需要借助景深将屏幕变成一个三维空间。这里需要注意的是,设置景深和变换类型需要设在不同的元素上,景深在父元素上,变换类型在子元素上。添加如下css代码。

.container {
position: relative;
margin: 20px auto;
width: 500px;
height: 500px;
background: radial-gradient(rgba(0, 0, 0, 0) 10%, rgba(0, 0, 0, .5) 100%);
/* 设置景深 */
perspective: 500px;
}

.cube-box {
position: absolute;
top: 200px;
left: 200px;
width: 100px;
height: 100px;
line-height: 100px;
text-align: center;
/* 让所有的元素在3D空间中呈现 */
transform-style: preserve-3d;
}

这样,一个简易舞台就搭建起来了,当然,在没有添加任何“演员”之前,这个舞台看不出来任何特殊之处。

坐标轴

在添加“演员”之前,为了后面方便理解,我们先构建出一个坐标轴(包含xyz轴)。

<div class="container">
<div class="cube-box">
<div class="x-axis"></div>
<div class="y-axis"></div>
<div class="z-axis"></div>
</div>
</div>

然后分别添加三个轴的样式。首先是x轴。

.x-axis {
position: absolute;
top: 50%;
left: 50%;
margin-left: -100px;
margin-top: -1px;
width: 200px;
height: 1px;
background: #000;
}

.x-axis::before {
content: '>';
display: block;
position: absolute;
width: 16px;
height: 16px;
line-height: 16px;
font-size: 12px;
text-align: center;
right: -7px;
top: -9px;
}

.x-axis::after {
content: 'x';
display: block;
position: absolute;
width: 16px;
height: 16px;
line-height: 16px;
font-size: 12px;
text-align: center;
right: 4px;
top: -16px;
}

这里通过position将x轴定位在了屏幕的正中心,并且通过两个伪元素给坐标轴添加了标识。同样的方式来添加一下y轴的样式。

.y-axis {
background: #000;
width: 1px;
height: 200px;
position: absolute;
top: 50%;
left: 50%;
margin-top: -100px;
margin-left: -1px;
}

.y-axis::before {
content: '>';
display: block;
position: absolute;
width: 16px;
height: 16px;
line-height: 16px;
font-size: 12px;
text-align: center;
right: -8.5px;
bottom: -7px;
transform: rotateZ(90deg);
}

.y-axis::after {
content: 'y';
display: block;
position: absolute;
width: 16px;
height: 16px;
line-height: 16px;
font-size: 12px;
text-align: center;
right: -14px;
bottom: 6px;
}

在这里用到了transform: rotateZ(90deg)来实现将>旋转90°达到标向y轴正轴方向的目的。我们来学习一下rotate()

{
/* 正值是顺时针旋转,反之逆时针旋转 */
transform: rotate(45deg); /* 定义 2D 旋转,在参数中规定角度。这里是顺时针旋转45度。也就是绕z轴旋转 */
transform: rotate3d(1, -1, 0, 45deg); /* 定义 3D 旋转。前三个值分别代表在三个方向上进行旋转的矢量值,可以是0到1之间的值(依次为x,y,z),第四个值代表旋转的角度。 */
transform: rotateX(45deg); /* 定义沿 X 轴的 3D 旋转 */
transform: rotateY(45deg); /* 定义沿 Y 轴的 3D 旋转 */
transform: rotateZ(45deg); /* 定义沿 Z 轴的 3D 旋转 */
}

注意rotate3d(x,y,z,angle)前三个值的范围是0到1,但是实际上可以是任意数值,这个数值会使该方向上旋转的角度发生变化,但是不是单纯的矢量值与角度的乘积,而是有一个特殊的变化弧度。

最后是z轴的样式。

.z-axis {
background: #000;
width: 200px;
height: 1px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -100px;
margin-top: -1px;
transform: rotateY(90deg);
}

.z-axis::before {
content: '<';
display: block;
position: absolute;
width: 16px;
height: 16px;
line-height: 16px;
font-size: 12px;
text-align: center;
left: -8px;
top: -9px;
}

.z-axis::after {
content: 'z';
display: block;
position: absolute;
width: 16px;
height: 16px;
line-height: 16px;
font-size: 12px;
text-align: center;
left: 4px;
top: -16px;
}

z轴上用到了transform: rotateY(90deg)将坐标轴放到正确的方向上。需要注意的是当z轴到正确的方向上时,我们是看不到z轴的,需要先将他的伪元素样式调好再进行旋转。

下面我们进入正题,正式开始画正方体。

六个面

首先我们先画出正方体的六个面。

<div class="container">
<div class="cube-box">
<div class="item front">front</div>
<div class="item behind">behind</div>
<div class="item left">left</div>
<div class="item right">right</div>
<div class="item top">top</div>
<div class="item down">down</div>
<div class="x-axis"></div>
<div class="y-axis"></div>
<div class="z-axis"></div>
</div>
</div>

然后给这六个面添加样式,因为是相同的六个面,所以可以使用同一套样式。添加如下CSS代码。

.item {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #fff;
opacity: 0.6;
}

添加完样式看效果,发现正方体的6个面都已经堆叠在一起了,这时候应该让他们各自归位了,首先先看正面和背面。

正面和背面都在xy平面上,也就是肉眼看到的这一面。其中正面是不需要动的,只需要安静的呆在你的面前就可以了。而背面需要向z轴负半轴方向移动正方体的边长的长度。为了方便四个侧面的处理,这里将正面向z轴正半轴的方法移动半个边长的长度,背面向z轴负半轴方向移动半个边长的长度。元素的移动就需要用到 transformtranslate() 方法。

{
transform: translate(50px, -50px); /* 定义 2D 转换。两个值依次表示在x轴上移动50px的距离,在y轴上移动-50px的距离。当只写一个值时,默认在y轴上不移动 */
transform: translate3d(50px, 50px, 50px); /* 定义 3D 转换。在三个方向上均移动50px的距离。3个值分别为x轴、y轴、z轴 */
transform: translateX(50px); /* 3D转换。在x轴方向上移动 */
transform: translateY(50px); /* 3D转换。在y轴方向上移动 */
transform: translateZ(50px); /* 3D转换。在z轴方向上移动 */
}

添加如下CSS代码。

.front {
transform: translateZ(50px);
}
.behind {
transform: translateZ(-50px);
}

因为他们的中心坐标始终都在同一个XY坐标点上。所以这时候只能透过半透明的 front 看到后面“变小”的其他面。

现在我们来处理左边和右边这两面,这两个面在yz平面上。发挥你的想象力想象一下,他们两个面在xy面上都需要绕Y轴进行旋转才能到yz面上去,也就是要用到 transformrotate() 方法。而为了让其能够正常的旋转到正确的位置上,还需要利用 translate 将其旋转点移动到两边。添加如下css代码。

.left {
/* 先将 left 在x轴上向左移动半个边长的长度,使 left 的中心点在 front 的左边边线上,然后将其旋转90度,使其与 front 和 behind 垂直,逆时针旋转,使其正面朝外 */
/* 需要注意的是,当使用多个 transform 方法时,需要注意其顺序,不同的顺序会导致不同的结果。 */
transform: translateX(-50px) rotateY(90deg);
}
.right {
transform: translateX(50px) rotateY(-90deg);
}

同样的思想,让我们处理一下 topdown 这两个面。这两个面为了出现在上面和下面,也就是xz平面上,需要绕x轴旋转90度。添加如下css代码。

.top {
/* 先将 top 在y轴上向左移动半个边长的长度,使 top 的中心点在 front 的上边边线上,然后将其旋转90度,使其与 front 和 behind 垂直,逆时针旋转,使其正面朝外 */
transform: translateY(-50px) rotateX(90deg);
}
.down {
transform: translateY(50px) rotateX(-90deg);
}

这个时候一个正方体就已经成型了,可是还是只能看到一个面啊,那我们就让其动起来。

正方体旋转动画

这里默认你已经了解了上面我所说的 animation 相关的知识点了。

首先我们定义一个旋转的动画,控制不同阶段的不同旋转角度,命名为 cubeRotating

@keyframes cubeRotating {
0% {
transform: rotate(0deg);
}
25% {
transform: rotateX(180deg);
}
50% {
transform: rotateX(360deg) rotateY(180deg);
}
75% {
transform: rotateY(360deg) rotateZ(180deg);
}
100% {
transform: rotateZ(360deg);
}
}

这里定义了5个阶段的状态。0% 和 100% 都是初始状态。中间的几个阶段分别进行了不同程度和方向上的旋转。需要注意的是,在每个阶段完成后添加下个阶段的变换时,都需要以变换后的新坐标系为基准添加。

前面我们定义了正方体外壳 div 的位置,其中心点刚好是正方体的正中心。给其添加此动画。

.cube-box {
/*
以数字表示 animation 第几个属性
1: 动画名称
2: 动画持续时长,即完成一个周长所需的时间,默认为0
3: 动画的速度曲线,默认为ease,详细的曲线介绍可以参考[速度曲线](https://www.runoob.com/cssref/css3-pr-animation-timing-function.html)
4: 动画的延迟执行时间,只会在第一次执行时延迟,多次执行间无延迟
5: 动画的执行次数,可以是数字或者 infinite(无限次)
*/
animation: cubeRotating 6s linear 1s infinite;
}

源码&预览

预览:

源码:GITHUB

赛博朋克的按钮特效

无意间发现赛博朋克2077的官网中的“现已发售”按钮的特效与游戏中的特效差不多,非常具有科幻感,于是乎就研究了一下这种特效。

按钮

首先我们做一个类似的按钮。比较简单,就直接放代码了。

<div class="btn">
<span class="btn-text">现已发售</span>
</div>
.btn {
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin: 100px 0 0 265px;
width: 337px;
height: 85px;
font-size: 26px;
line-height: 1;
font-weight: 700;
color: #fff;
letter-spacing: 2px;
background: linear-gradient(45deg, transparent, transparent 5%, #ff003c 5%, #ff003c);
cursor: pointer;
}

官网对于按钮背景使用的是图片,这里通过线性渐变实现了类似的效果。只要使两个颜色的位置相同,就会产生这样割裂的效果。

clip-path

在开始做特效之前,我们先看看这个css属性:clip-path

clip-path CSS 属性使用裁剪方式创建元素的可显示区域。区域内的部分显示,区域外的隐藏。

从简介来看,这个属性是用来裁剪元素的,通过控制可显示区域来达到画画的效果,类似于svg中的<clipPath>

他可以接收以下几种类型的值。

clip-path: <clip-source> | [ <basic-shape> || <geometry-box> ] | none

其中

<clip-source> = <url>
<basic-shape> = <inset()> | <circle()> | <ellipse()> | <polygon()> | <path()>
<geometry-box> = border-box | padding-box | content-box | margin-box | fill-box | stroke-box | view-box

<clip-source>用 <url> 表示剪切元素的路径。
<basic-shape>是一种形状,其大小和位置由<几何盒>值定义。如果没有指定几何框,则边框将用作参考框。
<geometry-box>如果同 <basic-shape> 一起声明,它将为基本形状提供相应的参考框盒。通过自定义,它将利用确定的盒子边缘包括任何形状边角(比如说,被 border-radius 定义的剪切路径)

下面对其详细的值做一个介绍。

值类型 说明
none 默认值。不裁剪
circle([r]? [at x y]?) 定义一个圆形剪切路径,其中r为半径,支持百分比,x、y为圆心坐标
ellipse([xr yr]? [at x y]?) 定义一个椭圆形剪切路径,其中xr为x轴半径,yr为y轴半径,支持百分比,x、y为圆心坐标
inset(top right bottom left [round radius]?) 定义一个矩形,top、right、bottom和left分别为距离四个边的距离,可缩写。redius为圆角值,与border-redius值写法一致
polygon(<fill-rule>?, [x, y]#) 定义一个多边形,fill-rule为填充规则,可选值有nonzero和evenodd,默认值是nonzero。x、y为多边形顶点坐标
path([<fill-rule>,]? <string>) 定义一段路径,fill-rule为填充规则,string为一个字符串,内容为路径点
margin-box 使用 margin box 作为引用框
padding-box 使用 padding box 作为引用框
content-box 使用 content box 作为引用框
border-box 使用 border box 作为引用框
fill-box 利用对象边界框作为引用框。
stroke-box 使用笔触边界框(stroke bounding box)作为引用框。
view-box 使用最近的 SVG 视口(viewport)作为引用框。

简单地说就是,可以直接引入外部svg资源来画路径;或者自己通过预设的方案画一个路径,同时标注其作用的区域。

官方的介绍还是不太清楚,我们通过分别用几个例子来展示这几个值的用法。

circle()

下面的代码是几种不同的携带参数的效果的展示。最后一个是利用此方法实现了一个简单的动画。

注意:当未携带参数时,半径为较短边的边长,效果等同于border-radius:50%;

ellipse()

下面的代码是几种不同的携带参数的效果的展示。最后一个是利用此方法实现了一个简单的动画。

注意:当未携带参数时,两个半径分别为所在方向的边的边长的一半。

inset()

下面的代码是几种不同的携带参数的效果的展示。最后一个是利用此方法实现了一个简单的动画。

注意:当未携带参数时,效果等同于参数为0,不裁剪。

polygon()

下面的代码是几种不同的携带参数的效果的展示。最后一个是利用此方法实现了一个简单的动画。多边形做动画时,需前后顶点的个数一致,不然动画无法正确执行。

注意:当未携带参数时,不裁剪。

path()

下面的代码只展示了一个。这是因为目前我也没完全的学会去自己画路径,后面如果学的差不多了再来补充。想要学习这方面内容可以看最后一节我列出的文章,或者直接搜索svg的path详解,二者说一样的。

<geometry-box>

所有的浏览器对geometry-box的支持都不够,现在没有一个浏览器可以看出来效果,我们只需要知道默认值是padding-box即可。

源码及预览

预览:

源码:GITHUB

对于上一小节通过渐变实现的缺角按钮也可以通过这种方式来实现。下面我们用clip-path来试着实现一下缺角按钮,只需要用polygon()将所有的顶点都按顺序写出来即可。

.btn {
background: #ff003c;
clip-path: polygon(0 0, 337px 0, 337px 85px, 20px 85px, 0 65px);
/* 采用百分比的形式 */
/* clip-path: polygon(0 0, 100% 0, 100% 100%, 5.9% 100%, 0 76.5%); */
}

用上面的css替换原来的渐变,可以发现效果和原来是基本一样的。下面我们试试去实现官网上的不规则播放器。这里我们以一个图片替代播放器。

不规则播放器

对于这个不规则的播放器,我们同样可以使用polygon()将所有的边全部描述出来。

<div class="video-box">
<div class="video"></div>
</div>
.video-box {
width: 669px;
height: 375px;
margin: 100px;
clip-path: polygon(98% 5%, 95% 0%, 25% 0%, 20% 5%, 0% 5%, 0% 66.67%, 23% 100%, 60% 100%, 65% 95%, 100% 95%, 100% 60%, 98% 55%, 98% 5%);
}

.video-box .video {
width: 100%;
height: 100%;
background: url(https://cyberpunk.gog-statics.com/build/images/home3/screen-image-about-768457e5.jpg) 0 0/100% 100%;
}

特效

下面我们正式进入特效部分。通过仔细观察官网中的按钮可以发现,这个按钮其实是有两层的,且两层都一模一样。下面的一层做为主要显示,上面的一层通过剪切以及移动来达到现在我们看到的效果。

我们先在原来的基础上添加一个同样的按钮置于上层。

<div class="btn">
<span class="btn-text">现已发售</span>
<span class="copy-btn"></span>
</div>
.copy-btn {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
width: 337px;
height: 85px;
font-size: 26px;
line-height: 1;
font-weight: 700;
color: #fff;
letter-spacing: 2px;
cursor: pointer;
background: linear-gradient(45deg, transparent, transparent 5%, #ff003c 5%, #ff003c);
}

.copy-btn::after {
content: '现已发售';
display: inline-block;
}

在这里我们将背景效果的呈现全部换为了渐变。这是因为接下来的动画我们还会用到clip-path进行剪切,如果上层的背景也用clip-path会因为剪切点不同而导致动画部分效果不生效。对于下层是因为用了clip-path会导致上层的移动无法在区域外显示。

下面我们定义一个动画。动画也很简单,就是在不同的阶段进行不同区域的剪切,然后再加上translate()实现左右闪动的效果。

/* 直接采用官网中的样式代码 */
@keyframes glitch-anim-1 {
0% {
transform:translateZ(0);
clip-path:polygon(0 2%, 100% 2%, 100% 5%, 0 5%)
}
2% {
clip-path:polygon(0 78%, 100% 78%, 100% 100%, 0 100%);
transform:translate(-5px)
}
6% {
clip-path:polygon(0 78%, 100% 78%, 100% 100%, 0 100%);
transform:translate(5px)
}
8% {
clip-path:polygon(0 78%, 100% 78%, 100% 100%, 0 100%);
transform:translate(-5px)
}
9% {
clip-path:polygon(0 78%, 100% 78%, 100% 100%, 0 100%);
transform:translate(0)
}
10% {
clip-path:polygon(0 54%, 100% 54%, 100% 44%, 0 44%);
transform:translate3d(5px, 0, 0)
}
13% {
clip-path:polygon(0 54%, 100% 54%, 100% 44%, 0 44%);
transform:translateZ(0)
}
13.1% {
clip-path:polygon(0 0, 0 0, 0 0, 0 0);
transform:translate3d(5px, 0, 0)
}
15% {
clip-path:polygon(0 60%, 100% 60%, 100% 40%, 0 40%);
transform:translate3d(5px, 0, 0)
}
20% {
clip-path:polygon(0 60%, 100% 60%, 100% 40%, 0 40%);
transform:translate3d(-5px, 0, 0)
}
20.1% {
clip-path:polygon(0 0, 0 0, 0 0, 0 0);
transform:translate3d(5px, 0, 0)
}
25% {
clip-path:polygon(0 85%, 100% 85%, 100% 40%, 0 40%);
transform:translate3d(5px, 0, 0)
}
30% {
clip-path:polygon(0 85%, 100% 85%, 100% 40%, 0 40%);
transform:translate3d(-5px, 0, 0)
}
30.1% {
clip-path:polygon(0 0, 0 0, 0 0, 0 0)
}
35% {
clip-path:polygon(0 63%, 100% 63%, 100% 80%, 0 80%);
transform:translate(-5px)
}
40% {
clip-path:polygon(0 63%, 100% 63%, 100% 80%, 0 80%);
transform:translate(5px)
}
45% {
clip-path:polygon(0 63%, 100% 63%, 100% 80%, 0 80%);
transform:translate(-5px)
}
50% {
clip-path:polygon(0 63%, 100% 63%, 100% 80%, 0 80%);
transform:translate(0)
}
55% {
clip-path:polygon(0 10%, 100% 10%, 100% 0, 0 0);
transform:translate3d(5px, 0, 0)
}
60% {
clip-path:polygon(0 10%, 100% 10%, 100% 0, 0 0);
transform:translateZ(0);
}
60.1% {
clip-path:polygon(0 0, 0 0, 0 0, 0 0);
}
to {
clip-path:polygon(0 0, 0 0, 0 0, 0 0);
}
}

上面的代码中用到了很多的xx.1%,是为了让每个阶段之间更加具有割裂感,使动画效果更加棒。

最后我们用一下这个动画。

.copy-btn:hover {
animation: glitch-anim-1 2s linear alternate infinite;
}

然后就可以看到和官网一模一样的效果了。

源码及预览

预览:

源码:GITHUB

未完待续

后面的文章会继续进行实例的学习。

参考

菜鸟教程
clip-path
svg之path详解
clippy
CSS 奇思妙想边框动画

文章作者: JaCo Wu
文章链接: https://jacokwu.cn/blog/2021/01/26/从简单实例入手css动画与特效(一)/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 JaCo Wu的博客