目录
一、编码之前
1. 搜集素材,越简洁越好
2. 观察细节,构思实现的可能性
3. 结构拆分,化整为零
二、CSS逐个实现中国结部件
1. 网格
2. 半圆环
3. 四分之三圆环
4. 十字结
5. 挂绳
6. 流苏
三、加点动画
1. 拉一下
2. 画个红包
3. 红包雨动画
4. 拉一下触发红包雨
总结
首页 web前端 css教程 手把手带你使用纯CSS绘制一个中国结,并添加动画效果!

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

Jan 27, 2022 am 10:52 AM
css

本篇文章手把手教大家,一步步使用纯CSS绘制一个中国结,并给这个中国结添加红包雨动画效果,希望对大家有所帮助!

手把手带你使用纯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的主题色而变色。你会直接开始写代码吗?

你会想两个问题:

  • APP作为一个软件,是否有和手机壳互动的接口

  • 手机壳如果接收到色值,如何变色

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

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

  • 中国结的绳子是由渐变色组成,深红,浅红,深红

  • 中间的主体部分由22根相互交叉的绳子组合而成,而且每过一个交叉点就会交换一下层级顺序

  • 有一些环状结构,颜色渐变的过程与直线相同

  • 整体都是红色,以黄色点缀

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

  • 直线的颜色渐变,使用 linear-gradient 或者 repeating-linear-gradient

  • 环状渐变,使用 radial-gradient

  • 网格的交叉,使用 mask 遮罩来达到交叉效果

  • 四分之三环以及底部两根弯曲的绳子使用 clip-path 来裁剪

  • 为了使编码更方便,采用 SCSS

  • 许多地方可以使用 ::before ::after 实现,减少html代码

3. 结构拆分,化整为零

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

  • 中间像棋盘一样的网格结构,可以作为一个 html 标签

6.png

  • 四周16个小半圆,使用16个标签定位实现

7.png

  • 两个四分之三圆,放在一组里,使用相同的样式,第二个基于第一个旋转180deg

8.png

  • 两个十字结,样式一样,所以也放在一组里

9.png

  • 顶部三个小结构,放在一组,外层命名为header

10.png

  • 底部左右两部分高度相似,也放在一组,命名为footer

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还内置了几个生成背景图的函数:

  • linear-gradient:线性渐变
  • repeating-linear-gradient:重复线性渐变
  • radial-gradient:径向渐变
  • conic-gradient:圆锥渐变

这些函数都可以和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="grid"></div>
<i> <i> <i> <i> <i> <i> <i> <i> <i> <i> <i> <i> <i> <i> <i> <i>
登录后复制
.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

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1670
14
CakePHP 教程
1428
52
Laravel 教程
1329
25
PHP教程
1274
29
C# 教程
1256
24
vue中怎么用bootstrap vue中怎么用bootstrap Apr 07, 2025 pm 11:33 PM

在 Vue.js 中使用 Bootstrap 分为五个步骤:安装 Bootstrap。在 main.js 中导入 Bootstrap。直接在模板中使用 Bootstrap 组件。可选:自定义样式。可选:使用插件。

了解HTML,CSS和JavaScript:初学者指南 了解HTML,CSS和JavaScript:初学者指南 Apr 12, 2025 am 12:02 AM

WebDevelovermentReliesonHtml,CSS和JavaScript:1)HTMLStructuresContent,2)CSSStyleSIT和3)JavaScriptAddSstractivity,形成thebasisofmodernWebemodernWebExexperiences。

HTML,CSS和JavaScript的角色:核心职责 HTML,CSS和JavaScript的角色:核心职责 Apr 08, 2025 pm 07:05 PM

HTML定义网页结构,CSS负责样式和布局,JavaScript赋予动态交互。三者在网页开发中各司其职,共同构建丰富多彩的网站。

bootstrap怎么写分割线 bootstrap怎么写分割线 Apr 07, 2025 pm 03:12 PM

创建 Bootstrap 分割线有两种方法:使用 标签,可创建水平分割线。使用 CSS border 属性,可创建自定义样式的分割线。

bootstrap怎么插入图片 bootstrap怎么插入图片 Apr 07, 2025 pm 03:30 PM

在 Bootstrap 中插入图片有以下几种方法:直接插入图片,使用 HTML 的 img 标签。使用 Bootstrap 图像组件,可以提供响应式图片和更多样式。设置图片大小,使用 img-fluid 类可以使图片自适应。设置边框,使用 img-bordered 类。设置圆角,使用 img-rounded 类。设置阴影,使用 shadow 类。调整图片大小和位置,使用 CSS 样式。使用背景图片,使用 background-image CSS 属性。

bootstrap怎么设置框架 bootstrap怎么设置框架 Apr 07, 2025 pm 03:27 PM

要设置 Bootstrap 框架,需要按照以下步骤:1. 通过 CDN 引用 Bootstrap 文件;2. 下载文件并将其托管在自己的服务器上;3. 在 HTML 中包含 Bootstrap 文件;4. 根据需要编译 Sass/Less;5. 导入定制文件(可选)。设置完成后,即可使用 Bootstrap 的网格系统、组件和样式创建响应式网站和应用程序。

bootstrap怎么调整大小 bootstrap怎么调整大小 Apr 07, 2025 pm 03:18 PM

要调整 Bootstrap 中元素大小,可以使用尺寸类,具体包括:调整宽度:.col-、.w-、.mw-调整高度:.h-、.min-h-、.max-h-

bootstrap按钮怎么用 bootstrap按钮怎么用 Apr 07, 2025 pm 03:09 PM

如何使用 Bootstrap 按钮?引入 Bootstrap CSS创建按钮元素并添加 Bootstrap 按钮类添加按钮文本

See all articles