css揭秘 - 形状(一)

648 阅读6分钟

这是我参与更文挑战的第5天,活动详情查看: 更文挑战

自适应的椭圆

难题

我们都知道,给一个宽高为 200px 正方形元素设置 100px 的 border-radius 就可以把它变成一个圆形。我们还知道,如果指定任何大于 100px 的半径,得到的依然是一个圆形。

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

但是,我们往往不愿意对一个元素指定宽度和高度,希望它可以根据内容进行自适应。那么我们希望得到如下效果:如果它的宽高相等则显示为一个圆;如果宽高不等,则显示为一个椭圆。

解决方案

border-radius 可以单独的指定水平半径和垂直半径,只需要使用斜杠分隔这两个值即可。因此如果我们有一个尺寸为 200px✖️100px 的元素,就可以把它的两个圆角的半径分别指定为宽高的一半就可以得到一个精准的椭圆。但是一旦元素发生改变就需要改变 border-radius 的值。

事实上,border-radius 不仅可以接受长度值,还可以接受百分比值,而这个百分比值会基于元素的尺寸进行解析。因此创建一个自适应的椭圆,只需要将两个半径值都设置为50%即可。因为斜杠前后的值是相同的,因此可以简化为:

/* 如下代码即可得到一个自适应的椭圆 */
border-radius:50%;  

半椭圆

border-radius 包括各个展开式属性:

  • border-top-left-radius
  • border-top-right-radius
  • border-bottom-left-radius
  • border-bottom-right-radius 因此,我们可以为所有的四个角提供完全不同的水平半径和垂直半径,方法是在斜杠前指定14个值,在斜杠后指定另外14个值。于是,我们可以通过以下方式来实现半椭圆:
/*当 border-radius 的值为10px / 5px 20px 时,相当于 10px 10px 10px 10px / 5px 20px 5px 20px 。*/
border-radius: 50% / 100% 100% 0 0;   /*下图一*/
border-radius: 100% 0 0 100% / 50%;   /*下图二*/
border-radius: 50% /  0 0 100% 100%;  /*下图三*/
border-radius: 0 100% 100% 0 / 50%;   /*下图四*/

image.png

四分之一椭圆

要创建一个四分之一椭圆,其中一个角的水平半径和垂直半径都需要为100%,而其它三个角都不能设置为圆角,由于这四个角的半径在水平和垂直方向上都是相同的,因此不需要使用斜杠语法。

border-radius: 100% 0 0 0;    /*下图一*/
border-radius: 0 0 0 100%;    /*下图二*/
border-radius: 0 0 100% 0;    /*下图三*/
border-radius: 0 100% 0 0;    /*下图四*/

image.png

注:是不是还能用 border-radius 来生成椭圆的其他切块(比如八分之一椭圆、三分之一椭圆)?很遗憾,border-radius 属性无法生成这些形状。

糖果按钮

image.png

平行四边形

难题

平行四边形是矩形的超集:它的各条边是两两平行的,但是各个角不一定都是直角。

我们首先通过 skew() 这个变形属性来对这个矩形进行拉伸:

transform: skewX(-45deg);

效果如下:

image.png

不仅是矩形发生了倾斜变化,它的内容也发生了斜向变形,这很不好看,而且很难读。有没有办法只让容器的形状倾斜,而保持其内容不变呢?

嵌套元素方案

可以对内容再应用一次反向的 skew() 变形,从而抵消容器的变形,但是,这意味着需要使用额外的一层 html 元素来包裹内容。

<a href="#yolo" class="button">
    <div>Click me</div>
</a>
<style>
    .button { transform: skewX(-45deg); }
    .button > div { transform: skewX(45deg); }
</style>

伪元素方案

另一种思路是把所有样式应用到伪元素上,然后在对伪元素金星变性。但是用伪元素生成的方块是重叠在内容之上的,一旦给它设置背景,就会遮住内容。因此需要给伪元素设置 z-index:-1 即可实现。

.button {
    position: relative;
/* 其他的文字颜色、内边距等样式…… */
}
.button::before {
    content: ''; /* 用伪元素来生成一个矩形 */
    position: absolute;
    top: 0; right: 0; bottom: 0; left: 0;
    z-index: -1;
    background: lightseagreen;
    transform: skew(45deg);
}

image.png

菱形图片

难题

将图片裁切为菱形是一种常见的设计方式,通常不希望在图片处理软件中奖图片处理好,这样维护的成本太高,因此需要通过代码去处理使得一张正常的图片可以以菱形的方式展现。

基于变形的方案

将图片用一个 div 包裹起来,然后对其应用相反的 rotate() 样式。先尝试一下:

<div class="picture">
    <img src="1.jpg" alt="..." />
</div>
.picture {
    width: 150px;
    transform: rotate(45deg);
    overflow: hidden;
}
.picture > img {
    max-width: 100%;
    transform: rotate(-45deg);
}

但是,最后的结果是把它裁切成了一个八角形。主要问题在于 max-width: 100%。100%会被解析为容器(.picture)的边长。但是,我们想让图片的宽度与容器的对角线相等,而不是与边长相等。因此需要使用 scale 将图片放大。两次结果对比如下:

.picture {
    width: 400px;
    transform: rotate(45deg);
    overflow: hidden;
}
.picture > img {
    max-width: 100%;
    transform: rotate(-45deg) scale(1.42);
}

image.png

裁切路径方案

上述方案基本可以奏效,但是它需要一层额外的 html 标签,而且如果处理的是一张非正方形的图片,也会有问题。

可以使用 clip-path 属性,该属性可以将元素裁切为我们想要的任何形状。可以使用 polygon() 函数来指定一个菱形。实际上,它可以使用一系列(以逗号分隔的)坐标点来指定任意的多边形。还可以使用百分比,会解析为元素自身的尺寸。

clip-path 所能创造的奇迹还不止于此。这个属性甚至可以参与动画,只要动画是在同一种形状函数(比如这里是 polygon() )之间进行的,而且点的数量是相同的。如果我们希望图片在鼠标悬停时平滑地扩展为完整的面积,只需要这样做:

img {
    width: 150px;
    height: 150px;
    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%);
}

2.gif

最后说一句

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力,多谢支持。