《CSS揭秘》第3章-形状

自适应的椭圆

border-radius 属性的基本用法

难题

给任何正方形元素设置一个足够大的border-radius,就可以把它变成一个圆形。所用到的 CSS 代码如下所示:

1
2
3
4
background: #fb3; 
width: 200px;
height: 200px;
border-radius: 100px; /* >= 正方形边长的一半 */

不过,我们往往不愿意对一个元素指定固定的宽度和高度,因为我们希望它能根据其内容自动调整并适应,进而控制圆:如果它的宽高相等,就显示为一个圆;如果宽高不等,就显示为一个椭圆。

解决方案

border-radius,有一个鲜为人知的真相:

  1. 可以单独指定水平和垂直半径,只要用一个斜杠(/)分隔这两个值即可。
  2. 可以接受百分比值。这个百分比值会基于元素的尺寸进行解析,即宽度用于水平半径的解析,而高度用于垂直半径的解析。这意味着相同的百分比可能会计算出不同的水平和垂直半径。
1
2
3
4
5
6
7
8
9
border-radius:50%/50%;
```

#### 半椭圆
我们需要给每个角指定不同的半径:
- border-top-left-radius
- border-top-right-radius
- border-bottom-right-radius
- border-bottom-left-radius

//沿横轴劈开的半椭圆
border-radius:50%/100% 100% 0 0;

//沿纵轴劈开的半椭圆
border-radius:100% 0 0 100%/50%;

1
2
3
4
5
6
7
![](http://img.aisss.top/17-12-14/78618757.jpg)
![](http://img.aisss.top/17-12-14/24744463.jpg)
**注意** :当水平圆角或垂直圆角为0时,另一个就无所谓是多少。


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

border-radius:100% 0 0 0;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
![](http://img.aisss.top/17-12-14/76410207.jpg)         





## 平行四边形
> 基本的CSS变形

### 难题
我们可以通过 skew() 的变形属性来对这个矩形进行斜向拉伸,但是,这导致它的内容也发生了斜向变形,这很不好看,而且难读。
![](http://img.aisss.top/17-12-14/19855225.jpg)


### 嵌套元素方案
我们可以对内容再应用一次反向的`skew()`变形,从而抵消容器的变形效果,但是需要多一层HTML元素。
Click me
.button { transform: skewX(-45deg); } .button > div { transform: skewX(45deg); }
1
2
3
4
5
6
7
8
**注意** :只对非行内元素有效。     


### 伪元素方案
我们也可以把所有样式(背景、边框等)应用到伪元素上,然后再对伪元素进行变形。
关键点:
- 偏移量设置为0,使其拉伸至与宿主元素的尺寸
- 设置`z-index: -1`,不遮蔽宿主元素

.button{
position:relative;
}

.button::before{
content:’’;
position:absolute;
top:0;right:0;bottom:0;left:0;
background:#58a;
tansform:skew(45deg);
z-inde:-1;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
这个技巧的关键在于:   
*我们利用伪元素以及定位属性产生了一个方块,然后对伪元素设置样式,并将其放置在其宿主元素的下层。*



## 菱形图片
> 基本的CSS变形,平行四边形

### 难题
当网页设计师想要实现这种设计风格时,他们通常不希望在图像处理软件中预先把图片裁好,可维护性太差。

### 基于变形的方案
需要把图片用一个`<div>`包裹起来,然后对其应用相反的`rotate()`变形样式
...
.picture { width: 400px; transform: rotate(45deg); overflow: hidden; } .picture > img { max-width: 100%; transform: rotate(-45deg); }
1
2
3
4
5
6
7
8
9
10
它并没有一步到位地直接达到我们期望的效果,而是裁成一个八角形。      
![](http://img.aisss.top/17-12-15/46392692.jpg)

主要问题在于`max-width:100%`这条声明。100%会被解析为容器(.picture)的边长。但是,我们想让图片的宽度与容器的对角线相等,而不是与边长相等。将`max-width`扩大1.41倍。

如果用`scale()`变形样式来把这个图片放大,实际上会更加合理,原因如下:
- 图片的尺寸属性保留100%这个值,这样当浏览器不支持变形样式时仍然可以得到一个合理的布局
- 通过`scale()`变形样式来缩放图片时,是以它的中心点进行缩放的。通过`width`属性来放大图片时,只会以它的左上角为原点进行缩放,从而迫使我们动用额外的负外边距来把图片的位置调整回来。

![](http://img.aisss.top/17-12-15/37275161.jpg)
...
.picture{ width:400px; transform:rotate(45deg); overflow:hidden; } .picture>img{ max-width:100%; transform:roate(-45deg) scale(1.42); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    

### 裁切路径方案
它的主要思路是使用`clip-path`属性,但支持性不好。



## 切角效果
> CSS渐变、background-size、“条纹背景”

### 难题
把角切掉不仅是为了省钱,它还是一种非常流行的设计风格,不论是在印刷媒介还是在网页设计中都是如此。
但是,目前的 CSS 仍然无法做到只用一行简单直观的代码就生成这样的效果。这导致绝大多数网页开发者倾向于使用背景图片来达到目的。

### 解决方案
第一种方案来自于无所不能的 CSS 渐变。假设我们只需要一个角被切掉的效果,其中最大的窍门在于充分利用渐变的一大特性:
渐变可以接受一个角度作为方向,而且色标的位置信息也可以是绝对的长度值,这一点丝毫不受容器尺寸的影响。

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

1
2
3
4
![](http://img.aisss.top/17-12-15/87165453.jpg)     
事实上,第一行声明并不是必需的,加上它是将其作为回退机制。

现在,假设我们想要两个角被切掉的效果,以底部的两个角为例

background: #58a;
background:
linear-gradient(-45deg, transparent 15px, #655 0)
right,
linear-gradient(45deg, transparent 15px, #58a 0)
left;
background-size: 50% 100%;
background-repeat: no-repeat; //如果不关闭,每层渐变图案各自平铺了两次。这导致我们的两层渐变背景仍然是相互覆盖的

1
2
3
![](http://img.aisss.top/17-12-18/72570681.jpg)     

下面是4个切角效果

background:#58a;
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 left,
linear-gradient(-45deg,transparent 15px,#58a 0) bottom right;
background-size:50% 50%;
background-repeat:no-repeat;

1
2
3
4
![](http://img.aisss.top/17-12-18/67549746.jpg)     

但是它的可维护性并不理想。我们在改变背景色时需要修改五处;而在改变切角尺寸时需要修改四处。
使用预处理器(sass)的 mixin 可以帮助我们减少代码的重复度。

@minin beveled-corners($bg,$tl:0,$tr:$tl,$bl:$tl,$br:$tr){
background:$bg;
backgrounnd:
linear-gradient(135deg,transparent $tl,$bg 0) top left,
linear-gradient(225deg,transparent $tr,$bg 0) top right,
linear-gradient(45deg,transparent $bl,$bg 0) bottom left,
linear-gradient(-45deg transparent $br,$bg 0) bottom right;
background-size:50% 50%;
background-repeat:no-repeat;
}

//传入2~5 个参数调用
@include beveled-corners(#58a,15px,5px);

1
2
3
4
    

### 弧形切角
弧形切角(很多人也把这种效果称为“内凹圆角”,因为它看起来就像是圆角的反向版本),我们会用径向渐变:

background:#58a;
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 left,transparent 15px,#58a 0) bottom left,
radial-gradient(circle at bottom right,transparent 15px,#58a 0) bottom right;
background-size:50% 50%;
background-repeat:no-repeat;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
![](http://img.aisss.top/17-12-18/57969223.jpg)

### 内联SVG与border-image方案
### 裁切路径方案



## 梯形标签页
> 基本的3D变形,平行四边形

### 难题
网页开发者如果没有用精心设计的背景图片来实现梯形,那多半就是在用边框来模拟梯形两侧的斜边。
尽管这个技巧为我们节省了背景图片所产生的额外HTTP请求,并且可以很容易地适应不同的宽度,但它还不是最佳方案。它用光了仅有的两个伪元素,而且在样式层面上也不够灵活。

### 解决方案
我们可以在 CSS 中用 3D 旋转来模拟出这个效果:

transform:perspective(.5em) rotateX(5deg);

1
2
3
![](http://img.aisss.top/17-12-18/84181656.jpg)     
整个元素应用 3D 变形的,因此它上面的文字也变形了。对元素使用了3D变形之后,其内部的变形效应是“不可逆转”的。
唯一可行的途径就是把变形效果作用在伪元素上。

.tab {
position: relative;
display: inline-block;
padding: .5em 1em .35em;
color: white;
}

.tab::before {
content: ‘’; /* 用伪元素来生成一个矩形 */
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
z-index: -1;
background: #58a;
transform: perspective(.5em) rotateX(5deg);
}

1
2
3
4
5
6
7
 ![](http://img.aisss.top/17-12-18/97165838.jpg)   

当我们没有设置`transform-origin`属性时,应用变形效果会让这个元素以它自身的中心线为轴进行空间上的旋转。因此,元素投射到2D屏幕上的尺寸会发生多种变化:它的宽度会增加,它所占据的位置会稍稍下移,它在高度上会有少许缩减。
为了让它的尺寸更好掌握,我们可以为它指定`transform-origin:bottom`;,当它在 3D 空间中旋转时,可以把它的底边固定住,只有高度会发生变化。
不过这样一来,高度的缩水会变得更加显眼。

我们还可以换种思路:通过变形属性来改变它的尺寸。我们会发现,垂直方向上的缩放程度(也就是`scaleY()`变形属性)在达到130% 左右时刚好可以补足它在高度上的缩水:

transform: scaleY(1.3) perspective(.5em)
rotateX(5deg);
transform-origin: bottom;

1
2
3
4
![](http://img.aisss.top/17-12-18/71829833.jpg)     


其实,当你开始为标签页增加一些样式的时候,这个技巧的独特优势才会逐渐显现出来。

nav > a{
position:relative;
display:inline-block;
padding: .3em 1em 0;
}

nav > a::before{
content:’’;
position:absoltue;
top:0;left:0;right:0;bottom:0;
z-index:-1;
background:#ccc;
background-image: linear-gradient(
hsla(0,0%,100%,.6),
hsla(0,0%,100%,0));
border: 1px solid rgba(0,0,0,.4);
border-bottom:none;
border-radius: .5em .5em 0 0;
border-shodaw: 0 .15em white inset;
transform:perspective(.5em) rotateX(5deg);
transform-origin:bottom;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
![](http://img.aisss.top/17-12-18/53850367.jpg)     

我们只需要把`transform-origin`改成bottom left或bottom right,就可以立即得到左侧倾斜或右侧倾斜的标签页。
![](http://img.aisss.top/17-12-18/75860253.jpg)
它存在一个非常大的缺点:斜边的角度依赖于元素的宽度。不过,,斜度的差异一般非常难以察觉。


## 简单的拼图
> CSS渐变,基本的SVG,CSS动画,“条纹背景”,“只适用椭圆”

饼图在过去很长一段时期内完全无法通过Web技术创建出来,即便是最简单的两种颜色的形态也不例外。
过去要实现饼图,要么动用一个外部的图像处理软件来为饼图中的多个值制作多张图片,要么动用那些专门为复杂图表而设计的JavaScript框架。


### 基于transform方案
这个方案在结构层面是最佳选择:
它只需要一个元素作为容器,而其他部分是由伪元素、变形属性和 CSS 渐变来实现的。

我们换种思路:
**把圆形的左右两部分指定为两种颜色,然后用伪元素覆盖上去,通过旋转来决定露出多大的扇区。**


1. 第一步
为了把圆形的右半部分设置为棕色,我们要用到一个简单的线性渐变:

.pie {
width: 100px; height: 100px;
border-radius: 50%;
background: yellowgreen;
background-image: linear-gradient(right,transparent 50%,#655 0);
}

1
2
3
4
5
![](http://img.aisss.top/17-12-19/83074108.jpg)         


2. 第二步
我们设置伪元素相对于整个饼图进行重叠。不过现在还没有设置任何样式,它还起不到遮盖的作用,暂时只是一个透明的矩形:

.pie::before{
content:’’;
display:block;
margin-left:50%;
height:100%;
}

1
2
3
4
5
6
7
8
9
![](http://img.aisss.top/17-12-19/45613434.jpg)     


3. 第三步
在开始为它设置样式之前,我们还要再做一些观察和分析:
- 应该给它指定绿色背景。在这里使用`background-color: inherit`声明可以避免代码的重
复。
- 它是绕着圆形的圆心来旋转的,对它自己来说,这个点就是它左边缘的中心点。因此,我们应该把它的`transform-origin`设置为`0 50%`,或者干脆写成`left`。
- 因此要么给`.pie`设置`overflow:hidden`的样式,要么给这个伪元素指定合适的`border-radius`属性来把它变成一个半圆。

.pie::before{
content:’’;
display:block;
margin-left:50%;
height:100%;
background-color:inherit;
transform-origin:left;
border-radius:0 100% 100% 0/50%;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
![](http://img.aisss.top/17-12-19/77845116.jpg)     


4. 第四步
我们现在可以通过一个`rotate()`变形属性来让这个伪元素转起来。
![](http://img.aisss.top/17-12-19/24358896.jpg)

我们的饼图在显示0到50%的比率时运作良好,但如果我们尝试显示60%的比率时(比如指定旋转值为 .6turn 时),结果就变成如下图所示了
![](http://img.aisss.top/17-12-19/85057715.jpg)


5. 第五步
我们就会发现,可以使用上述技巧的一个反向版本来实现这个范围内的比率:
设置一个棕色的伪元素,让它在 0 至 .5turn 的范围内旋转。

.pie::before{
content:’’;
display:block;
margin-left:50%;
height:100%;
background:#655;
transform-origin:left;
border-radius:0 100% 100% 0/50%;
transform:rotate(.1turn);
}

1
2
3
4
5
![](http://img.aisss.top/17-12-19/709242.jpg)   


6. 第六步
由于已经找到了实现任意比率的方法,我们甚至可以用 CSS 动画来实现一个饼图从 0 变化到 100% 的动画,从而得到一个炫酷的进度指示器:

@keyframes spin{
to {transform:rotate(.5turn);}
}

@keyframes bg{
50% {background:#655;}
}

.pie::before{
content:’’;
display:block;
margin-left:50%;
height:100%;
background-color:inherit;
transform-origin:left;
border-radius:0 100% 100% 0/50%;
animation:spin 3s linear infinite,
bg 6s step-end infinite;
}

1
2
3
4
        

我们怎样才能制作出多个不同比率的静态饼图呢?理想情况下,我们希望可以用这样的方式来
书写结构:
20%
60%
1
2
3
4
5
6
7
8
9
10
11
12
首先,我们需要探索如何用内联样式来实现这个需求;    
接下来,我们就可以写一小段脚本来解析文本内容并把内联样式添加到元素上去。


第一个挑战:
我们无法为伪元素设置内联样式。如何解决?
用负的动画延时来直接跳至动画中的任意时间点,并且定格在那里。

第二个挑战:
动画是作用在伪元素上的,但我们希望最终内联样式可以设置在 .pie 元素上。如何解决?
由于`<div>`上并没有任何动画效果,我们可以用内联样式的方式为其设置`animation-delay`属性,然后再在伪元素上应用`animation-delay: inherit`属性。
整体代码如下:

@keyframes spin {
to { transform: rotate(.5turn); }
}

@keyframes bg {
50% { background: #655; }
}

.pie::before {
/* [其余的样式代码保持原样] */
animation: spin 50s linear infinite,
bg 100s step-end infinite;
animation-play-state: paused;
animation-deplay:inherit;

1
此时,我们就可以再次优化结构,把饼图的比率写到元素的内容中,就像我们最开始所期望的那样;然后我们通过一段简单的脚本来把`animation-delay`写到内联样式中

$$(.pie).forEach(function(pie){
var p=parseFloat(pie.textContent);
pie.style.animationDelay=’-‘+p+’s’;
});

1
2
3
4
5
6
我们还可以进一步优化,把比率文字放置在饼图的中心处,从而方便用户选中它。为了实现这一点,我们需要进行以下步骤:
- 把这个饼图的`height`换成`line-height`(或者添加一个跟`height`相等的`line-height`属性,但这会增加无意义的代码重复;其实`line-height`本身就可以起到设置高度的作用)。
- 通过绝对定位来完成对伪元素的尺寸设置和定位操作,这样它就不会把文字推到下面了。
- 增加`text-align: center`来实现文字的水平居中。

最终代码看起来会是这样的:

.pie{
position:relative;
width:100px;
line-height:100px;
border-radius:50%;
background:yellowgreen;
background-image:linear-gradient(to right,transparent 50%,#655 0);
color:transparent;
text-align:center;
}

@keyframes spin {
to { transform: rotate(.5turn); }
}

@keyframes bg {
50% { background: #655; }
}

.pie::before{
position:absolute;
top:0;left:50%;right:0;height:100%;
border-radius:0 100% 100% 0/50%;
background-color:inherit;
transform-origin:left;
animation:spin 50s linear infinite;
bg 100s step-end infinite;
animation-play-status:paused;
animation-deplay:inherit;
}
```

liborn wechat
欢迎您扫一扫上面的微信二维码,订阅我的公众号!