《深入解析CSS》 |视觉效果

前言:所有内容与示例源码源于基思·J·格兰特的《深入解析css》,文章用于笔记整理。文章示例源码仓库书籍源码仓库

引言:一个网站,从看起来还可以,到看起来非常棒,差别在于细节。要实现这些细节,CSS中那些需要艺术创意的部分就派上用场了。

一、渐变

也许你已经了解过纯色背景和一些背景图片的使用方法,但是background属性依然还有很多的功能等待我们去探索:

  • background-image——指定一个文件或者生成的颜色渐变作为背景图片。
  • background-position——设置背景图片的初始位置。
  • background-size——指定元素内背景图片的渲染尺寸。
  • background-repeat——决定在需要填充整个元素时,是否*铺图片。
  • background-origin——决定背景相对于元素的边框盒、内边距框盒(初始值)或内容盒子来定位。
  • background-clip——指定背景是否应该填充边框盒(初始值)、内边距框盒或内容盒子。
  • background-attachment——指定背景图片是随着元素上下滚动(初始值),还是固定在视口区域。注意,使用fixed值会对页面性能产生负面影响。
  • background-color——指定纯色背景,渲染到背景图片下方。

其中,background-image可以接受一个图片URL路径,它也可以接受渐变函数:linear-gradient()、repeating-linear-gradient()、radial-gradient()、repeating-radial-gradient()。

两个颜色

.fade {
  height: 200px;
  width: 400px;
  background-image: linear-gradient(to right, white, blue);
}
<div class="fade"></div>

image.png

linear-gradient的三个参数:角度、起始颜色和终止颜色。角度值可以是:to right、to bottom、to bottom right、0deg(垂直向上)、180deg等;颜色可以用其他表示如hex(#0000ff)、RGB(rgb(0, 0, 255))或者transparent关键字。

多个颜色渐变

.fade2 {
    height: 200px;
    width: 400px;
    background-image: linear-gradient(90deg, red, white, blue);
    /*等价于
    background-image: linear-gradient(90deg, red 0%, white 50%, blue 100%);
    */
}
<div class="fade2"></div>

image.png

条纹

.fade3 {
  height: 200px;
  width: 400px;
  background-image: linear-gradient(90deg, red 40%, white 40%, white 60%, blue 60%);
}
<div class="fade3"></div>

image.png

重复渐变

.fade4 {
  height: 1em;
  width: 400px;
  background-image: repeating-linear-gradient(-45deg, #57b, #57b 10px, #148 10px, #148 20px);
  border-radius: 0.3em;
}
<div class="fade4"></div>

image.png

径向渐变

.fade5 {
      height: 200px;
      width: 400px;
      background-image: radial-gradient(white, blue);
}
<div class="fade5"></div>

image.png

其他:

image.png

二、阴影

阴影是另一种可以为网页增加立体感的特效。有两个属性可以创建阴影,box-shadow可以为元素盒子生成阴影,text-shadow可以为渲染后的文字生成阴影。

image.png

简单阴影

image.png

.box{
  padding: 1em;
  border: 0;
  font-size: 0.8rem;
  color: white;
  cursor: pointer;
}
.shadow{
  border-radius: 0.5em;
  background-color: thistle;
  box-shadow:0.5em 1em blueviolet
}
<botton class="box shadow">我有阴影</botton>

立体感

image.png

.shadow2 {
  border-radius: 0.5em;
  background-image: linear-gradient(to bottom, #57b, #148);
  box-shadow: 0.1em 0.1em 0.5em #124;
}
.shadow2:active {
  box-shadow: inset 0 0 0.5em #124, inset 0 0.5em 1em rgba(0,0,0,0.4);
}
<botton class="box shadow2">立体感</botton>

inset关键字使阴影出现在内部。在本例中,激化状态下的阴影用逗号隔开了,表示添加了多个阴影。第一个内阴影(inset 0 0 0.5em #124)偏移量为0,轻微模糊。这在元素的边缘内添加了一个阴影环。第二个内阴影(inset 0 0.5em 1emrgba(0,0,0,0.4))在垂直方向有一点偏移,这样就让按钮顶端的阴影延长了一些。RGBA颜色表示法定义了一个半透明的黑色。建议你自己动手修改一下这些值,看看它们到底如何影响最终的渲染效果。

在Chrome浏览器中点击按钮时,你会发现按钮周围环绕了一个浅蓝色的光圈,这是浏览器为按钮的:focus状态默认添加的轮廓线。可以通过设置.shadow2:focus{ outline:none; }来移除轮廓线。建议你在移除轮廓线的同时,添加一些其他特效来代替,这样当用户使用键盘导航的时候,就可以看到当前焦点状态在哪里。

扁*化

image.png

.shadow3 {
  background-color: #57b;
  box-shadow: 0 0.2em 0.2em rgba(0, 0, 0, 0.15);
}
.shadow3:hover {
  background-color: #456ab6;
}
.shadow3:active {
  background-color: #148;
}
<botton class="box shadow3">扁*化设计</botton>

扁*化设计讲究色彩明快统一、外观简洁明了,这就意味着尽量少使用渐变、阴影和圆角。鼠标悬停到激活状态颜色渐渐加深

其他风格

image.png

.shadow4 {
  border-radius: 0.5em;
  background-color: #57b;
  box-shadow: 0 0.4em #148;
  text-shadow: 1px 1px #148;  /* 加上了文字阴影 */
}
.shadow4:active {
  background-color: #456ab5;
  transform: translateY(0.1em);  /* 点击时下移 */
  box-shadow: 0 0.3em #148;   	/* 减小阴影偏移 抵消下移 */
}
.shadow4:focus {
  outline: none;
}
<botton class="box shadow4">其他风格</botton>

三、混合模式

background-image属性可以接受任意数量的值,相互之间以逗号分隔,比如:

background-image:url(bear.jpg),linear-gradient(to bottom,#57b,#148);

在本例中,bear.jpg会遮盖在渐变之上,渐变就会不可见。而如果我们使用两张背景图片,那么一般是希望第二张图片也可以透视显示,这时就可以使用混合模式(blend mode)

混合模式用来控制叠放的图片怎样融合在一起,有些模式的命名有点让人摸不着头脑,比如滤色(screen)、颜色加深(color-burn)、强光(hard-light)、叠底(multiply)等。

叠底

image.png

.blend {
  /* 这里使用了min-height属性,是为了确保元素不会显示成高度为0(因为是空元素) */
  min-height: 400px;
  background-image: url(images/dog.jpeg), url(images/dog.jpeg);
  background-size: cover;
  background-repeat: no-repeat;

  /* 为每张背景图设置不同的初始位置 */
  background-position: -30vw, 30vw;   
  /* 叠底的混合模式 */  
  background-blend-mode: multiply;
}
<div class="blend"></div>

两个背景使用了同一张图片,但是背景位置不同。background-size若是cover,则可以调整背景图片的大小,使其填满整个元素,可能导致图片的边缘被裁切掉一部分;使用contain可以保证整个背景图片可见

尝试修改混合模式的其他值,查看它们有哪些不同效果,比如color-burn或者difference。这里列举一些实际应用:

  • 使用某种颜色或者渐变为图片着色;
  • 为图片添加纹理效果,比如划痕或者老胶片放映时的颗粒感等;
  • 缓和、加深或者减小图片的对比度,使图片上的文字更具可读性;
  • 在图片上覆盖了一条文字横幅,但是还想让图片完整显示。

图片着色

image.png

.blend2 {
  min-height: 400px;
  background-image: url(images/dog.jpeg);
  background-color: rgb(234, 236, 220);  
  background-size: cover;
  background-repeat: no-repeat;
  /*图片着色的混合模式*/
  background-blend-mode: luminosity;
}
<div class="blend2"></div>

background-blend-mode不仅仅合并多个背景图片,还会合并background-color。明度混合模式(还有其他几种类似的混合模式)最终的渲染结果,取决于哪个图层在其他图层之上,这是非常重要的。

其他

image.png

图片纹理

image.png

.blend3 {
  min-height: 400px;
  background-image: url("images/02.png"), url("images/dog.jpeg");
  /* 每200px*铺一张纹理图片 */
  background-size: 200px, cover;
  background-repeat: repeat, no-repeat;
  background-position: center center;
  /* 柔光混合 */
  background-blend-mode: soft-light;
}
<div class="blend3"></div>

融合混合

image.png

.blend4 {
  background-image: url("images/dog.jpeg");
  background-size: cover;
  background-position: center;
  padding: 15em 0 1em;
}
.blend4 > h1 {
  margin: 0;
  font-family: Helvetica, Arial, sans-serif;
  font-size: 6rem;
  text-align: center;
  /* 强光混合 */
  mix-blend-mode: hard-light;
  background-color: #c33;
  color: #808080;
  border: 0.1em solid #ccc;
  border-width: 0.1em 0;
}
<div class="blend4">
    <h1>Ursa Major</h1>
</div>

四、过渡

Web可以做更多的事情,比如元素可以淡出、菜单可以滑入、颜色可以从一种变为另一种,实现这些效果最简单的方式是过渡(transitions)

过渡是通过一系列transition-*属性来实现的。如果某个元素设置了过渡,那么当它的属性值发生变化时,并不是直接变成新值,而是使用过渡效果。下面是一个示例,最开始是个蓝绿色方角按钮,鼠标悬停时,过渡成一个红色圆角按钮,其中还有过渡中间状态:image.png

button {
  background-color: hsl(180, 50%, 50%);
  border: 0;
  color: white;
  font-size: 1rem;
  padding: .3em 1em;
  /* 所有属性变化都使用过渡效果 */
  transition-property: all;
  /* 过渡时长 */
  transition-duration: 0.5s;
}
button:hover {
  background-color: hsl(0, 50%, 50%);
  border-radius: 1em;
}
<button>hover over me</button>
  • transition-property:指定哪些属性使用过渡
  • transition-duration:代表过渡到最终值之前需要的时间
  • transition-timing-function:定时函数。比如linear、ease-in,也可以自定义
  • transition-delay:延迟时间

image.png

上面时简写属性,如果需要为两个不同的属性分别设置不同的过渡,可以添加多个过渡规则,以逗号分隔:

**transition**: border-radius 0.3s linear, background-color 0.6s ease;
# 等价于
**transition-property**: border-radius, background-color; 
**transition-duration**:0.3s,0.6s; 
**transition-timing-function**: linear, ease;

五、变换

变换(transform)属性可以用来改变页面元素的形状和位置,其中包括二维或者三维的旋转、缩放和倾斜。这里有四种变换函数:

image.png

  • 旋转(Rotate):元素绕着一个轴心转动一定角度。
  • *移(Translate):元素向上、下、左、右各个方向移动(有点类似于相对定位)。
  • 缩放(Scale):缩小或放大元素。
  • 倾斜(Skew):使元素变形,顶边滑向一个方向,底边滑向相反的方向。

效果

旋转(rotate)

body {
  background-color: hsl(210, 80%, 20%);
  font-family: Helvetica, Arial, sans-serif;
}
img {
  max-width: 100%;
}
.card {
  box-sizing: border-box;
  padding: 0.5em;
  margin: 0 auto;
  background-color: white;
  max-width: 300px;
  /* 向右旋转15度 */
  transform: rotate(15deg);
}
<div class="card">
    <img src="images/01.jpg" alt="a chicken"/>
    <h4>Mrs. Featherstone</h4>
    <p> She may be a bit frumpy, but Mrs Featherstone gets the job done. She lays her largish cream-colored eggs on a daily basis. She is gregarious to a fault.</p>
    <p>This Austra White is our most prolific producer.</p>
</div>

倾斜(skew)

.card {
  ...
  transform: skew(10deg);
}

缩放(scale)

.card {
  ...
  transform:scale(0.5);
}

移动(translate)

.card {
  ...
  //向右移动20px 向下移动40px
  transform:translate(20px, 40px)
}

更改变换基点

默认情况下,基点就是元素的中心,但可以通过transform-origin属性改变基点位置:

image.png

基点也可以指定为百分比,从元素左上角开始测量,下面的两句声明是等价的。也可以使用px、em或者其他单位的长度值来指定基点。

transform-origin:right center; 
transform-origin:100% 50%;

使用多重变换

.card {
  padding: 0.5em;
  margin: 0 auto;
  background-color: white;
  max-width: 300px;
  transform: rotate(15deg) translate(20px, 0);
}

示例-在运动中变换

变换本身不具备太多实用性,当和动作结合起来使用的时候,变换就会有用多了。这里会实现左侧的导航菜单,示例包含多个过渡和一对变换。首先来实现页面:(分支20)

image.png

01基础框架

<header>
    <h1 class="page-header">The Yolk Factory</h1>
</header>

<nav class="main-nav">
    <ul class="nav-links">
      <li>
        <a href="/">
          <img src="images/home.svg" class="nav-links__icon"/>
          <span class="nav-links__label">Home</span>
        </a>
      </li>
      <li>
        <a href="/events">
          <img src="images/calendar.svg" class="nav-links__icon"/>
          <span class="nav-links__label">Events</span>
        </a>
      </li>
      <li>
        <a href="/members">
          <img src="images/members.svg" class="nav-links__icon"/>
          <span class="nav-links__label">Members</span>
        </a>
      </li>
      <li>
        <a href="/about">
          <img src="images/star.svg" class="nav-links__icon"/>
          <span class="nav-links__label">About</span>
        </a>
      </li>
    </ul>
</nav>
html {
  box-sizing: border-box;
}
*,*::before,*::after {
  box-sizing: inherit;
}

body {
  /* 深蓝色背景渐变 */
  background-color: hsl(200, 80%, 30%);
  background-image: radial-gradient(hsl(200, 80%, 30%), hsl(210, 80%, 20%));
  color: white;
  font-family: Raleway, Helvetica, Arial, sans-serif;
  line-height: 1.4;
  margin: 0;
  /* 确保body元素填满视窗 */
  min-height: 100vh;
}
h1, h2, h3 {
  font-family: Alfa Slab One, serif;
  font-weight: 400;
}
main {
  display: block;
}
img {
  max-width: 100%;
}

.page-header {
  margin: 0;
  padding: 1rem;
}

SVG——Scalable Vector Graphics的简称,可缩放矢量图形。这是一种基于XML的图片格式,使用向量定义图片。因为图片是使用数学计算来定义的,所以可以放大或缩小到任意尺寸。绝大部分浏览器支持SVG。

SVG:一种更好的图标解决方案图标在某些设计中是非常重要的一部分,图标的使用技巧也一直在进化。很长一段时间里,图标使用的最佳实践是把所有图标放入单个图片文件,称之为精灵图(sprite sheet)。然后使用CSS背景图片,小心翼翼地调整尺寸和背景位置,在元素中显示精灵图上的图标。后来,图标字体(icon fonts)开始流行起来。这种解决方案每个图标都作为字符放入自定义的字体文件。通过使用Web字体,单个字符将被渲染成图标。

02小屏菜单布局

image.png

.nav-links {
  /* 使用弹性布局在屏幕水*方向上展开项目 */
  display: flex;
  justify-content: space-between;
  margin-top: 0;
  margin-bottom: 1rem;
  padding: 0 1rem;
  list-style: none;
}
.nav-links > li + li {
  margin-left: 0.8em;
}
.nav-links > li > a {
  display: block;
  padding: 0.8em 0;
  color: white;
  font-size: 0.8rem;
  text-decoration: none;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.nav-links__icon {
  height: 1.5em;
  width: 1.5em;
  /* 把图标向下移动,与文本标签对齐 */
  vertical-align: -0.2em;
}
.nav-links > li > a:hover {
  color: hsl(40, 100%, 70%);
}

03大屏菜单布局

以使用固定定位使菜单停靠在屏幕左侧,如下:

image.png

@media (min-width: 30em) {
  .main-nav {
    position: fixed;
    left: 0;
    top: 8.25rem;
    z-index: 10;
    background-color: transparent;
    transition: background-color .5s linear;
    border-top-right-radius: 0.5em;
    border-bottom-right-radius: 0.5em;
  }
  .main-nav:hover {
    background-color: rgba(0, 0, 0, 0.6);
  }
}

@media (min-width: 30em) {
  .nav-links {
    display: block;
    padding: 1em;
    margin-bottom: 0;
  }
  .nav-links > li + li {
    margin-left: 0;
  }
  .nav-links__label {
    margin-left: 1em;
  }
}

04悬停放大图标

image.png

@media (min-width: 30em) {
  ...
  .nav-links__icon {
    transition: transform 0.2s ease-out;
  }
  .nav-links a:hover > .nav-links__icon,
  .nav-links a:focus > .nav-links__icon {
    transform: scale(1.3);
  }
}

05隐藏菜单

菜单的标签没有必要一直保持可见状态。默认情况下可以把它们隐藏。只在相应位置保留图标,告诉用户菜单的位置。当用户移动鼠标到菜单或者导航元素上时,再把标签以淡入的方式展示出来。

image.png

@media (min-width: 30em) {
  ...
  /* 默认隐藏菜单 */
  .nav-links__label {
    /* 把标签设置为行内块级元素,这样就可以对其使用变换效果了 */
    display: inline-block;
    margin-left: 1em;
    padding-right: 1em;
    /* 开始时隐藏标签 */
    opacity: 0;
    /* 标签左移1em */
    transform: translate(-1em);
    /* 添加过渡 */
    transition: transform 0.4s cubic-bezier(0.2, 0.9, 0.3, 1.3),
                opacity 0.4s linear;
  }
  /* 鼠标悬停或者激活状态下,设置标签可见,并把它移动回正确的位置 */
  .nav-links:hover .nav-links__label,
  .nav-links a:focus > .nav-links__label {
    opacity: 1;
    transform: translate(0);
  }
}

自定义的cubic-bezier()函数产生了一个弹跳特效:标签向右移动时,超出了停止位置,然后再回到最终位置停下来。运动曲线如图:

image.png

06交错显示过渡

用到transition-delay属性,为每个菜单项设置不同的延迟时间。这样就可以使每段动画交错飞入显示,不再一次性全部展示出来,就像翻滚的“波浪”:

image.png

.nav-links:hover .nav-links__label,
.nav-links a:focus > .nav-links__label {
  opacity: 1;
  transform: translate(0);
}
.nav-links > li:nth-child(2) .nav-links__label {
  transition-delay: 0.1s;
}
.nav-links > li:nth-child(3) .nav-links__label {
  transition-delay: 0.2s;
}
.nav-links > li:nth-child(4) .nav-links__label {
  transition-delay: 0.3s;
}
.nav-links > li:nth-child(5) .nav-links__label {
  transition-delay: 0.4s;
}

动画特性

有些变换看上去好像没有存在的必要,比如*移变换的结果,通常也可以使用相对定位来实现。实际上,变换在浏览器中的性能要好得多。如果我们要对元素的定位使用位移动画,可以明显感受到性能很差。如果我们要实现过渡或动画,无论什么类型,包括定位或大小操作,都应该尽可能考虑使用变换。要理解为什么需要这样做,我们需要先看看浏览器是如何渲染页面的。

渲染路径

浏览器计算好了页面上哪些样式应用于哪些元素上之后,需要把这些样式转化成屏幕上的像素,这个过程叫作渲染(rending)。渲染可以分为三个阶段:布局、绘制和合成:

image.png

1.布局

在第一个阶段布局中,浏览器需要计算每个元素将在屏幕上占多大空间。因为文档流的工作方式,所以一个元素的大小和位置可以影响页面上无数其他元素的大小和位置。这个阶段会解决这个问题。任何时候改变一个元素的宽度或高度,或者调整位置属性,元素的布局都会重新计算。如果使用JavaScript在DOM中插入或者移除元素,也会重新计算布局。一旦布局发生改变,浏览器就必须重排(reflow)页面,重新计算所有其他被移动或者缩放的元素的布局。

2.绘制

布局之后是绘制。这个过程就是填充像素:描绘文本,着色图片、边框和阴影。这不会真正显示在屏幕上,而是在内存中绘制。页面各部分生成了很多的图层(layers)。如果改变某个元素的背景颜色,就必须重新绘制它。改变背景颜色比改变元素大小需要的计算操作要少。某些条件下,页面元素会被提取到自己的图层。这时候,它会从页面的其他图层中独立出来单独绘制。浏览器把这个图层发送到计算机的图形处理器(graphicsprocessing unit, GPU)进行绘制,而不是像主图层那样使用主CPU绘制。这样安排是有好处的,因为GPU经过了充分的优化,比较适合做这类计算。这就是我们经常提到的硬件加速(hardware acceleration),因为需要依赖于计算机上的某些硬件来推进渲染速度。多个图层就意味着需要消耗更多的内存,但好处是可以加快渲染。

3.合成

在合成(composite)阶段,浏览器收集所有绘制完成的图层,并把它们提取为最终显示在屏幕上的图像。合成过程需要按照特定顺序进行,以确保图层出现重叠时,正确的图层显示在其他图层之上。opacity和transform这两个属性如果发生改变,需要的渲染时间就会非常少。当我们修改元素的这两个属性之一时,浏览器就会把元素提升到其自己的绘制图层并使用GPU加速。因为元素存在于自己的图层,所以整个图像变化过程中主图层将不会发生变化,也无须重复的重绘。如果只是对页面做一次性修改,那么通常不会感觉出这种优化可以带来明显的差异。但如果修改的是动画的一部分,屏幕需要在一秒内发生多达几十次的更新,这种情况下渲染速度就很重要了。大部分的屏幕每秒钟会刷新60次。理想情况下,动画中每次变化所需的重新计算也要至少这么快,才能在屏幕上生成最流畅的运动轨迹。浏览器在每次重新计算的时候需要做的事情越多,越难达到这种速度。

总结:处理过渡或者动画的时候,尽量只改变transform和opacity属性。如果有需要,可以修改那些只导致重绘而不会重新布局的属性。只有在没有其他替代方案的时候,再去修改那些影响布局的属性

3D变换

属性

我们可以像之前那样使用translate()函数,在水*和垂直方向上*移(X轴和Y轴)。也可以使用translateX()和translateY()函数实现同样的效果。下面两条声明会产生同样的效果。

transform;translate(15px,50px); 
transform:translatex(15px) translateY(50px):

同样可以使用translateZ()函数实现Z轴上的*移,相当于移动元素使其更靠*或远离用户。同样,也可以用rotateX()、rotateY()和rotateZ()使元素绕着三个不同维度的坐标轴进行旋转:

image.png

透视距离

为页面添加3D变换之前,我们需要先确定一件事情,即透视距离(perspective)。变换后的元素一起构成了一个3D场景。可以把透视距离想象成“摄像机”和场景之间的距离。如果镜头比较*(即透视距离小),那么3D效果就会比较强。如果镜头比较远(即透视距离大),那么3D效果就会比较弱。

image.png

可以通过两种方式指定透视距离:使用perspective()变换或者使用perspective属性。下面是一个示例,为四个元素添加旋转效果,设置相同的透视为perspective(200px),使用rotateX()让它们向后倾斜:

image.png

.row{
  display: flex;
  justify-content: center;
}
.box{
  box-sizing: border-box;
  width: 150px;
  margin: 1.5em;
  padding: 60px 0;
  text-align: center;
  background-color: salmon;
  transform: perspective(200px) rotateX(40deg);
}
<div class="row">
  <div class="box">one</div>
  <div class="box">two</div>
  <div class="box">three</div>
  <div class="box">four</div>
</div>

有时候我们希望多个元素共享统一的透视距离,就仿佛它们处于相同的3D空间中,它们都向着远方的一个相同的交汇点延伸。这时需要将透视举例放在父容器row中。这时候父容器包含的所有应用了3D变换的子元素,都将共享相同的透视距离。

image.png

.row{
  ...
  perspective:200px;
}
.box{
  ...
  transform: rotateX(40deg);
}

perspective-origin

默认情况下,透视距离的渲染是假设观察者(或者镜头)位于元素中心的正前方。perspective-origin属性可以上下、左右移动镜头的位置。可以使用关键字top、left、bottom、right和center,也可以使用百分比或者长度值,从元素的左上角开始计算(比如perspective-origin: 25%25%)。

image.png

.row{
  ...
  perspective:200px;
  perspective-origin: left bottom;
}
.box{
  ...
  transform: rotateX(20deg);
}

backface-visibility

如果你使用rotateX()或者rotateY()旋转元素超过90度,你会看到元素的背面,默认情况下背面是可见的,但我们可以为元素设置backface-visibility:hidden来改变它

transform-style(preserve-3d)

如果你要使用嵌套元素构建复杂的3D场景,transform-style属性就变得非常重要。如果接下来我们对容器元素进行3D旋转,由于实际上没有对整个场景进行旋转,只是旋转3D场景的2D照片,场景中的立体感也被破坏了:

image.png

为了改正这个问题,我们应该对父元素使用transform-style: preserve-3d

六、动画

有时候仅仅使用过渡还不够。过渡是直接从一个地方变换到另一个地方,相比之下,我们可能希望某个元素的变化过程是迂回的路径。为了对页面变化有更加精确的控制,CSS提供了关键帧动画

关键帧

关键帧(keyframe)是指动画过程中某个特定时刻。从原理上看,过渡其实和关键帧动画类似:我们定义第一帧(起始点)和最后一帧(结束点),浏览器计算中间值,使得元素可以在这些值之间*滑变换。而使用关键帧动画,我们就不再局限于只定义两个点,浏览器负责填充一个个点与点之间的值,直到最后一个关键帧,最终生成一系列无缝衔接的过渡。

image.png

核心概念

  • @keyframes定义动画
  • animation属性添加动画

下面创建一个简单的动画来熟悉一下语法。该动画是一个盒子变身向右移,再变色回到原来的位置:

.box {
  width: 100px;
  height: 100px;
  background-color: green;
  /* 2。应用动画 */
  animation: over-and-back 1.5s linear 3;
}

/* 1.定义动画 */
@keyframes over-and-back {
  0% {
    background-color: hsl(0, 50%, 50%);
    transform: translate(0);
  }
  50% {
    transform: translate(50px);
  }
  100% {
    background-color: hsl(270, 50%, 90%);
    transform: translate(0);
  }
}
<div class="box"></div>

基础属性

animation属性是好几个属性的简写。在这个演示中,我们实际上指定了以下四个属性

animation: over-and-back 1.5s linear 3;
  • animation-name(over-and-back):动画名称,与@keyframes规则定义的名称统一
  • animation-duration(1.5s):动画持续时间
  • animation-timing-function(linear):定时函数,用来描述动画如何加速和/或减速。可以是贝塞尔曲线或者关键字值,就像过渡使用的定时函数一样(ease-in、ease-out等)
  • animation-iteration-count(3):代表动画重复的次数。初始值默认是1

注意:浏览器对动画的支持情况比较好,仅有小部分移动浏览器需要使用-webkit-前缀,动画属性(-webkit-animation)和关键帧@规则(@-webkit-keyframes)都要用到。

示例

现在想让卡片逐个显示。在开始动画前,先放置三张卡片:(分支24)

image.png

body {
  background-color: wheat;
  font-family: Helvetica, Arial, sans-serif;
}
.container{
  display: flex;
  justify-content:space-around;
  margin: 200px 0;
}
.card {
  box-sizing: border-box;
  padding: 0.5em;
  background-color: white;
  max-width: 300px;
}
img {
  max-width: 100%;
}
<div class="container">
  <div class="card">
    <img src="images/01.png" alt="a chicken"/>
    <h4>图片1</h4>
    <p> She may be a bit frumpy, but Mrs Featherstone gets the job done. She lays her largish cream-colored eggs on a daily basis. She is gregarious to a fault.</p>
  </div>

  <div class="card">
    <img src="images/02.png" alt="a chicken"/>
    <h4>图片2</h4>
    <p> She may be a bit frumpy, but Mrs Featherstone gets the job done. She lays her largish cream-colored eggs on a daily basis. She is gregarious to a fault.</p>
  </div>

  <div class="card">
    <img src="images/03.png" alt="a chicken"/>
    <h4>图片3</h4>
    <p> She may be a bit frumpy, but Mrs Featherstone gets the job done. She lays her largish cream-colored eggs on a daily basis. She is gregarious to a fault.</p>
  </div>
</div>

动画效果1:由远垂直于*面的透明卡片 变成 *处*行于*面的卡片

.card {
  ...
  /* 2.添加动画 */
  animation:fly-in 600ms ease-in;
}

/* 1.定义动画 */
@keyframes fly-in {
  0% {transform: translateZ(-800px) rotateY(90deg);opacity: 0;}
  56% {transform: translateZ(-160px) rotateY(87deg);opacity: 1;}
  100% {transform: translateZ(0) rotateY(0);}
}

动画延迟

  • animation-delay:推迟动画开始的时间。属性行为和transition-delay类似

    动画效果2:逐个展示

    .card {
    animation:fly-in 600ms ease-in;
    }
    .card:nth-child(2){
    animation-delay: 0.15s;
    }
    .card:nth-child(3){
    animation-delay: 0.3s;
    }
    

填充模式

  • animation-fill-mode:在动画播放前或播放后应用动画样式。

    动画效果3:暂停在第一帧

    上面的动画会发现有个bug,有些元素提前展示在了页面上,等了一段时间才播放,而我们希望它们一开始就是透明的。这个问题的出现是因为transform和opacity属性只应用在了动画执行期间。动画开始的时候,它们瞬间变成0%关键帧上应用的属性值。我们需要把动画样式后向填充设置,就像一直暂停在第一帧,直到动画开始播放。可以使用animation-fill-mode属性来实现

    这里的深色盒子代表动画的持续时间。animation-fill-mode的初始值是none,意思是动画执行前或执行后动画样式都不会应用到元素上。如果设置animation-fill-mode:backwards,表示在动画执行之前,浏览器就会取出动画中第一帧的值,并把它们应用在元素上;使用forwards会在动画播放完成后仍然应用最后一帧的值;使用both会同时向前和向后填充。

image.png

.card {
  ...
  animation-fill-mode: backwards;
}

反馈示例

image.png

body {font-family: Helvetica, Arial, sans-serif;}
form {max-width: 500px;}
label,textarea {
  display: block;
  margin-bottom: 1em;
}
textarea {
  width: 100%;
  font-size: inherit;
}
button {
  padding: 0.6em 1em;
  border: 0;
  background-color: hsl(220, 50%, 50%);
  color: white;
  font: inherit;
  transition: background-color 0.3s linear;
}
button:hover {
  background-color: hsl(220, 45%, 40%);
}
<form>
  <label for="trip">Tell us about your first trip to the zoo:</label>
  <textarea id="trip" name="about-my-trip" rows="5"></textarea>
  <button type="submit" id="submit-button">Save</button>
</form>

用户点击Save按钮的时候,它会发送数据到服务器,而网络连接需要时间。如果用户在等待响应的时候,可以有一些形象的指示告诉他们内容已经提交了。通常情况下我们会使用动画来提供这种指示。现在修改Save按钮,增加一种“正在加载”的状态。这时候按钮文字隐藏起来,用一个旋转图标代替:

image.png

点击Save按钮的时候,输入功能是禁止的,is-loading类添加到了按钮上,旋转指示器会显示出来。这里只是为了演示,刷新页面就可以重置表单和移除is-loading类

button.is-loading {
  position: relative;
  /* 隐藏文字按钮 */
  color: transparent;
}
button.is-loading::after {
  position: absolute;
  content: "";
  display: block;
  width: 1.4em;
  height: 1.4em;
  /* 定位伪元素 */
  top: 50%;
  left: 50%;
  margin-left: -0.7em;
  margin-top: -0.7em;
  border-top: 2px solid white;
  border-radius: 50%;
  /* 重复循环旋转动画 */
  animation: spin 0.5s linear infinite;
}
@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
<script type="text/javascript">
  var input = document.getElementById('trip');
  var button = document.getElementById('submit-button');
  button.addEventListener('click', function(event) {
    event.preventDefault();
    button.classList.add('is-loading');
    button.disabled = true;
    input.disabled = true;
  });
</script>
posted @ 2020-09-19 12:37  sanhuamao  阅读(274)  评论(0编辑  收藏  举报