用 CSS3 实现一个 Webpack 的 logo 动画

3,700

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 9 天,点击查看活动详情

原文来自我的个人博客

前言

先直接展示下最终效果,想看代码的可以直接拉到最底部

4.gif

webpack 官网上的截图:

image.png

分析:webpack 这个 logo 的实现思路很简单,其实就是绘制了两个正方形,外面一个里面一个,然后让两个正方形按照不同的方向旋转即可

所以我们又可以把目标变成:如何实现一个正方形

css 中实现一个正方形,就不得不先讲一下 css3 transform

1. CSS3 Transform 介绍

CSS3 Transform 属性不但允许你进行 2D 的旋转,缩放或平移指定的元素,还支持 3D 变换元素。

常见的函数 transform function 有:

  • 平移:translate3d(tx, ty, tz)translateX(tx)translateY(ty)translateZ(tz)
  • 缩放:scale3d(sx, sy, sz)scaleX(sy)scaleY(sy)scaleZ(sz)
  • 旋转:rotate3d(x, y, z, a)rotateX(x)rotateY(y)rotateZ(z)

◼ 通过上面的几个函数,我们可以改变某个元素的 3D 形变。 ◼ 3D 形变函数会创建一个合成层来启用GPU硬件加速,比如: translate3dtranslateZscale3drotate3d ...

1.1. 3D旋转

1. rotateZ 、rotateX、rotateY

  • 旋转:rotateX(deg)rotateY(deg)rotateZ(deg)
    • CSS 函数定义一个变换,它将元素围绕固定轴旋转。旋转量由指定的角度确定; 为正,旋转将为顺时针,为负,则为逆时针。
  • 值个数
    • 只有一个值,表示旋转的角度(单位 deg
  • 值类型:
    • deg:<angle> 类型,表示旋转角度(不是弧度)。
    • 正数为顺时针
    • 负数为逆时针
  • 简写:rotate3d(x, y, z, deg)
  • 注意:旋转的原点受 transform-origin 影响

image.png

2. rotate3d

  • 旋转:rotate3d(x, y, z, a)
    • CSS 函数定义一个变换,它将元素围绕固定轴旋转。旋转量由指定的角度定义; 为正,运动将为顺时针,为负,则为逆时针。
  • 值个数
    • 一个值时,表示 z 轴 旋转的角度
    • 四个值时,表示在 3D 空间之中,旋转有 x , y , z 个旋转轴和一个旋转角度。
  • 值类型:
    • x:<number> 类型,可以是 0 到 1 之间的数值,表示旋转轴 X 坐标方向的矢量(用来计算形变矩阵中的值)。
    • y:<number> 类型,可以是 0 到 1 之间的数值,表示旋转轴 Y 坐标方向的矢量。
    • z:<number> 类型,可以是 0 到 1 之间的数值,表示旋转轴 Z 坐标方向的矢量。
    • a:<angle> 类型,表示旋转角度。正的角度值表示顺时针旋转,负值表示逆时针旋转。
  • 注意:旋转的原点受 transform-origin 影响

1.2. 3D透视

  • 透视:perspective
    • 定义了观察者与 z=0 平面的距离,使具有三维位置变换的元素产生透视效果(z 表示Z 轴)。
    • z>0 的三维元素比正常的大,而 z<0 时则比正常的小,大小程度由该属性的值决定。
  • 值个数
    • 只有一个值,表示观察者距离 z=0 的平面距离 和 none
  • 必须是 <none> <length> 中的一个
    • none:没有应用 perspective 样式时的默认值。
    • length:定观察者距离 z=0 平面的距离(如下图 d 的距离,单位 px)。 ✓ 为元素及其内容应用透视变换。当值为 0 或负值时,无透视变换。
  • 透视的两种使用方式:
    1. 在父元素上定义 CSS 透视属性
    2. 如果它是子元素或单元素子元素,可以使用函数 perspective()
  • 透视演练场:

image.png

1.3. 3D位移

1. translateX、translateY、translateZ

  • 平移:translateX(x)translateY(y)translateZ(z)
    • 该函数表示在二、三维平面上移动元素。
  • 值个数
    • 只有一个值,设置对应轴上的位移
  • 值类型:
    • 数字:100px
    • 百分比:参照元素本身( refer to the size of bounding box )

2. translate3d

平移:translate3d(tx, ty, tz)

  • CSS 函数在 3D 空间内移动一个元素的位置。这个移动由一个三维向量来表达,分别表示他在三个方向上移动的距离。
  • 值个数
    • 三个值时,表示在 3D 空间之中, tx , ty , tz 分别表示他在三个方向上移动的距离。
  • 值类型:
    • tx:是一个 <length> 代表移动向量的横坐标。
    • ty:是一个 <length> 代表移动向量的纵坐标。
    • tz:是一个 <length> 代表移动向量的 z 坐标。它不能是 <percentage> 值;那样的移动是没有意义的。
  • 注意:
    • translateX(tx)等同于 translate(tx, 0) 或者 translate3d(tx, 0, 0)
    • translateY(ty) 等同于 translate(0, ty) 或者 translate3d(0, ty, 0)
    • translateZ(zx) 等同于 translate3d(0, 0, tz)

1.4. 3D缩放

1. scaleX、scaleY、scaleZ

  • 缩放:scaleXscaleYscaleZ
    • 函数指定了一个沿 xyz 轴调整元素缩放比例因子。
  • 值个数
    • 一个值时,设置对应轴上的缩放(无单位)
  • 值类型:
    • 数字: ✓ 1:保持不变 ✓ 2:放大一倍 ✓ 0.5:缩小一半
    • 百分比:不支持百分比

2. scaleX、scaleY、scaleZ

  • 缩放:scale3d(sx, sy,sz)
    • CSS 函数定义了在 3D 空间中调整元素的缩放比例因子 。。
  • 值个数
    • 三个值时,表示在 3D 空间之中, sx, sy, sz 分别表示他在三个方向上缩放的向量。
  • 值类型:
    • sx:是一个 <number> 代表缩放向量的横坐标。
    • sy:是一个 <number> 表示缩放向量的纵坐标。
    • sz:是 <number> 表示缩放向量的 z 分量的 a(再讲到 3D 正方体再演示)。
  • 注意:
    • scaleX(sx) 等价于 scale(sx, 1)scale3d(sx, 1, 1)
    • scaleY(sy) 等价于 scale(1, sy)scale3d(1, sy, 1)
    • scaleZ(sz) 等价于 scale3d(1, 1, sz)

1.5. 3D空间

  • 变换式:transform-style
    • 该CSS属性用于设置元素的子元素是定位在 3D 空间中还是平展在元素的2D平面中。
    • 在3D空间中同样是可以使用透视效果。
  • 值类型:
    • flat:指示元素的子元素位于元素本身的平面内。
    • preserve-3d:指示元素的子元素应位于 3D 空间中。

image.png

  • 背面可见性:backface-visibility
    • 该CSS 属性 backface-visibility 指定某个元素当背面朝向观察者时是否可见。
  • 值类型:
    • visible:背面朝向用户时可见。
    • hidden:背面朝向用户时不可见。

image.png

2. 先实现一个正方形的骰子

先将骰子的六个面绘制出来,这里我用的 grid 布局,用 flex 也可以的

body {
  margin: 0;
  width: 100vw;
  height: 100vh;
  position: relative;
}
.dice-wrapper {
  position: relative;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 200px;
  height: 200px;
  display: flex;
  justify-content: space-around;
  align-items: center;
}
.dice-face {
  background-color: #fff;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border: 2px solid #000;
  border-radius: 10%;
  display: grid;
  place-items: center;
  grid-template: repeat(3, 1fr) / repeat(3, 1fr);
  grid-template-areas:
    "a . c"
    "e g f"
    "d . b";
}

.dot {
  display: inline-block;
  width: 50%;
  height: 50%;
  border-radius: 50%;
  background-color: #000;
}

.dot:nth-child(2) {
  grid-area: b;
}

.dot:nth-child(3) {
  grid-area: c;
}

.dot:nth-child(4) {
  grid-area: d;
}

.dot:nth-child(5) {
  grid-area: e;
}

.dot:nth-child(6) {
  grid-area: f;
}

.dot:nth-child(odd):last-child {
  grid-area: g;
}
<div class="dice-wrapper">
  <div class="dice-face first-face">
    <span class="dot"></span>
  </div>
  <div class="dice-face second-face">
    <span class="dot"></span>
    <span class="dot"></span>
  </div>
  <div class="dice-face third-face">
    <span class="dot"></span>
    <span class="dot"></span>
    <span class="dot"></span>
  </div>
  <div class="dice-face fourth-face">
    <span class="dot"></span>
    <span class="dot"></span>
    <span class="dot"></span>
    <span class="dot"></span>
  </div>
  <div class="fifth-face dice-face">
    <span class="dot"></span>
    <span class="dot"></span>
    <span class="dot"></span>
    <span class="dot"></span>
    <span class="dot"></span>
  </div>
  <div class="dice-face sixth-face">
    <span class="dot"></span>
    <span class="dot"></span>
    <span class="dot"></span>
    <span class="dot"></span>
    <span class="dot"></span>
    <span class="dot"></span>
  </div>
</div>

image.png

现在将 dice-wrapper 稍作修改:

/* 在父元素中添加 transform-style来启用3D空间 */
transform-style: preserve-3d;
transform: rotateX(-33.5deg) rotateY(45deg);

此时六个面的效果都是这样的:

image.png

接下来我们一个个调整位置

第一个面

.first-face {
  transform: rotateX(90deg) translateZ(100px);
}

image.png

第二个面

.second-face {
  transform: rotateX(-90deg) translateZ(100px);
}

image.png

**第三个和第四个面 **

.third-face {
  transform: rotateY(0deg) translateZ(100px);
}
.fourth-face {
  transform: rotateY(-180deg) translateZ(100px);
}

image.png

最后两个面

.fifth-face {
  transform: rotateY(-90deg) translateZ(100px);
}
.sixth-face {
  transform: rotateY(90deg) translateZ(100px);
}

image.png

3. 实现 Webpack logo

有了上面实现正方形的基础,我们终于可以来实现这个 logo 啦。

3.1 实现基本框架 v1 版本

  1. css 部分
body {
  padding: 0;
  margin: 0;
  width: 100%;
  height: 100%;
  background-color: #2b3a42;

  display: flex;
  justify-content: center;
  align-items: center;
}

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.webpack-logo {
  width: 100%;
  height: 200px;
  /* border: 1px solid red; */

  position: relative;
}
  1. html 部分
<div class="webpack-logo">
  <!-- cube-inner -->
  <ul class="cube-inner">
    <li class="top"></li>
    <li class="bottom"></li>
    <li class="front"></li>
    <li class="back"></li>
    <li class="left"></li>
    <li class="right"></li>
  </ul>

  <!-- cube-outer -->
  <ul class="cube-outer">
    <li class="top"></li>
    <li class="bottom"></li>
    <li class="front"></li>
    <li class="back"></li>
    <li class="left"></li>
    <li class="right"></li>
  </ul>
</div>

上面两部分都很简单:

  1. cube-inner 作为里面的正方
  2. cube-outer 作为外面的正方形。
  3. 两个正方形都有 top bottom front back left right 六个面

3.2 绘制两个正方形

根据第 2 节中的知识绘制两个正方形,要注意的是两个正方形的大小控制好两倍,我这里设置的是 50px 100px

添加 css

.cube-inner {
  position: absolute;
  left: 50%;
  top: 50%;
  /* 关键,不要用 transform */
  margin: -25px 0px 0px -25px;
  width: 50px;
  height: 50px;
  /* background-color: red; */

  /* 启用3D空间 */
  transform-style: preserve-3d;
  transform: rotateX(-33.5deg) rotateY(45deg);
  /* 帧动画 */
  animation: innerLoop 4s ease-in-out infinite;
}

.cube-inner li {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: #175d96;
  border: 1px solid white;
}

.cube-inner .top {
  transform: rotateX(90deg) translateZ(25px);
}

.cube-inner .bottom {
  transform: rotateX(-90deg) translateZ(25px);
}

.cube-inner .front {
  transform: rotateX(0deg) translateZ(25px);
}

.cube-inner .back {
  transform: rotateX(-180deg) translateZ(25px);
}
.cube-inner .left {
  transform: rotateY(-90deg) translateZ(25px);
}

.cube-inner .right {
  transform: rotateY(90deg) translateZ(25px);
}

/* cube outer */
.cube-outer {
  position: absolute;
  left: 50%;
  top: 50%;
  /* 关键,不要用 transform */
  margin: -50px 0px 0px -50px;
  width: 100px;
  height: 100px;

  /* 启用3D空间 */
  transform-style: preserve-3d;
  transform: rotateX(-33.5deg) rotateY(45deg);
  /* 帧动画 */
  animation: outerLoop 4s ease-in-out infinite;
}

.cube-outer li {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(141, 214, 249, 0.5);
  border: 1px solid white;
}

.cube-outer .top {
  transform: rotateX(90deg) translateZ(50px);
}

.cube-outer .bottom {
  transform: rotateX(-90deg) translateZ(50px);
}

.cube-outer .front {
  transform: rotateX(0deg) translateZ(50px);
}

.cube-outer .back {
  transform: rotateX(-180deg) translateZ(50px);
}
.cube-outer .left {
  transform: rotateY(-90deg) translateZ(50px);
}

.cube-outer .right {
  transform: rotateY(90deg) translateZ(50px);
}

3.3 添加动画

要让两个正方形转起来,可以使用 keyframes 帧动画

添加 css

@keyframes outerLoop {
  0% {
    transform: rotateX(-33.5deg) rotateY(45deg);
  }

  50%,
  100% {
    transform: rotateX(-33.5deg) rotateY(405deg);
  }
}

@keyframes innerLoop {
  0% {
    transform: rotateX(-33.5deg) rotateY(45deg);
  }

  50%,
  100% {
    transform: rotateX(-33.5deg) rotateY(-315deg);
  }
}

OK,大功告成。