纯CSS实现骚气红丝带

在本文中,我们将探讨如何使用 CSS 以最少的代码创造出精美的 CSS 丝带形状,并最终实现下面这个效果:

下面我们使用html和css来实现这个效果。我们使用内容自适应方式布局,不用担心里面的文字长度。本文介绍两种丝带:左侧的丝带称为"折叠丝带",右侧的丝带称为"旋转丝带"。

通过CSS创建折叠丝带形状

首先要实现折叠 CSS 丝带,先定义形状的变量。

css 复制代码
.ribbon {
  --r: 20px; /* 控制丝带的切割效果 */
  --s: 20px; /* 折叠部分的尺寸 */
  --c: #d81a14; /* 颜色控制 */
}

--r--s这两个变量控制形状,--c控制颜色。

如果要在CSS中实现多边形,我们可以使用css的 clip-path 属性。我们提前在图形上添加一些填充内容避免文本被切割,然后使用clip-path

css 复制代码
.ribbon {
  --r: 20px; /* 控制丝带的切割效果 */
  --s: 20px; /* 折叠部分的尺寸 */
  --c: #d81a14; /* 颜色控制 */

  line-height: 1.6; /* 控制高度 */
  padding-inline: 1.2lh calc(var(--r) + .2lh);
  background: var(--c);
  clip-path: polygon(1lh 0, 100% 0, calc(100% - var(--r)) 50%, 100% 100%, 100% 100%, 0 100%, 0 100%);
}

使用 CSS lh 单位

很多同学可能不知道 lh 单位是什么,它是与 line-height 值相对应的新单位。由于这里使用了一行文本,所以设置 line-height 来控制元素的高度,因此 1lh 就等于元素的高度。所以在 clip-path 中,我们使用这个高度来切割等腰三角形的形状。如下图:

接着我们需要创建折叠部分,需要使用 clip-path 更新上面的多边形。 clip-path 可以切割元素边界的外部区域,包括盒子阴影、轮廓、伪元素等。

在下面示例中,利用 box-shadow 配合clip-path 来实现切割。通过更新 XiYi 来切割多边形的四个新点,其中三个点位于元素的外部区域。因为我们要切割的部分在外部,但是它是不可见的,这里我们添加了大的 box-shadow 让元素变得可见。代码如下:

css 复制代码
.ribbon {
  --r: 20px; /* 控制丝带的切割效果 */
  --s: 20px; /* 折叠部分的尺寸 */
  --c: #d81a14; /* 颜色控制 */

  line-height: 1.6; /* 控制高度 */
  padding-inline: 1.2lh calc(var(--r) + .2lh);
  background: var(--c);
  clip-path: polygon(1lh 0, 100% 0, calc(100% - var(--r)) 50%, 100% 100%, 1lh 100%, 1lh calc(100% + var(--s)), .5lh calc(100% + var(--s) + var(--r)), 0 calc(100% + var(--s)), 0 100%);
  box-shadow: 0 0 0 999px var(--c); /* 较大的阴影扩散半径 */
}

最后通过引入渐变和另一个框阴影,就实现了阴影效果。到这里我们的 CSS 丝带形状已经成型了。

现在介绍如何创建第二种形状(绿色丝带)。这里使用相同的方法,用第一个多边形并将其反转一下。

这样写:

css 复制代码
clip-path: polygon(X1 Y1, X2 Y2, ..., Xn Yn)

要获得相反的形状,我们将所有 Xi 更改为 100% - Xi!在查看代码之前,大家尝试单独使用第一个形状的多边形来实现这一点。

在上面的动画中,当鼠标悬停在形状上时,可以看到漂亮的展开收起动画。为了实现这一点,需要通过偏移一些点来更新悬停时的多边形。这里不需要重新编写整个多边形,可以重新定义一个 CSS 变量来控制偏移。

如果大家关注动画部分,就会注意到有三个点向左移动,同时有三个点向下和向左移动。

修改 Xi 的点向左移动,修改 Yi 的点向下和向左移动来实现这一点。然后再修改 d 以控制这一运动:

css 复制代码
.ribbon {
  --d: 0px; /* 这将控制偏移量 */
  clip-path: polygon(calc(1lh + var(--d)) 0, 100% 0, calc(100% - var(--r)) 50%, 100% 100%, calc(1lh + var(--d)) 100%, calc(1lh + var(--d)) calc(100% + var(--s) + var(--d)), calc(.5lh + var(--d)) calc(100% + var(--s) + var(--r) + var(--d)), var(--d) calc(100% + var(--s) + var(--d)), var(--d) 100%);
}
.ribbon:hover {
  --d: .2lh;
}

部分同学第一次看到这种多边形,可能会感到困惑,因为它看起来有些复杂。我们从一个简单的多边形开始,然后逐步添加更多点和计算,最终得到这个复杂的多边形。

创建旋转的 CSS 丝带形状

现在让我们处理第二种形状。对于这种形状,我们将使用新的三角函数以及 CSS 变量和 calc(),方法与前一个形状类似。为了理解这个形状背后的逻辑,让我们旋转它并确保文本保持在一条直线上。

下面添加了一些透明度以查看主要元素背后的部分,然后使用伪元素来创建这些部分。这里添加了蓝色轮廓来说明元素的区域。该形状将由两个变量控制:

css 复制代码
.ribbon {
  --r: 30px;  /* 控制丝带的切割效果 */
  --a: 15deg; /* 控制旋转角度 */
}

其中 r 的作用与前一个形状相同。a 将控制主要元素的旋转。

下面我们从主要元素开始说。从图中我们可以看到,我们需要从每一面切割它,但是这次不能使用 clip-path,我们将使用渐变颜色,其中需要切割的部分需要使用透明的颜色:

css 复制代码
.ribbon {
  --r: 30px;  /* 控制丝带的切割效果 */
  --a: 15deg; /* 控制旋转角度 */

  background:
    linear-gradient(calc(90deg + var(--a)),
      #0000 calc(1lh*sin(var(--a))),
      var(--c) 0 calc(100% - 1lh*sin(var(--a))),
      #0000 0
    );
}

效果如图:

这里的高度等于 1lh/cos(a)。宽度等于 (100% - x)*cos(a),其中 100% 是主要元素的宽度,x 是我们带有透明度的那一小部分,它等于 1lh*tan(a)

两个伪元素具有相同的尺寸,代码如下:

css 复制代码
.ribbon:before,
.ribbon:after {
  content: "";
  position: absolute;
  height: calc(1lh/cos(var(--a)));
  width: calc(100%*cos(var(--a)) - 1lh*sin(var(--a)));
}

在确定尺寸后,我们需要正确定位每个伪元素,并对其进行旋转和切割:

css 复制代码
.ribbon:before,
.ribbon:after {
  content: "";
  position: absolute;
  transform: translate3d(0,0,-1px);
  rotate: var(--a);
  height: calc(1lh/cos(var(--a)));
  width: calc(100%*cos(var(--a)) - 1lh*sin(var(--a)));
  background: color-mix

(in srgb,var(--c),#000 40%);
}
h1:before {
  right: 0;
  top: 0;
  transform-origin: top right;
  clip-path: polygon(0 0,100% 0,100% 100%,0 100%,var(--r) 50%);
}
h1:after {
  left: 0;
  bottom: 0;
  transform-origin: bottom left;
  clip-path: polygon(0 0,100% 0,calc(100% - var(--r)) 50%,100% 100%,0 100%);
}

这里代码应该比较清晰易懂,clip-path 的值应该也容易理解。要注意的是,我们使用了 color-mix() 函数,这个属性允许创建主颜色的深色版本。现在如果我们将元素旋转相反的方向,就会得到旋转的 CSS 丝带形状。

完整代码

css 复制代码
<h1>I am a ribbon</h1>
<h1 class="alt">I am a ribbon</h1>
css 复制代码
@property --a {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: true;
}

h1 {
  --r: 30px;  /* control the cutout of the ribbon */
  --a: 15deg; /* control the rotation (only positive values) */
  --c: #d81a14;
  
  line-height: 1.6; /* this will control the height */
  padding-inline: .5lh; /* OR calc(tan(var(--a))*1.5lh) */
  color: #fff;
  background:
    linear-gradient(calc(90deg + var(--a)),
      #0000 calc(1lh*sin(var(--a)) - 1px),
      var(--c) calc(1lh*sin(var(--a))) calc(100% - 1lh*sin(var(--a))),
      #0000 calc(100% - 1lh*sin(var(--a)) + 1px)
    );
  position: relative;
  rotate: calc(-1*var(--a));
  transform-style: preserve-3d;
  transition: --a .5s;
  cursor: pointer;
  white-space: nowrap;
}
h1.alt {
  --c: #8FBE00;
  rotate: var(--a);
  background:
    linear-gradient(calc(90deg - var(--a)),
      #0000 calc(1lh*sin(var(--a)) - 1px),
      var(--c) calc(1lh*sin(var(--a))) calc(100% - 1lh*sin(var(--a))),
      #0000 calc(100% - 1lh*sin(var(--a)) + 1px)
    );
}
h1:before,
h1:after{
  content: "";
  position: absolute;
  transform: translate3d(0,0,-1px);
  rotate: var(--a);
  height: calc(1lh/cos(var(--a)));
  width: calc(100%*cos(var(--a)) - 1lh*sin(var(--a))) ;
  background: color-mix(in srgb,var(--c),#000 40%);
  pointer-events: none;
}
h1.alt:before,
h1.alt:after {
  rotate: calc(-1*var(--a));
}
h1:before {
  right: 0;
  top: 0;
  transform-origin: top right;
  clip-path: polygon(0 0,100% 0,100% 100%,0 100%,var(--r) 50%);
}
h1.alt:before {
  bottom: 0;
  top: auto;
  transform-origin: bottom right;
}
h1:after {
  left: 0;
  bottom: 0;
  transform-origin: bottom left;
  clip-path: polygon(0 0,100% 0,calc(100% - var(--r)) 50%,100% 100%,0 100%);
}
h1.alt:after {
  top: 0;
  bottom: auto;
  transform-origin: top left;
}

h1:hover {
  --a: 0deg;
}

/* we fallback to something else if lh is not supported
   1lh = 1.6em (the line-height value)
*/
@supports not (height:1lh) {
  h1 {
    padding-inline: .8em; 
    background:
      linear-gradient(calc(90deg + var(--a)),
        #0000 calc(1.6em*sin(var(--a)) - 1px),
        var(--c) calc(1.6em*sin(var(--a))) calc(100% - 1.6em*sin(var(--a))),
        #0000 calc(100% - 1.6em*sin(var(--a)) + 1px)
      );
  }
  h1.alt {
    background:
      linear-gradient(calc(90deg - var(--a)),
        #0000 calc(1.6em*sin(var(--a)) - 1px),
        var(--c) calc(1.6em*sin(var(--a))) calc(100% - 1.6em*sin(var(--a))),
        #0000 calc(100% - 1.6em*sin(var(--a)) + 1px)
      );
  }
  h1:before,
  h1:after{
    height: calc(1.6em/cos(var(--a)));
    width: calc(100%*cos(var(--a)) - 1.6em*sin(var(--a))) ;
  }
}


body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-content: center;
  grid-auto-flow: column;
  gap: 50px;
}

h1 {
  font-family: sans-serif;
  text-transform: uppercase;
  font-size: 2.5rem;
}
相关推荐
hotdogc10174 分钟前
告别复杂的配置:为什么你应该关注 NGINX 的现代替代品 Caddy
前端
独立开阀者_FwtCoder7 分钟前
《独立开发工具 • 半月刊》 第 012 期
前端·javascript·github
墨渊君7 分钟前
从图片到语音: Kimi 视觉模型与火山引擎的完美结合!
前端·javascript·aigc
独立开阀者_FwtCoder9 分钟前
React 正式接入 AI
前端·javascript·github
星语卿18 分钟前
前端如何调用外部api获取省市区数据
前端
Canmick19 分钟前
为什么我建议立即停用 VBScript
前端
憨憨是条狗24 分钟前
深入理解ArcGIS API for JavaScript中的图层渲染与信息获取
前端
心.c24 分钟前
JavaScript 数据结构详解
前端·javascript·数据结构
江城开朗的豌豆24 分钟前
Nest.js开发 VS Java开发,看看你跟适合学习那个?
java·前端·后端
lyc23333326 分钟前
鸿蒙Next应用接续开发:多设备无缝接力指南
前端