3. CSS揭秘——形状

512 阅读10分钟

1. 自适应的椭圆

给任何正方形元素设置一个足够大的 border-radius,就可以把它变成一个圆形

background: #fb3;
width: 200px;
height: 200px;
border-radius: 100px; /* 大于等于正方形边长的一半 */

其中,如果指定任何大于 100px 的半径,仍然可以得到一个圆形

1.gif

当任意两个相邻圆角的半径之和超过 border box 的尺寸时,必须按比例减小各个边框半径所使用的值,直到它们不会相互重叠为止。

  • border-radius,可以单独指定水平和垂直半径,只要用一个斜杠(/)分隔这两个值即可。这个特性允许我们在拐角处创建椭圆圆角
  • border-radius,不仅可以接受长度值,还可以接受百分比值。这个百分比值会基于元素的尺寸进行解析。因此相同的百分比可能会计算出不同的水平和垂直半径。

上边的例子中,border-radius接受长度值,只要元素的尺寸发生变化,border-radius 的值就得跟着改,不过可以设置border-radius使用百分比值

1.1 border-radius使用百分比值实现自适应椭圆

如果要创建一个自适应的椭圆,我们可以把这两个半径值都设置为 50%:

border-radius: 50% / 50%;
// border-radius: 50% ; // 继续简化

1.gif

1.2 半椭圆

border-radius 简写属性:

  • border-top-left-radius
  • border-top-right-radius
  • border-bottom-right-radius
  • border-bottom-left-radius

可以向border-radius一次性提供用空格分开的多个值。

  • 如果我们传给它四个值,这四个值就会被分别从左上角开始以顺时针顺序应用到元素的各个拐角
  • 如果只提供了三个值,则意味着第四个值与第二值相同;
  • 如果只有两个值,则意味着第三个值与第一个相同

image.png

可以为所有四个角提供完全不同的水平和垂直半径,方法是在斜杠前指定 1~4 个值,在斜杠后指定另外 1~4 个值。请注意这两组值是单独展开为四个值的。举例来说,当 border-radius 的值为10px / 5px 20px 时,其效果相当于 10px 10px 10px 10px / 5px 20px 5px 20px。

半椭圆特征:

image.png

  • 左上角和右上角的半径值应该是相同的;与此类似,左下角和右下角的半径值也应该是相同的。
  • 顶部边缘并没有平直的部分(也就是说,整个顶边都是曲线),这意味着左上角和右上角的半径之和应该等于整个形状的宽度
  • 左半径和右半径在水平方向上的值应该均为 50%
  • 垂直方向,顶部的两个圆角占据了整个元素的高度,而且底部完全没有任何圆角。因此,在垂直方向上 border-radius 的合理值就是 100% 100% 0 0
  • 因为底部两个角的垂直圆角是零,因此它的水平圆角总是会被计算为零
border-radius: 50% / 100% 100% 0 0;
border-radius: 100% 0 0 100% / 50%; // 沿纵轴劈开的半椭圆

image.png

image.png

1.3 四分之一椭圆

要创建一个四分之一椭圆,其中一个角的水平和垂直半径值都需要是100%,而其他三个角都不能设为圆角。这四个角的半径在水平和垂直方向上都是相同的

border-radius: 100% 0 0 0;

image.png

2. 平行四边形

平行四边形往往可以传达出一种动感。

用 CSS 创建一个按钮状的平行四边形链接

  • 通过 skew() 的变形属性来对这个矩形进行斜向拉伸
  • 只让容器的形状倾斜,而保持其内容不变

2.1 嵌套元素实现平行四边形

使用一层额外的 HTML 元素来包裹内容,对内容再应用一次反向的skew()变形,从而抵消容器的变形效果。

<!DOCTYPE html>
<html>
  <head>
    <style>
      .button {
        display: inline-block;
        padding: 0.5em 1em;
        margin: 0.5em;
        border: 0;

        background: #58a;
        color: white;
        text-transform: uppercase;
        text-decoration: none;
        font: bold 200%/1 sans-serif;

        transform: skewX(-45deg);
      }
      .button > div {
        transform: skewX(45deg);
      }
    </style>
  </head>

  <body>
    <a href="#yolo" class="button">
      <div>Click me</div>
    </a>
  </body>
</html>

1.gif

如果想把这个效果应用到一个行内元素,必须把它的 display 属性设置inline-block或 block,否则变形是不会生效的。这一点对它内层的元素也是适用的。

2.2 伪元素实现平行四边形

把所有样式(背景、边框等)应用到伪元素上,然后再对伪元素进行变形。因为我们的内容并不是包含在伪元素里的,所以内容并不会受到变形的影响。

设置伪元素继承其宿主元素的尺寸

  • 给宿主元素应用 position: relative 样式,并为伪元素设置 position:absolute
  • 然后再把所有偏移量设置为零,以便让它在水平和垂直方向上都被拉伸至宿主元素的尺寸
  • 用伪元素生成的方块是重叠在内容之上的,一旦给它设置背景,就会遮住内容
  • 给伪元素设置z-index: -1 样式,这样它的堆叠层次就会被推到宿主元素之后
<!DOCTYPE html>
<html>
  <head>
    <style>
      .button {
        position: relative;
        /* 其他的文字颜色、内边距等样式…… */
        display: inline-block;
        padding: 0.5em 1em;
        margin: 1em;
        border: 0;

        color: white;
        text-transform: uppercase;
        text-decoration: none;
        font: bold 200%/1 sans-serif;
      }
      .button::before {
        content: ""; /* 用伪元素来生成一个矩形 */
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        z-index: -1;
        background: #58a;
        transform: skew(-45deg);
      }
    </style>
  </head>

  <body>
    <a href="#yolo" class="button">
      <div>Click me</div>
    </a>
  </body>
</html>

1.gif

利用伪元素以及定位属性产生了一个方块,然后对伪元素设置样式,并将其放置在其宿主元素的下层

变形一个元素而不想变形它的内容时就可以用到该伪元素方案。不仅对 skew() 变形来说很有用,也适用于其他任何变形样式

3. 菱形图片

把图片裁切为菱形是一种常见的设计手法

3.1 基于变形实现菱形图片

  • 把图片用一个 <div> 包裹起来,
  • 然后对包裹的<div> rotate()变形样式,对图片应用相反的 rotate()变形样式
  • 用 scale() 变形样式来把这个图片放大,放大倍数大于等于根号二 (1.42)
<!DOCTYPE html>
<html>
  <head>
    <style>
      .picture {
        width: 197px;
        margin: 5em;
        transform: rotate(45deg);
        overflow: hidden;
      }
      .picture > img {
        max-width: 100%;
        transform: rotate(-45deg) scale(1.42);
      }
    </style>
  </head>

  <body>
    <div class="picture">
      <img src="./catlace.jpg" alt="..." />
    </div>
  </body>
</html>

1.gif

3.2 裁切路径实现菱形图片

裁切路径允许我们把元素裁剪为我们想要的任何形状.

使用 polygon()(多边形)函数来指定一个菱形。实际上,polygon()函数允许用一系列(以逗号分隔的)坐标点来指定任意的多边形。甚至可以使用百分比值,它们会解析为元素自身的尺寸

<!DOCTYPE html>
<html>
  <head>
    <style>
      img {
       // 四个顶点 左上角 0% 0% 右下角100% 100%
        clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
      }
    </style>
  </head>
  <body>
    <img src="./catlace.jpg" alt="..." />
  </body>
</html>

1.gif

如果动画是在同一种形状函数之间进行的,而且点的数量是相同的,clip-path属性甚至可以参与动画

img {
    clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
    transition: 1s clip-path;
}
img:hover {
    clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}

1.gif

4. 切角效果

当切角效果只应用在元素的某一侧,且切角的尺寸刚好达到元素高度的 50% 时,就会得到一个箭头形状

4.1 CSS线性渐变实现切角

4.1.1 一个角被切掉——一层线性渐变

把一个透明色标放在切角处,然后在相同位置设置另一个色标,并且把它的颜色设置为我们想要的背景色

background: linear-gradient(-45deg, transparent 15px, #58a 0);

image.png

4.1.2 两个角被切掉

  • 使用两层线性渐变
  • 使用 background-size 让每层渐变分别只占据整个元素一半的面积
  • 关掉background-repeat背景平铺
background: linear-gradient(-45deg, transparent 15px, #58a 0) right,
  linear-gradient(45deg, transparent 15px, #655 0) left;
background-size: 50% 100%; /* 等价于 background-size: 50% 100%, 50% 100%; */
background-repeat: no-repeat;

1.gif

4.1.3 把四个角都做出切角效果

需要四层渐变图案

background: linear-gradient(135deg, transparent 15px, #58a 0) top left,
  linear-gradient(-135deg, transparent 15px, #58a 0) top right,
  linear-gradient(-45deg, transparent 15px, #58a 0) bottom right,
  linear-gradient(45deg, transparent 15px, #58a 0) bottom left;
/* 简写 background-size: 50% 50%; */
background-size: 50% 50%, 50% 50%, 50% 50%, 50% 50%;
background-repeat: no-repeat;

image.png

可维护性并不理想。我们在改变背景色时需要修改四处;在改变切角尺寸时也需要修改四处。

使用Scss预处理器的 @mixin 可以减少代码的重复度

<template>
  <div class="home">
  </div>
</template>

<script>
export default {
  name: "AboutView"
}
</script>
<style lang="scss" scoped>
@mixin beveled-corners($bg, $tl:0, $tr:$tl, $br:$tl, $bl:$tr) {
 background: $bg;
 background:
 linear-gradient(135deg, transparent $tl, $bg 0) top left,
 linear-gradient(225deg, transparent $tr, $bg 0) top right,
 linear-gradient(-45deg, transparent $br, $bg 0) bottom right,
 linear-gradient(45deg, transparent $bl, $bg 0) bottom left;
 background-size: 50% 50%;
 background-repeat: no-repeat;
}
div.home {
  margin: 100px auto;
  height: 200px;
  width: 300px;
  @include beveled-corners(#58a, 15px, 25px);
}
</style>

1.gif

在 SCSS 的 @mixin为各个参数指定了默认值,而且这些默认值也可以引用其他参数的值。因此如果我们提供的值少于四个,它的行为跟 border-radius 属性是类似的

4.2 用径向渐变实现弧形切角

background: radial-gradient(circle at top left, transparent 15px, #58a 0) top left,
  radial-gradient(circle at top right, transparent 15px, #58a 0) top right,
  radial-gradient(circle at bottom right, transparent 15px, #58a 0) bottom right,
  radial-gradient(circle at bottom left, transparent 15px, #58a 0) bottom left;
background-size: 50% 50%;
background-repeat: no-repeat;

image.png

4.3 内联 SVG 与 border-image 方案

使用 border-image,并通过一个内联的 SVG 图像来产生切角效果:

  • border-image 会解决缩放问题,而 SVG 可以实现与尺寸完全无关的完美缩放——这就是矢量图的好处
  • 每个切片的尺寸都可以设置为 1,切角的尺寸是 1,直线边缘也都是 1。这并不表示 1 像素;它所对应的是SVG 文件的坐标系统(因此不需要单位)
  • 指定一个整体背景色
  • 设置background-clip 避免背景色蔓延到边框区域
<!DOCTYPE html>
<html>
  <head>
    <style>
      div {
        width: 200px;
        height: 100px;
      }
      .circle {
        border: 15px solid transparent;
        border-image: 1
          url('data:image/svg+xml,\
          <svg xmlns="http://www.w3.org/2000/svg" width="3" height="3" fill="%2358a">\
          <polygon points="0,1 1,0 2,0 3,1 3,2 2,3 1,3 0,2"/>\
          </svg>');

        background: #58a;
        background-clip: padding-box;
      }
    </style>
  </head>

  <body>
    <div class="circle"></div>
  </body>
</html>

1.gif 这个方案的优点:

  • 在改变切角尺寸时,只需修改一处边框宽度就可以了。
  • 可以给它加上动画,因为 border-width 属性是支持动画的!
  • 在改变背景色时只改两处,而不是四处。
  • 此外,由于背景效果跟切角效果是相互独立的,可以把背景设置为一层渐变图案或者其他图案

4.4 裁切路径方案——clip-path

CSS 裁切路径最神奇的地方在于可以同时使用百分比数值(它会以元素自身的宽高作为基数度进行换算)和绝对长度值,从而提供巨大的灵活性

用裁切路径将一个元素切出 20px 大小(以水平方向度量)的斜面切角:

background: #58a;
/*多边形八个顶点组成的路径 */
clip-path: polygon(
  20px 0,
  calc(100% - 20px) 0,
  100% 20px,
  100% calc(100% - 20px),
  calc(100% - 20px) 100%,
  20px 100%,
  0 calc(100% - 20px),
  0 20px
);

image.png

  • 可以使用任意类型的背景,甚至可以对替换元素(比如图片)进行裁切。
  • 当内边距不够宽时,它会裁切掉文本

5. 梯形标签页

image.png

在现实的三维世界中旋转一个矩形。由于透视的关系,我们最终看到的二维图像往往就是一个梯形

可以在 CSS 中用 3D 旋转来模拟出梯形效果:

<!DOCTYPE html>
<html>
  <head>
    <style>
      div {
        background: #58a;
        width: 100px;
        height: 50px;
        line-height: 50px;
        margin: 20px auto;
      }
      .circle {
        transform: perspective(0.5em) rotateX(5deg);
      }
    </style>
  </head>

  <body>
    <div class="circle">TRAPEZOID</div>
  </body>
</html>

image.png

对元素使用了 3D变形之后,其内部的变形效应是“不可逆转”的,这一点跟 2D 变形不同 (在 2D 变形的体系之下,内部的逆向变形可以抵消外部的变形效应)

由于是对整个元素应用 3D 变形的,因此它上面的文字也变形了。因此,如果想使用 3D 变形的功能来生成梯形,唯一可行的是把变形效果作用在伪元素上

5.1 把3D变形效果作用在伪元素上

<!DOCTYPE html>
<html>
  <head>
    <style>
      .tab {
        position: relative;
        display: inline-block;
        padding: 0.5em 1em 0.35em;
        color: white;
        margin: 10px 100px;
      }
      .tab::before {
        content: ""; /* 用伪元素来生成一个矩形 */
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        z-index: -1;
        background: #58a;
        transform: perspective(0.5em) rotateX(5deg);
      }
    </style>
  </head>

  <body>
    <div class="tab">TRAPEZOID</div>
  </body>
</html>

image.png

指定 transform-origin:bottom;,当它在 3D 空间中旋转时,可以把它的底边固定住

5.2 梯形标签页实例

添加背景、边框、圆角、投影等一系列样式

<!DOCTYPE html>
<html>
  <head>
    <style>
      .nav > a {
        position: relative;
        display: inline-block;
        padding: 1.3em 3.3em 0;
      }
      .nav > a::before {
        content: "";
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        z-index: -1;
        background: #ccc;
        background-image: linear-gradient(
          hsla(0, 0%, 100%, 0.6),
          hsla(0, 0%, 100%, 0)
        );
        border: 1px solid rgba(0, 0, 0, 0.4);
        border-bottom: none;
        border-radius: 0.5em 0.5em 0 0;
        box-shadow: 0 0.15em white inset;
        transform: perspective(0.5em) rotateX(5deg);
        transform-origin: bottom;
      }
    </style>
  </head>

  <body>
    <span class="nav"><a>Home</a></span>
    <span class="nav"><a>Project</a></span>
    <span class="nav"><a>About</a></span>
  </body>
</html>

只需要把 transform-origin 改成bottom left 或 bottom right,就可以立即得到左侧倾斜或右侧倾斜的标签页

1.gif

6. 简单的饼图

6.1 角向渐变实现饼图

在生成饼图时只需要一个圆形元素,再配上一幅具有两个色标的角向渐变即可。

<!DOCTYPE html>
<html>
  <head>
    <style>
      .pie {
        width: 200px;
        height: 200px;
        border-radius: 50%;
        background: conic-gradient(
          deeppink 20%,
          #fb3 0,
          #fb3 30%,
          yellowgreen 0
        );
      }
    </style>
  </head>

  <body>
    <div class="pie"></div>
  </body>
</html>

image.png