• 技术文章 >web前端 >css教程

    手把手带你使用纯CSS绘制一个中国结,并添加动画效果!

    青灯夜游青灯夜游2022-02-18 09:03:58转载232
    本篇文章手把手教大家,一步步使用纯CSS绘制一个中国结,并给这个中国结添加红包雨动画效果,希望对大家有所帮助!

    春节是中国人最重要的节日,春节期间的习俗也非常多,东西南北各不相同。 为了增添年味,过年时家家户户会置办各种年货和装饰品,把家里营造得红红火火,红灯笼,红对联,红福字,以及红色的中国结。

    1.png

    中国结原材料是简简单单的红绳,经过古人的巧妙构思,编织成一个菱形网格的样子。网格上的绳线紧紧连接在一起,象征一家人团结和睦,幸福美满。

    2.png

    那么如何用CSS来实现一个中国结呢? 先看最终效果。

    3.png

    4.gif

    在线预览Codepen地址:
    https://codepen.io/cliea/pen/LYOPbBr

    如此 Amazing 的效果,你也可以做出来,下面让我们开始吧!

    一、编码之前

    1. 搜集素材,越简洁越好

    先从网上搜一张中国结的图片,中国结的样式不止一种,我们选择一种最经典的中国结的编织样式。 图片的质量决定了最后成品的质量,下面就是一张比较整洁,结构清晰的中国结图片。供我们写CSS时参考使用。

    5.png

    2. 观察细节,构思实现的可能性

    有了图片就可以开始写代码了吗?当然不是。

    首先回想一下现在要做的事:用CSS画个中国结。

    你真的想好了吗?这是一个可以实现的目标吗?想象一下,当你的领导给你布置一个任务:让手机壳根据APP的主题色而变色。你会直接开始写代码吗?

    你会想两个问题:

    这是一个比较极端的例子,上面两条都无法实现。回到CSS和这张中国结的图片。我们首先要想的是,我们应该用哪些CSS技术,来实现这个图片。你现在回过头仔细观察一下上面的图片。

    经过短暂的观察,我们发现这样一些要点

    然后就是预想一下实现原理

    3. 结构拆分,化整为零

    上面是从技术角度从整体观察,下面就是对整个图片进行拆分,先确定其 html 结构。

    6.png

    7.png

    8.png

    9.png

    10.png

    11.png

    这样我们得到了 html 的结构

    <div class="chinese-knot">
      <div class="grid"></div>
      <div class="ring-small">
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
      </div>
      <div class="ring-big">
        <i><b></b></i>
        <i><b></b></i>
      </div>
      <div class="cross-node">
        <div class="node">
          <i></i>
          <i></i>
          <i></i>
          <i></i>
        </div>
        <div class="node">
          <i></i>
          <i></i>
          <i></i>
          <i></i>
        </div>
      </div>
      <div class="header">
        <i></i>
        <b></b>
        <span></span>
      </div>
      <div class="footer">
        <b></b>
        <b></b>
        <div class="tassels">
          <i></i>
          <i></i>
        </div>
      </div>
    </div>

    实际编码当中,html 并不是一次写成,而是经过不断调整才成为上面这个样子。

    二、CSS逐个实现中国结部件

    1. 网格

    网格最终效果是个菱形,也就是正方形旋转了45deg,我们先不旋转,看看它是什么样子

    12.png

    先设定一个变量,表示绳子的宽度,我们设为--width,这个尺寸很重要,后面所有尺寸都是基于这个宽度,这样后面我们调整整个图形的大小,只要改这一个--width就行了。

    :root {
      --width: 1.7vh;
    }

    垂直和水平都各有11根绳,绳子之间的间隙约为绳子宽度的 0.5 倍,所以可以得到网格的宽高都为 11 + 0.5 * 10 = 16 倍的绳子宽度,所以我们可以这样写:

    :root {
      --width: 1.7vh;
      --grid-width: calc(var(--width) * 16);
    }
    .grid {
      width: var(--grid-width);
      height: var(--grid-width);
    }

    body 加上一些样式,让盒子居中,再加一个深色背景

    body{
      margin: 0;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      background: #1d1e22;
      overflow: hidden;
    }

    再给 .grid 也加一个白色背景色,测试一下:

    13.png

    这样屏幕正中间就出现了一个白色方块,下面我们把白色背景改成11根线的样式:

    :root{
      --width: 1.7vh;
      --red-1: #f40001;
      --red-2: #d40000;
      --red-3: #8c0703;
      --rope: 
        var(--red-3), 
        var(--red-2) calc(var(--width) * 0.25), 
        var(--red-1) calc(var(--width) * 0.45), 
        var(--red-1) calc(var(--width) * 0.55), 
        var(--red-2) calc(var(--width) * 0.75), 
        var(--red-3) var(--width);
      --grid-width: calc(var(--width) * 16);
      --bg-line: linear-gradient(90deg, var(--rope), transparent var(--width)) 0 0 / calc(var(--width) * 1.5) calc(var(--width) * 1.5);
    }
    .grid{
      width: var(--grid-width);
      height: var(--grid-width);
      background: var(--bg-line);
    }

    就得到了下面的效果:

    14.png

    可能你有点蒙圈。发生了什么事情?

    还是让事情变得简单点,我们先画一根不带渐变的红线:

    .grid{
      background: linear-gradient(
        90deg, 
        var(--red-1), 
        var(--red-1) var(--width), 
        transparent var(--width)
      );
    }

    15.png

    先是一个线性渐变 linear-gradient,然后旋转角度设为90deg,让它从左到右渐变(默认是从下往上),然后起始值设为--red-1(你问我red-1red-3哪来的?效果图上吸来的),在--width处也设置为--red-1,这样就得到了一根宽为 --width 的红线。但这还没完,得接着在--width 处加一个透明transpanrent,这样从--width直到图形的最右侧就都不填充颜色了。

    但这不太像根绳子,让红线渐变起来:

    .grid{
      background: linear-gradient(
        90deg, 
        var(--red-3), 
        var(--red-2) calc(var(--width) * 0.25), 
        var(--red-1) calc(var(--width) * 0.45), 
        var(--red-1) calc(var(--width) * 0.55), 
        var(--red-2) calc(var(--width) * 0.75), 
        var(--red-3) var(--width), 
        transparent var(--width)
      );
    }

    16.png

    这样就得到了一根有一点点立体效果的绳子。可是怎么让它横向重复11次,并且间隔0.5倍的--width呢?看下面的代码:

    .grid{
      background: linear-gradient(
        90deg, 
        var(--red-3), 
        var(--red-2) calc(var(--width) * 0.25), 
        var(--red-1) calc(var(--width) * 0.45), 
        var(--red-1) calc(var(--width) * 0.55), 
        var(--red-2) calc(var(--width) * 0.75), 
        var(--red-3) var(--width), 
        transparent var(--width)
      ) 0 0 / calc(var(--width) * 1.5) calc(var(--width) * 1.5);
    }

    大家来找茬:这段代码和上一段有什么不同?眼尖的你可能已经看出来了,多了这一行:

    0 0 / calc(var(--width) * 1.5) calc(var(--width) * 1.5)

    /为分界线,左边的含义是background-positoin,右边的含义是background-size

    0 0也就是左上角。calc(var(--width) * 1.5) calc(var(--width) * 1.5) 也就是一个正方形,宽度为1.5倍绳宽。

    17.png

    这样一个小方块,在垂直和水平方向上重复,就得了我们想要的结果:

    18.png

    可是我们想要的是网格,现在顶多也就算个栅格。

    那就使用伪类复制一份,并且旋转90deg

    :root{
      --width: 1.7vh;
      --red-1: #f40001;
      --red-2: #d40000;
      --red-3: #8c0703;
      --rope: 
        var(--red-3), 
        var(--red-2) calc(var(--width) * 0.25), 
        var(--red-1) calc(var(--width) * 0.45), 
        var(--red-1) calc(var(--width) * 0.55), 
        var(--red-2) calc(var(--width) * 0.75), 
        var(--red-3) var(--width);
      --grid-width: calc(var(--width) * 16);
      --bg-line: linear-gradient(90deg, var(--rope), transparent var(--width)) 0 0 / calc(var(--width) * 1.5) calc(var(--width) * 1.5);
    }
    .grid {
      width: var(--grid-width);
      height: var(--grid-width);
      background: var(--bg-line);
      &:after {
        content: "";
        display: block;
        width: var(--grid-width);
        height: var(--grid-width);
        background: var(--bg-line);
        transform: rotate(90deg);
      }
    }

    19.png

    对比一下参考图片:

    20.png

    不能说完全不相干,但是人家一看就经过了能工巧匠的编织,咱们这只能算简单的叠加,怎么才能让上面变成下面呢?

    经过仔细的观察,发现只要把上面一层横着的线,稍加一些遮挡就能实现交叉编织的效果。用哪个css属性实现呢?那就只有mask 了。

    下图蓝色框是需要遮挡的部分,绿色框是需要重复的部分。

    21.png

    仔细分析一下绿框的构成:

    22.png

    本质上是在一个3×3的正方形上挖两个1×1的小洞,位置分别是0 01.5 1.5。我们要如何画这样一张图?并把这张图应用到mask上呢?

    mask是通过传入的图片进行遮罩处理,而背景图除了传入一张png以外,CSS还内置了几个生成背景图的函数:

    这些函数都可以和mask配合。这里我们使用conic-gradient实现上面的图形。

    conic-gradient 实现上图,思路要反着来:不是在方形上挖孔,而是用多个矩形将要渲染的部分填充颜色,剩下的部分自然就是透明的:

    23.png

    CSS实现如下:

    :root{
        ...
        --conic: #000 0 90deg, transparent 0 100%;
    }
    
    .grid {
      ...
      &:after {
        ...
        -webkit-mask: conic-gradient(from 0deg at var(--width) calc(var(--width) * 1.5), var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3),
          conic-gradient(from 90deg at calc(var(--width) * 2.5) 0, var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3),
          conic-gradient(from 180deg at calc(var(--width) * 1.5) var(--width), var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3),
          conic-gradient(from 90deg at 0 calc(var(--width) * 2.5), var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3);
      }
    }

    预览效果

    24.png

    目前为止完整代码

    :root{
      --width: 1.7vh;
      --red-1: #f40001;
      --red-2: #d40000;
      --red-3: #8c0703;
      --rope: 
        var(--red-3), 
        var(--red-2) calc(var(--width) * 0.25), 
        var(--red-1) calc(var(--width) * 0.45), 
        var(--red-1) calc(var(--width) * 0.55), 
        var(--red-2) calc(var(--width) * 0.75), 
        var(--red-3) var(--width);
      --grid-width: calc(var(--width) * 16);
      --bg-line: linear-gradient(90deg, var(--rope), transparent var(--width)) 0 0 / calc(var(--width) * 1.5) calc(var(--width) * 1.5);
      --conic: #000 0 90deg, transparent 0 100%;
    }
    body{
      margin: 0;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      background: #1d1e22;
      overflow: hidden;
    }
    .grid {
      width: var(--grid-width);
      height: var(--grid-width);
      background: var(--bg-line);
      &:after {
        content: "";
        display: block;
        width: var(--grid-width);
        height: var(--grid-width);
        background: var(--bg-line);
        transform: rotate(90deg);
        -webkit-mask: conic-gradient(from 0deg at var(--width) calc(var(--width) * 1.5), var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3),
          conic-gradient(from 90deg at calc(var(--width) * 2.5) 0, var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3),
          conic-gradient(from 180deg at calc(var(--width) * 1.5) var(--width), var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3),
          conic-gradient(from 90deg at 0 calc(var(--width) * 2.5), var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3);
      }
    }
    <div class="grid"></div>

    没错,这个图形,只用了.grid这一个标签!

    但是只有网格还不够,让我们继续。

    2. 半圆环

    回头看一下参考图:

    25.png

    嗯,环形渐变,那就是radial-gradient了:

      <div class="ring-small">
        <i></i>
      </div>
    .ring-small {
      i {
        position: absolute;
        width: calc(var(--width) * 2.5);
        height: calc(var(--width) * 1.5);
        background: radial-gradient(
            circle at 50% 100%, 
            transparent calc(var(--width) * 0.25), 
            var(--red-3) calc(var(--width) * 0.25), 
            var(--red-2) calc(var(--width) * (0.25 + 0.25)),
            var(--red-1) calc(var(--width) * (0.25 + 0.45)), 
            var(--red-1) calc(var(--width) * (0.25 + 0.55)), 
            var(--red-2) calc(var(--width) * (0.25 + 0.75)),
            var(--red-3) calc(var(--width) * (0.25 + 1)), 
            transparent calc(var(--width) * (0.25 + 1))
        );
      }
    }

    26.png

    这样就得到了半个环形图,让我们使用定位把它和网格结合看看

    /* 先给最外层加个相对定位,后面的绝对定位都相对这一层 */
    .chinese-knot {
      width: var(--grid-width);
      height: var(--grid-width);
      position: relative;
    }
    .ring-small {
      i {
        position: absolute;
        top: calc(var(--width) * -1.5);
        left: calc(var(--width) * 3);
      }
    }

    27.png

    对比素材图,发现环形不是直接紧贴在网格上的,而是先延伸了一小段直线,再接的曲线。那我们就给它增个高吧:

    .ring-small {
      i {
        &:before,
        &:after {
          content: "";
          position: absolute;
          bottom: calc(var(--width) * -0.5 + 1px);
          width: var(--width);
          height: calc(var(--width) * 0.5);
          background: var(--bg-line);
        }
        &:after {
          right: 0;
        }
      }
    }

    上面使用两个伪类,为半圆环加了两截高度为 0.5--width的增高垫,效果如下图

    28.png

    接着复制16个这样的图形,分别定位到各自的位置上:

    <div class="chinese-knot">
      <div class="grid"></div>
      <div class="ring-small">
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
        <i></i>
      </div>
    </div>
    .ring-small {
      i {
        position: absolute;
        width: calc(var(--width) * 2.5);
        height: calc(var(--width) * 1.5);
        background: radial-gradient(
            circle at 50% 100%, 
            transparent calc(var(--width) * 0.25), 
            var(--red-3) calc(var(--width) * 0.25), 
            var(--red-2) calc(var(--width) * (0.25 + 0.25)),
            var(--red-1) calc(var(--width) * (0.25 + 0.45)), 
            var(--red-1) calc(var(--width) * (0.25 + 0.55)), 
            var(--red-2) calc(var(--width) * (0.25 + 0.75)),
            var(--red-3) calc(var(--width) * (0.25 + 1)), 
            transparent calc(var(--width) * (0.25 + 1))
        );
        &:before,
        &:after {
          content: "";
          position: absolute;
          bottom: calc(var(--width) * -0.5 + 1px);
          width: var(--width);
          height: calc(var(--width) * 0.5);
          background: var(--bg-line);
        }
        &:after {
          right: 0;
        }
        &:nth-child(-n + 4) {
          top: calc(var(--width) * -2 + 2px);
        }
        &:nth-child(1) {
          left: calc(var(--width) * 3);
        }
        &:nth-child(2) {
          left: calc(var(--width) * 6);
        }
        &:nth-child(3) {
          left: calc(var(--width) * 9);
        }
        &:nth-child(4) {
          left: calc(var(--width) * 12);
        }
        &:nth-child(-n + 8):nth-child(n + 5) {
          bottom: calc(var(--width) * -2 + 2px);
          transform: rotate(180deg);
        }
        &:nth-child(5) {
          left: calc(var(--width) * 1.5);
        }
        &:nth-child(6) {
          left: calc(var(--width) * 4.5);
        }
        &:nth-child(7) {
          left: calc(var(--width) * 7.5);
        }
        &:nth-child(8) {
          left: calc(var(--width) * 10.5);
        }
        &:nth-child(-n + 12):nth-child(n + 9) {
          left: calc(var(--width) * -2.5 + 2px);
          transform: rotate(-90deg);
        }
        &:nth-child(9) {
          top: calc(var(--width) * 3.5);
        }
        &:nth-child(10) {
          top: calc(var(--width) * 6.5);
        }
        &:nth-child(11) {
          top: calc(var(--width) * 9.5);
        }
        &:nth-child(12) {
          top: calc(var(--width) * 12.5);
        }
        &:nth-child(-n + 16):nth-child(n + 13) {
          right: calc(var(--width) * -2.5 + 2px);
          transform: rotate(90deg);
        }
        &:nth-child(13) {
          top: calc(var(--width) * 2);
        }
        &:nth-child(14) {
          top: calc(var(--width) * 5);
        }
        &:nth-child(15) {
          top: calc(var(--width) * 8);
        }
        &:nth-child(16) {
          top: calc(var(--width) * 11);
        }
      }
    }

    就得到了这样的效果

    29.png

    哈哈,很像下水管道~

    3. 四分之三圆环

    还是先看素材:

    30.png

    嗯,不得不怀疑网易云的 LOGO 的灵感是不是就是中国结。

    30-1.png

    单个环形已经实现了,两个环也不难吧:

    <div class="ring-big">
        <i><b></b></i>
    </div>
    .ring-big {
      i {
        position: absolute;
        width: calc(var(--width) * 6);
        height: calc(var(--width) * 6);
        b {
          position: absolute;
          left: 0;
          top: 0;
          width: 100%;
          height: 100%;
          background: radial-gradient(
            circle at 50% 50%,
            transparent calc(var(--width) * 0.5),
            var(--red-3) calc(var(--width) * 0.5),
            var(--red-2) calc(var(--width) * (0.5 + 0.25)),
            var(--red-1) calc(var(--width) * (0.5 + 0.45)),
            var(--red-1) calc(var(--width) * (0.5 + 0.55)),
            var(--red-2) calc(var(--width) * (0.5 + 0.75)),
            var(--red-3) calc(var(--width) * (0.5 + 1)),
            transparent calc(var(--width) * (0.5 + 1)),
            transparent calc(var(--width) * 2),
            var(--red-3) calc(var(--width) * 2),
            var(--red-2) calc(var(--width) * (2 + 0.25)),
            var(--red-1) calc(var(--width) * (2 + 0.45)),
            var(--red-1) calc(var(--width) * (2 + 0.55)),
            var(--red-2) calc(var(--width) * (2 + 0.75)),
            var(--red-3) calc(var(--width) * (2 + 1)),
            transparent calc(var(--width) * (2 + 1))
          );
        }
      }
    }

    31.png

    为什么 <i> 标签里要再套一个标签呢,因为接下来我们要执行 clip-path,还要给圆环增高,而clip-path 会给增高的部分也裁剪掉,所以只能再套一层,让内层的 <b> 自己 clip,增高则使用 <i> 的伪类实现。下面就是将圆环右下角 1/4 裁剪掉并且加一个增高垫的代码:

    .ring-big {
      i {
        ...
        b {
          ...
          clip-path: polygon(0 0, 100% 0, 100% 50%, 50% 50%, 50% 100%, 0 100%);
        }
        &:before,
        &:after {
          content: "";
          position: absolute;
          top: calc(var(--width) * 3 - 1px);
          left: calc(var(--width) * 3.5);
          width: calc(var(--width) * 2.5);
          height: calc(var(--width) * 0.5 + 2px);
          background: repeating-linear-gradient(
            90deg, 
            var(--red-3), 
            var(--red-2) calc(var(--width) * 0.25), 
            var(--red-1) calc(var(--width) * 0.45), 
            var(--red-1) calc(var(--width) * 0.55), 
            var(--red-2) calc(var(--width) * 0.75), 
            var(--red-3) var(--width), 
            transparent var(--width), 
            transparent calc(var(--width) * 1.5)
          );
        }
        &:after {
          transform: rotate(90deg);
          transform-origin: left top;
          top: calc(var(--width) * 3.5);
          left: calc(var(--width) * 3.5 + 1px);
        }
      }
    }

    32.png

    复制一份并定位:

    .ring-big {
      i {
        ...
        &:nth-child(1) {
          left: calc(var(--width) * -3.5);
          top: calc(var(--width) * -3.5);
        }
        &:nth-child(2) {
          left: auto;
          top: auto;
          right: calc(var(--width) * -3.5);
          bottom: calc(var(--width) * -3.5);
          transform: rotate(180deg);
        }
      }
    }

    33.png

    到这里,工作的一半就已经完成了~继续

    4. 十字结

    34.png

    这个图形,相对于上面几个,已经没什么难度了,五个1×1 的正方形,中间的渐变方向和周围四个垂直。

    中间的正方形,用父级本身实现,里面周围四个,用四个子<i>标签实现:

    <div class="cross-node">
        <div class="node">
          <i></i>
          <i></i>
          <i></i>
          <i></i>
        </div>
        <div class="node">
          <i></i>
          <i></i>
          <i></i>
          <i></i>
        </div>
      </div>
    .cross-node {
      .node {
        position: absolute;
        z-index: 2;
        width: var(--width);
        height: var(--width);
        background: var(--bg-line);
        i {
          position: absolute;
          width: var(--width);
          height: var(--width);
          background: var(--bg-line);
          transform: rotate(90deg);
          &:nth-child(1) {
            left: calc(var(--width) * -1);
          }
          &:nth-child(2) {
            left: var(--width);
          }
          &:nth-child(3) {
            top: calc(var(--width) * -1);
          }
          &:nth-child(4) {
            top: var(--width);
          }
        }
        &:nth-child(1) {
          right: calc(var(--width) * -1);
          top: calc(var(--width) * -1);
        }
        &:nth-child(2) {
          left: calc(var(--width) * -1);
          bottom: calc(var(--width) * -1);
        }
      }
    }

    35.png

    36.png

    5. 挂绳

    前面我们都是让中国结处于一个斜躺的姿态,写头部和尾部之前,让我们先把它摆正:

    .chinese-knot {
      ...
      transform: rotate(-45deg) translate(calc(var(--width) * 4), calc(var(--width) * -4));
    }

    37.png

    回头看素材图:

    38.png

    先确定一下html结构:

    <div class="header">
        <i></i>
        <b></b>
        <span></span>
    </div>

    i 是上面的吊绳,b 是圆环,span 是衔接处的短绳,带点黄色装饰。为了方便调整定位,我们从下往上实现,先写短绳:

    :root {
      --yellow-1: #fced00;
      --yellow-2: #f28a00;
      --yellow-3: #da571b;
      --bg-yellow: linear-gradient(
        90deg,
        var(--yellow-3),
        var(--yellow-2) 20%,
        var(--yellow-1) 40%,
        var(--yellow-1) 60%,
        var(--yellow-2) 80%,
        var(--yellow-3) 100%
      );
    }
    .header {
      position: absolute;
      right: 0;
      top: 0;
      transform: rotate(45deg);
      i {
        position: absolute;
        bottom: calc(var(--width) * 1);
        left: calc(var(--width) * -0.5);
        width: calc(var(--width) * 1);
        height: calc(var(--width) * 2);
        background: var(--bg-line);
        &:before {
          content: "";
          display: block;
          height: calc(var(--width) * 0.5);
          background: var(--bg-yellow);
        }
      }
    }

    然后是圆环:

    .header {
      ...
      b {
        position: absolute;
        bottom: calc(var(--width) * 3);
        left: calc(var(--width) * -1.5);
        width: calc(var(--width) * 3);
        height: calc(var(--width) * 3);
        background: radial-gradient(
          circle at 50%,
          transparent calc(var(--width) * 0.75),
          var(--red-3) calc(var(--width) * 0.75),
          var(--red-2) calc(var(--width) * (0.75 + 0.15)),
          var(--red-1) calc(var(--width) * (0.75 + 0.3)),
          var(--red-1) calc(var(--width) * (0.75 + 0.45)),
          var(--red-2) calc(var(--width) * (0.75 + 0.6)),
          var(--red-3) calc(var(--width) * (0.75 + 0.75)),
          transparent calc(var(--width) * (0.75 + 0.75))
        );
      }
    }

    最后是长的吊绳:

    .header {
      ...
      span {
        position: absolute;
        bottom: calc(var(--width) * 5);
        left: calc(var(--width) * -0.25);
        width: calc(var(--width) * 0.5);
        height: calc(var(--width) * 30);
        background: linear-gradient(90deg, var(--red-2), var(--red-1) 20%, var(--red-2) 70%, var(--red-3));
        border-radius: calc(var(--width) * 0.25);
      }
    }

    单独效果

    39.png

    整体效果

    40.png

    6. 流苏

    41.png

    确定html结构:

    <div class="footer">
        <b></b>
        <b></b>
        <div class="tassels">
          <i></i>
          <i></i>
        </div>
    </div>

    可以看到,流苏部分,有两个弯曲的 1/8 环,我们用两个b 标签来表示。这个形状依然还是先画一个完整的环,然后裁剪来实现:

    .footer {
      position: absolute;
      left: 0;
      bottom: 0;
      b {
        position: absolute;
        width: calc(var(--width) * 15);
        height: calc(var(--width) * 15);
        background: radial-gradient(
          circle at 50%,
          transparent calc(var(--width) * 6.5),
          var(--red-3) calc(var(--width) * 6.5),
          var(--red-2) calc(var(--width) * (6.5 + 0.25)),
          var(--red-1) calc(var(--width) * (6.5 + 0.45)),
          var(--red-1) calc(var(--width) * (6.5 + 0.55)),
          var(--red-2) calc(var(--width) * (6.5 + 0.75)),
          var(--red-3) calc(var(--width) * (6.5 + 1)),
          transparent calc(var(--width) * (6.5 + 1))
        );
      }
    }

    42.png

    加上裁剪并定位:

    .footer {
      ...
      b {
        ...
        &:nth-child(1) {
          left: calc(var(--width) * -8.5);
          top: calc(var(--width) * 1);
          clip-path: polygon(50% 0, 50% 50%, 10% 0);
        }
        &:nth-child(2) {
          left: calc(var(--width) * -16);
          top: calc(var(--width) * -6.5);
          clip-path: polygon(100% 50%, 50% 50%, 100% 90%);
        }
      }
    }

    43.png

    两个小尾巴就实现了。

    最后是流苏。先画一下背景上的垂直细线,这里我们用 repeating-linear-gradient 实现,每隔 2px 画一条 1px 宽的透明度为 0.2 的黑线:

    .footer {
      .tassels {
        i {
          position: absolute;
          width: calc(var(--width) * 2.5);
          height: calc(var(--width) * 14);
          background: var(--red-2) repeating-linear-gradient(90deg, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.2) 1px, transparent 1px, transparent 3px) 50% 50% / 3px 1px;}
      }
    }

    44.png

    再蒙上一层黄色的装饰:

    .footer {
      .tassels {
        i {
          ...
          &:before {
            content: "";
            position: absolute;
            top: calc(var(--width) * 0.5);
            width: 100%;
            height: calc(var(--width) * 3.6);
            background: var(--bg-yellow);
            clip-path: polygon(0 0, 100% 0, 100% 10%, 0 10%, 0 15%, 100% 15%, 100% 85%, 0 85%, 0 90%, 100% 90%, 100% 100%, 0 100%, 0 0);
          }
      }
    }

    45.png

    上面代码中使用 clip-path 对黄色背景裁剪,露出两条红线,裁剪路径可以用下图表示:

    46.png

    最终效果:

    47.png

    三、加点动画

    本来到这里就应该结束了。但是我想让这个中国结有点实际用途,比如加点交互什么的。

    红包也是春节的习俗之一,那就加一个拉一下中国结掉落红包雨的特效吧~

    1. 拉一下

    给中国结在:active状态下加个位移即可实现:

    .chinese-knot {
      width: var(--grid-width);
      height: var(--grid-width);
      position: relative;
      transform: rotate(-45deg) translate(calc(var(--width) * 4), calc(var(--width) * -4));
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
      transition: all 0.5s;
      &:active {
        transform: rotate(-45deg) translate(calc(var(--width) * 2), calc(var(--width) * -2));
      }
    }

    48.gif

    2. 画个红包

    先搜索一个红包素材:

    49.png

    观察一下红包结构,深红背景,浅红弧形开口,加一个黄色圆形封口,上面写着一个繁体的开字。

    我们可以先确定 html 结构。.rain 作为外层,代表整个红包雨,一个i标签代表一个红包:

    <div class="rain">
      <i></i>
    </div>

    一个标签怎么实现上面提到的三种元素呢?看代码:

    .rain {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      display: flex;
      justify-content: space-around;
      i {
        position: relative;
        display: block;
        width: calc(var(--width) * 5);
        height: calc(var(--width) * 8);
        background: var(--red-3);
        border-radius: calc(var(--width) * 0.4);
        overflow: hidden;
        box-shadow: 0 calc(var(--width) * 1) calc(var(--width) * 1) rgba(0, 0, 0, 0.3);
        &:before {
          content: "";
          position: absolute;
          left: 50%;
          transform: translate(-50%, -50%);
          width: calc(var(--width) * 8);
          height: calc(var(--width) * 8);
          background: var(--red-1);
          opacity: 0.5;
          border-radius: 50%;
        }
        &:after {
          content: "開";
          position: absolute;
          left: 50%;
          transform: translate(-50%, 140%);
          width: calc(var(--width) * 2);
          height: calc(var(--width) * 2);
          background: var(--yellow-2);
          border-radius: 50%;
          display: flex;
          align-items: center;
          justify-content: center;
          font-style: normal;
          font-size: calc(var(--width) * 0.5);
          color: var(--yellow-1);
          text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.1);
        }
      }
    }

    50.png

    使用i标签自身实现红包主体,:before 伪类实现弧形的开口,:after 伪类实现黄色圆形封口,在content中写上字。

    一个红包完成了,再复制 9 个:

    <div class="rain">
      <i></i>
      <i></i>
      <i></i>
      <i></i>
      <i></i>
      <i></i>
      <i></i>
      <i></i>
      <i></i>
      <i></i>
    </div>

    51.png

    这样就得到了 10 个固定在顶部,并且整齐排列的红包了。

    3. 红包雨动画

    下雨嘛,从上往下运动就好了:

    .rain {
      ...
      i {
        ...
        animation: fall 3s ease-in infinite;
      }
    }
    @keyframes fall {
      0% {
        transform: translate(0, 0);
      }
      100% {
        transform: translate(0, 100vh);
      }
    }

    52.gif

    聪明的你估计已经猜到了这样的结果:谁家的雨是这样齐刷刷的下来的?

    那我们就红包的垂直位置错落一点,使用 sassrandom 函数来实现随机:

    .rain {
      ...
      i {
        ...
        @for $i from 1 through 10 {
          &:nth-child(#{$i}) {
            top: random(60) + vh;
          }
        }
      }
    }

    53.gif

    额,效果怎么和想象的不一样。依旧还是齐刷刷下落,只不过是"错落"的齐刷刷。

    那我们让每个红包的开始时间也随机不就行了嘛:

    .rain {
      ...
      i {
        ...
        @for $i from 1 through 10 {
          &:nth-child(#{$i}) {
            top: random(60) + vh;
            animation-delay: random(30) * 0.1s;
          }
        }
      }
    }

    54.gif

    嗯,好了一点点。但是有一个问题,屏幕上的雨点,有时候很多,有时候很少,不够均匀。那我们把动画的持续时间也随机会怎么样呢?

    .rain {
      ...
      i {
        ...
        @for $i from 1 through 10 {
          &:nth-child(#{$i}) {
            top: random(60) + vh;
            animation-delay: random(30) * 0.1s;
            animation-duration: random(10) * 0.1s + 2s; /* 2s ~ 3s 之间随机 */
          }
        }
      }
    }

    55.gif

    终于更像雨了~

    但是现在雨滴是凭空出现的,很生硬,我们只要把开始的位置挪到负一屏,然后让它下落到正二屏就行了:

    .rain {
      ...
      top: -100vh;
    }
    @keyframes fall {
      0% {
        transform: translate(0, 0);
      }
      100% {
        transform: translate(0, 200vh);
      }
    }

    56.gif

    这样就有了源源不断下落的效果。

    4. 拉一下触发红包雨

    CSS 不是 JS ,怎么触发点击事件呢?

    我们就要运用 CSS 本身的特性了,checkbox 复选框有个选中状态 :checked,而复选框可以用点击切换这个状态,再使用 CSS 的兄弟选择器 element ~ element 即可实现点击添加样式的效果。

    样式可以触发了,那如何触发动画呢?

    animation 属性添加到元素上后,播放状态默认是 running ,我们需要先把初始播放状态改为 paused (暂停), 然后通过上面的方法,把元素的播放状态改回 running 来实现播放动画的效果:

    <input type="checkbox" id="switch">
    <label class="chinese-knot" for="switch">...</label>
    <div class="rain">...</div>
    .rain {
      ...
      i {
        ...
        animation: fall 3s ease-in infinite;
        /* 默认不播放动画 */
        animation-play-state: paused;
      }
    }
    
    #switch {
      visibility: hidden;
      pointer-events: none;
    }
    /* checkbox 选中时播放动画 */
    #switch:checked ~ .rain i {
      animation-play-state: running;
    }
    /* 点击时重置动画,否则取消checkbox选中状态,动画会中止并停留在当前位置 */
    .chinese-knot:active ~ .rain i {
      animation: none;
    }

    上面的 html 中,我们让.chinese-knotdiv 改为 label 来指向 checkbox,方法是 labelforcheckboxid 设为相同的值。

    57.gif

    效果很不错,我们再给红包雨下落时加个背景,以提醒用户当前的状态。并且下红包雨时,调低中国结的透明度,以突出红包的存在感。

    <input type="checkbox" id="switch">
    <div class="bg"></div>
    <label class="chinese-knot" for="switch">...</label>
    <div class="rain">...</div>
    .bg {
      position: absolute;
      left: 0;
      top: 0;
      height: 100vh;
      width: 100vw;
      background: linear-gradient(0deg, #171a4b, #96367f);
      opacity: 0;
      transition: all 0.5s;
    }
    #switch:checked ~ .bg {
      opacity: 1;
    }
    #switch:checked ~ .chinese-knot {
      opacity: 0.2;
      &:hover {
        opacity: 0.5;
      }
    }

    58.gif

    完结撒花~~~

    总结

    这篇文章整理了我从搜集素材开始,创作一个作品的全部过程,代码写了一天,这篇文章写了半天。希望能让 CSS 初学者对 CSS 燃起兴趣,也希望让接触了一段时间 CSS 的朋友获得一些灵感和帮助。

    (学习视频分享:css视频教程

    以上就是手把手带你使用纯CSS绘制一个中国结,并添加动画效果!的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    专题推荐:CSS 中国结
    上一篇:深入解析自定义的CSS重置样式 下一篇:手把手教你使用纯CSS仿AntDesign的Logo彩蛋效果
    PHP编程就业班

    相关文章推荐

    • rgba是css3新增的属性吗• 值得收藏css预处理器scss的使用总结• 聊聊利用CSS实现九宫格布局的几种方法!• 深入解析自定义的CSS重置样式• 深入了解css变量(整理总结)

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网