前言
如果你想使你的按钮和登录界面更有特色,亦或者页面加载的效果更吸引人,那么CSS动画是一个不错的选择。
在这篇文章中,我将介绍我最喜欢的 CSS 动画用法。但首先我们要明白什么是CSS动画。
一、CSS动画如何工作?
CSS animations 使得可以将从一个 CSS 样式配置转换到另一个 CSS 样式配置。动画包括两个部分:描述动画的样式规则和用于指定动画开始、结束以及中间点样式的关键帧。
相较于传统的脚本实现动画技术,使用 CSS 动画有三个主要优点:
- 能够非常容易地创建简单动画,你甚至不需要了解 JavaScript 就能创建动画。
- 动画运行效果良好,甚至在低性能的系统上。渲染引擎会使用跳帧或者其他技术以保证动画表现尽可能的流畅。而使用 JavaScript实现的动画通常表现不佳(除非经过很好的设计)。
- 让浏览器控制动画序列,允许浏览器优化性能和效果,如降低位于隐藏选项卡中的动画更新频率。
要制作简单的 CSS 动画,您需要三样东西:一个要制作动画的 HTML 元素、一条将动画绑定到此元素的 CSS 规则,以及一组定义动画开始和结束时的样式的关键帧。您还可以添加声明以进一步自定义动画,例如速度和延迟。
这里有一个 CSS 动画的简单示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
div {
/* 动画的属性 */
animation-name: my-animation;
animation-duration: 2s;
animation-direction: alternate;
animation-iteration-count: infinite;
animation-timing-function: linear;
/* 其他属性 */
width: 300px;
height: 100px;
border-radius: 10px;
position: absolute;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
}
@keyframes my-animation {
from {
background-color: #ff7a59;
width: 300px;
top: 10px;
}
to {
background-color: #33475b;
width: 50px;
top: 100px;
}
}
</style>
</head>
<body>
<div></div>
</body>
</html>
在这个 CSS 关键帧动画中,div 是我们要制作动画的元素。
查看 CSS,我们可以看到动画声明与div 选择器相关联。这里最重要的声明是animation-name,它将关键帧my-animation绑定到我们的div 元素。
在它下面是几个影响动画时间和行为的附加声明。动画本身是使用关键帧创建的,由@keyframes 规则指示。关键帧定义动画的起始状态(在from{ }内)和结束状态(在to{ }内)。关键帧my-animation会更改 div 的三个样式属性:background-color、width和top。当这三个属性同时改变时,就会产生连贯的动画。
在我们的示例中,我们只有一个关键帧。具有多种动画类型的文档可能有多个关键帧,每个关键帧都绑定到不同的元素。
二、CSS关键帧动画示例
1.加载旋转器
CSS 动画可用于创建我们都熟悉的效果,旋转的加载图标就是一个例子。它们的含义几乎人人都能理解,而且代码也很简单。这个特定示例还展示了如何使用可缩放矢量图形实现类似的效果。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
#html-spinner{
width:40px;
height:40px;
border:4px solid #fcd779;
border-top:4px solid white;
border-radius:50%;
}
#html-spinner, #svg-spinner{
-webkit-transition-property: -webkit-transform;
-webkit-transition-duration: 1.2s;
-webkit-animation-name: rotate;
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
-moz-transition-property: -moz-transform;
-moz-animation-name: rotate;
-moz-animation-duration: 1.2s;
-moz-animation-iteration-count: infinite;
-moz-animation-timing-function: linear;
transition-property: transform;
animation-name: rotate;
animation-duration: 1.2s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
@-webkit-keyframes rotate {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(360deg);}
}
@-moz-keyframes rotate {
from {-moz-transform: rotate(0deg);}
to {-moz-transform: rotate(360deg);}
}
@keyframes rotate {
from {transform: rotate(0deg);}
to {transform: rotate(360deg);}
}
/* 其余页面样式*/
body{
background:#FABC20;
font-family: 'Open Sans', sans-serif;
-webkit-font-smoothing: antialiased;
color:#393D3D;
}
#container{
width:90%;
max-width:700px;
margin:1em auto;
position:relative;
}
/* 微调控制项定位 */
#html-spinner, #svg-spinner{
position:absolute;
top:80px;
margin-left:-24px;
}
#html-spinner{
left:25%;
}
#svg-spinner{
left:75%;
}
#html-para, #svg-para{
position:absolute;
top:100px;
width:40%;
padding:5%;
text-align:center;
}
#svg-para{
left:50%;
}
</style>
</head>
<body>
<div id="container">
<!--元素,用于使用HTML + CSS制作的微调器-->
<div id="html-spinner"></div>
<p id="html-para">仅使用HTML和CSS创建微调器</p>
<!--元素用于自定义SVG旋转器-->
<svg id="svg-spinner" xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
<circle cx="24" cy="4" r="4" fill="#fff"/>
<circle cx="12.19" cy="7.86" r="3.7" fill="#fffbf2"/>
<circle cx="5.02" cy="17.68" r="3.4" fill="#fef7e4"/>
<circle cx="5.02" cy="30.32" r="3.1" fill="#fef3d7"/>
<circle cx="12.19" cy="40.14" r="2.8" fill="#feefc9"/>
<circle cx="24" cy="44" r="2.5" fill="#feebbc"/>
<circle cx="35.81" cy="40.14" r="2.2" fill="#fde7af"/>
<circle cx="42.98" cy="30.32" r="1.9" fill="#fde3a1"/>
<circle cx="42.98" cy="17.68" r="1.6" fill="#fddf94"/>
<circle cx="35.81" cy="7.86" r="1.3" fill="#fcdb86"/>
</svg>
<p id="svg-para">使用自定义SVG和CSS动画创建的微调器</p>
</div>
</div>
</body>
</html>
2.CSS 加载器
这里还有一些加载动画可供尝试,以方便的网格形式呈现。每个动画都流畅而优雅,因此你可以挑选出最吸引你的动画。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
.loader {
--color: white;
--size-mid: 6vmin;
--size-dot: 1.5vmin;
--size-bar: 0.4vmin;
--size-square: 3vmin;
display: block;
position: relative;
width: 50%;
display: grid;
place-items: center;
}
.loader::before,
.loader::after {
content: '';
box-sizing: border-box;
position: absolute;
}
/**
loader --1
**/
.loader.--1::before {
width: var(--size-mid);
height: var(--size-mid);
border: 4px solid var(--color);
border-top-color: transparent;
border-radius: 50%;
animation: loader-1 1s linear infinite;
}
.loader.--1::after {
width: calc(var(--size-mid) - 2px);
height: calc(var(--size-mid) - 2px);
border: 2px solid transparent;
border-top-color: var(--color);
border-radius: 50%;
animation: loader-1 0.6s linear reverse infinite;
}
@keyframes loader-1 {
100% {
transform: rotate(1turn);
}
}
/**
loader --2
**/
.loader.--2::before,
.loader.--2::after {
width: var(--size-dot);
height: var(--size-dot);
background-color: var(--color);
border-radius: 50%;
opacity: 0;
animation: loader-2 0.8s cubic-bezier(0.2, 0.32, 0, 0.87) infinite;
}
.loader.--2::after {
animation-delay: 0.3s;
}
@keyframes loader-2 {
0%,
80%,
100% {
opacity: 0;
}
33% {
opacity: 1;
}
0%,
100% {
transform: translateX(-4vmin);
}
90% {
transform: translateX(4vmin);
}
}
/**
loader --3
**/
.loader.--3::before,
.loader.--3::after {
width: var(--size-dot);
height: var(--size-dot);
background-color: var(--color);
border-radius: 50%;
animation: loader-3 1.2s ease-in-out infinite;
}
.loader.--3::before {
left: calc(50% - 1.6vmin - var(--size-dot));
}
.loader.--3::after {
left: calc(50% + 1.6vmin);
animation-delay: -0.4s;
}
@keyframes loader-3 {
0%,
100% {
transform: translateY(-2.6vmin);
}
44% {
transform: translateY(2.6vmin);
}
}
/**
loader --4
**/
.loader.--4::before {
height: var(--size-bar);
width: 6vmin;
background-color: var(--color);
animation: loader-4 0.8s cubic-bezier(0, 0, 0.03, 0.9) infinite;
}
@keyframes loader-4 {
0%,
44%,
88.1%,
100% {
transform-origin: left;
}
0%,
100%,
88% {
transform: scaleX(0);
}
44.1%,
88% {
transform-origin: right;
}
33%,
44% {
transform: scaleX(1);
}
}
/**
loader --5
**/
.loader.--5::before,
.loader.--5::after {
height: 3vmin;
width: var(--size-bar);
background-color: var(--color);
animation: loader-5 0.6s cubic-bezier(0, 0, 0.03, 0.9) infinite;
}
.loader.--5::before {
left: calc(50% - 1vmin);
top: calc(50% - 3vmin);
}
.loader.--5::after {
left: calc(50% + 1vmin);
top: calc(50% - 1vmin);
animation-delay: 0.2s;
}
@keyframes loader-5 {
0%,
88%,
100% {
opacity: 0;
}
0% {
transform: translateY(-6vmin);
}
33% {
opacity: 1;
}
33%,
88% {
transform: translateY(3vmin);
}
}
/**
loader --6
**/
.loader.--6::before {
width: var(--size-square);
height: var(--size-square);
background-color: var(--color);
top: calc(50% - var(--size-square));
left: calc(50% - var(--size-square));
animation: loader-6 2.4s cubic-bezier(0, 0, 0.24, 1.21) infinite;
}
@keyframes loader-6 {
0%,
100% {
transform: none;
}
25% {
transform: translateX(100%);
}
50% {
transform: translateX(100%) translateY(100%);
}
75% {
transform: translateY(100%);
}
}
/**
loader --7
**/
.loader.--7::before,
.loader.--7::after {
width: var(--size-square);
height: var(--size-square);
background-color: var(--color);
}
.loader.--7::before {
top: calc(50% - var(--size-square));
left: calc(50% - var(--size-square));
animation: loader-6 2.4s cubic-bezier(0, 0, 0.24, 1.21) infinite;
}
.loader.--7::after {
top: 50%;
left: 50%;
animation: loader-7 2.4s cubic-bezier(0, 0, 0.24, 1.21) infinite;
}
@keyframes loader-7 {
0%,
100% {
transform: none;
}
25% {
transform: translateX(-100%);
}
50% {
transform: translateX(-100%) translateY(-100%);
}
75% {
transform: translateY(-100%);
}
}
/**
loader --8
**/
.loader.--8::before,
.loader.--8::after {
width: var(--size-dot);
height: var(--size-dot);
border-radius: 50%;
background-color: var(--color);
}
.loader.--8::before {
top: calc(50% + 4vmin);
animation: loader-8-1 0.8s cubic-bezier(0.06, 0.01, 0.49, 1.18) infinite;
}
.loader.--8::after {
opacity: 0;
top: calc(50% - 2vmin);
animation: loader-8-2 0.8s cubic-bezier(0.46, -0.1, 0.27, 1.07) 0.2s infinite;
}
@keyframes loader-8-1 {
0%,
55%,
100% {
opacity: 0;
}
0% {
transform: scale(0.2);
}
22% {
opacity: 1;
}
33%,
55% {
transform: scale(1) translateY(-6vmin);
}
}
@keyframes loader-8-2 {
0%,
100% {
opacity: 0;
}
33% {
opacity: 0.3;
}
0% {
transform: scale(0);
}
100% {
transform: scale(4);
}
}
/**
loader --9
**/
.loader.--9::before,
.loader.--9::after {
width: var(--size-dot);
height: var(--size-dot);
border-radius: 50%;
background-color: var(--color);
animation: loader-9 0.42s cubic-bezier(0.39, 0.31, 0, 1.11) infinite;
}
.loader.--9::before {
left: calc(50% - var(--size-dot) - 1.6vmin);
}
.loader.--9::after {
left: calc(50% + 1.6vmin);
animation-delay: 0.12s;
}
@keyframes loader-9 {
0%,
100% {
opacity: 0;
}
0% {
transform: translate(-4vmin, -4vmin);
}
66% {
opacity: 1;
}
66%,
100% {
transform: none;
}
}
/**
miscs
**/
.container {
display: grid;
grid-template-columns: repeat(3, 18vmin);
grid-template-rows: repeat(3, 18vmin);
grid-gap: 1vmin;
}
.item {
background: rgba(255, 255, 255, 0.1);
display: grid;
place-items: center;
border-radius: 4px;
transition: opacity 0.4s ease;
}
.container:hover .item {
opacity: 0.3;
}
.container:hover .item:hover {
opacity: 1;
}
.page {
margin: auto;
}
.header {
margin-bottom: 4vmin;
}
.header-title {
font-size: 3.75vmin;
}
.header-subtitle {
font-size: 2vmin;
text-transform: uppercase;
opacity: 0.6;
}
html,
body {
display: flex;
width: 100%;
height: 100%;
background-image: linear-gradient(to right top, #051937, #004d7a, #008793, #00bf72, #a8eb12);
font-family: 'Noto Sans', sans-serif;
color: white;
text-align: center;
letter-spacing: 0.3px;
}
</style>
</head>
<body>
<div class="page">
<header class="header">
<h1 class="header-title">CSS加载器</h1>
<p class="header-subtitle">单个HTML元素CSS动画</p>
</header>
<main class="container">
<div class="item">
<i class="loader --2"></i>
</div>
<div class="item">
<i class="loader --9"></i>
</div>
<div class="item">
<i class="loader --3"></i>
</div>
<div class="item">
<i class="loader --4"></i>
</div>
<div class="item">
<i class="loader --1"></i>
</div>
<div class="item">
<i class="loader --5"></i>
</div>
<div class="item">
<i class="loader --6"></i>
</div>
<div class="item">
<i class="loader --8"></i>
</div>
<div class="item">
<i class="loader --7"></i>
</div>
</main>
</div>
</body>
</html>
3.动画提交按钮
这个提交按钮是一种简洁、令人愉悦的方式,可以提供视觉反馈,表明某项操作已完成,例如表单已提交。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
@-webkit-keyframes {
}
@-webkit-keyframes extend {
0% {
width: 600px;
height: 200px;
border-radius: 100px;
} 10% {
width: 610px;
height: 210px;
background: #fff;
margin-left: - 5px;
margin-top: - 5px;
} 20% {
width: 600px;
height: 200px;
background: #6fb07f;
margin-left: 0px;
margin-top: 0px;
} 100% {
width: 200px;
height: 200px;
border-radius: 100px;
margin-left: 190px;
background: #6fb07f;
}
}
@keyframes extend {
0% {
width: 600px;
height: 200px;
border-radius: 100px;
} 10% {
width: 610px;
height: 210px;
background: #fff;
margin-left: - 5px;
margin-top: - 5px;
} 20% {
width: 600px;
height: 200px;
background: #6fb07f;
margin-left: 0px;
margin-top: 0px;
} 100% {
width: 200px;
height: 200px;
border-radius: 100px;
margin-left: 190px;
background: #6fb07f;
}
}
@-webkit-keyframes {
}
@-webkit-keyframes disappear {
0% {
opacity: 1;
} 20% {
color: #fff;
} 100% {
opacity: 0;
}
}
@keyframes disappear {
0% {
opacity: 1;
} 20% {
color: #fff;
} 100% {
opacity: 0;
}
}
@-webkit-keyframes {
}
@-webkit-keyframes appear {
0% {
opacity: 0;
} 70% {
opacity: 0;
} 100% {
opacity: 1;
}
}
@keyframes appear {
0% {
opacity: 0;
} 70% {
opacity: 0;
} 100% {
opacity: 1;
}
}
html {
background: #fff
}
input, button, submit {
border: none
}
.cont {
position: absolute;
width: 610px;
height: 10px;
left: 50%;
top: 50%;
margin: -100px 0 0 -300px
}
button {
border-width: 1px;
width: 600px;
height: 200px;
/*border-radius*/
border-radius: 100px;
background: #fff;
position: absolute;
border: 5px solid #6fb07f
}
button > span {
font-size: 48px;
color: #6fb07f
}
img {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
filter: alpha(opacity=0);
opacity: 0
}
button:focus {
/*animation*/
-webkit-animation: extend 1s ease-in-out;
-ms-animation: extend 1s ease-in-out;
animation: extend 1s ease-in-out;
-webkit-animation-fill-mode: forwards;
/* Chrome, Safari, Opera */
animation-fill-mode: forwards
}
button:focus > span {
/*animation*/
-webkit-animation: disappear 1s ease-in-out;
-ms-animation: disappear 1s ease-in-out;
animation: disappear 1s ease-in-out;
-webkit-animation-fill-mode: forwards;
/* Chrome, Safari, Opera */
animation-fill-mode: forwards
}
button:focus > img {
/*animation*/
-webkit-animation: appear 1s ease-in-out;
-ms-animation: appear 1s ease-in-out;
animation: appear 1s ease-in-out;
-webkit-animation-fill-mode: forwards;
/* Chrome, Safari, Opera */
animation-fill-mode: forwards
}
</style>
</head>
<body>
<div class="cont">
<button class="btn"><span>Submit</span><img src="https://i.cloudup.com/2ZAX3hVsBE-3000x3000.png" height="62" width="62"></button>
</div>
</body>
</html>
4.3D拨码开关
切换开关是 UI 设计中必不可少的元素。此示例对这一概念进行了三维旋转,并为切换状态添加了流畅的动画。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
* {
border: 0;
box-sizing: border-box;
margin: 0;
padding: 0;
}
body,
input {
font: 80px/1.5 sans-serif;
}
body,
input[type=checkbox]:before {
background-image:
linear-gradient(90deg, #f1f2f3 2px, #f1f2f300 2px),
linear-gradient(#f1f2f3 2px, #fff 2px);
background-repeat: repeat;
background-size: 0.75em 0.375em;
}
body {
background-position: 50% calc(50% + 0.2em);
display: grid;
place-items: center;
height: 100vh;
}
input[type=checkbox] {
--off: #c7cad1;
--mid: #829ad6;
--on: #255ff4;
--transDur: 0.5s;
--timing: cubic-bezier(0.6, 0, 0.4, 1);
animation: bgOff var(--transDur) var(--timing);
background-color: var(--off);
border-radius: 0.67em / 0.5em;
box-shadow:
0 0.05em 0.1em #00000007 inset,
0 -0.25em 0.25em #0001 inset,
0 -0.5em 0 #0001 inset,
0 0.1em 0.1em #0001;
cursor: pointer;
position: relative;
width: 2.25em;
height: 1.5em;
-webkit-appearance: none;
appearance: none;
-webkit-tap-highlight-color: transparent;
}
input[type=checkbox]:before {
animation: handleOff var(--transDur) var(--timing);
background-attachment: fixed;
background-position: 50% calc(50% - 0.1875em);
border-radius: 0.5em / 0.375em;
box-shadow:
0 0.175em 0.175em 0 #0001 inset,
0 0.375em 0 #0002 inset,
0 0.375em 0 var(--off) inset,
0 0.475em 0.1em #0001 inset;
content: "";
display: block;
position: absolute;
top: 0.125em;
left: 0.125em;
width: 1em;
height: 0.75em;
}
input[type=checkbox]:checked {
animation: bgOn var(--transDur) var(--timing) forwards;
}
input[type=checkbox]:checked:before {
animation: handleOn var(--transDur) var(--timing) forwards;
}
input[type=checkbox]:focus {
outline: none;
}
input[type=checkbox].pristine,
input[type=checkbox].pristine:before {
animation: none;
}
/* 深色模式 */
@media (prefers-color-scheme: dark) {
body,
input[type=checkbox]:before {
background-image:
linear-gradient(90deg, #3a3d46 2px, #3a3d4600 2px),
linear-gradient(#3a3d46 2px, #2e3138 2px);
}
input[type=checkbox] {
--off: #5c6270;
--mid: #3d5fb6;
}
}
/* 动画 */
@keyframes bgOff {
from {
background-color: var(--on);
}
50% {
background-color: var(--mid);
}
to {
background-color: var(--off);
}
}
@keyframes bgOn {
from {
background-color: var(--off);
}
50% {
background-color: var(--mid);
}
to {
background-color: var(--on);
}
}
@keyframes handleOff {
from {
box-shadow:
0 0.175em 0.175em 0 #0001 inset,
0 0.375em 0 #0002 inset,
0 0.375em 0 var(--on) inset,
0 0.475em 0.1em #0001 inset;
left: 1.125em;
width: 1em;
}
50% {
box-shadow:
0 0.175em 0.175em 0 #0001 inset,
0 0.375em 0 #0002 inset,
0 0.375em 0 var(--mid) inset,
0 0.475em 0.1em #0001 inset;
left: 0.125em;
width: 2em;
}
to {
box-shadow:
0 0.175em 0.175em 0 #0001 inset,
0 0.375em 0 #0002 inset,
0 0.375em 0 var(--off) inset,
0 0.475em 0.1em #0001 inset;
left: 0.125em;
width: 1em;
}
}
@keyframes handleOn {
from {
box-shadow:
0 0.175em 0.175em 0 #0001 inset,
0 0.375em 0 #0002 inset,
0 0.375em 0 var(--off) inset,
0 0.475em 0.1em #0001 inset;
left: 0.125em;
width: 1em;
}
50% {
box-shadow:
0 0.175em 0.175em 0 #0001 inset,
0 0.375em 0 #0002 inset,
0 0.375em 0 var(--mid) inset,
0 0.475em 0.1em #0001 inset;
left: 0.125em;
width: 2em;
}
to {
box-shadow:
0 0.175em 0.175em 0 #0001 inset,
0 0.375em 0 #0002 inset,
0 0.375em 0 var(--on) inset,
0 0.475em 0.1em #0001 inset;
left: 1.125em;
width: 1em;
}
}
</style>
</head>
<body>
<input class="pristine" type="checkbox" name="toggle" value="on">
</body>
<script type="text/javascript">
document.addEventListener("click",e => {
let tar = e.target;
if (tar.name == "toggle")
tar.removeAttribute("class");
});
</script>
</html>
5.扭曲方块
这是一个看似简单但却能创造迷幻效果的示例。页面上的所有方形 div 都应用了相同的旋转效果。它们的尺寸差异导致它们以不同的速度旋转,从而产生幻觉。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
html{
background: black;
height: 100%;
position: relative;
overflow: hidden;
}
.container{
height: 300px;
width: 300px;
position: absolute;
top: 50%;
left: 50%;
margin: -150px 0 0 -150px;
}
.square{
height: 94%;
width: 94%;
background: white;
position: absolute;
top: 50%;
left: 50%;
margin: -47% 0 0 -47%;
}
.black{
background: black;
animation: rotate 10s infinite linear;
}
@keyframes rotate{
0%{ transform: rotate(0deg); }
100%{ transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="container">
<div class="square black">
<div class="square">
<div class="square black">
<div class="square">
<div class="square black">
<div class="square">
<div class="square black">
<div class="square">
<div class="square black">
<div class="square">
<div class="square black">
<div class="square">
<div class="square black">
<div class="square">
<div class="square black">
<div class="square">
<div class="square black">
<div class="square">
<div class="square black">
<div class="square">
<div class="square black">
<div class="square">
<div class="square black">
<div class="square">
<div class="square black">
<div class="square">
<div class="square black">
<div class="square">
<div class="square black">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>