梯形标签页
梯形标签在导航栏部分经常遇到,书中只提到3D旋转的方案,但是万能的裁切和渐变怎么能丢?于是有了下面三种方式。
裁切方案
在形状这部分,裁切是一种很强大的方式,以左上角为原点,梯形的绘制对于它来说很简单。
<div class="echelon-a">this is a test</div>
div{
margin:20px;
max-width: 10em;
min-height: 4em;
font:125%/4em sans-serif;
text-align: center;
color: white;
}
.echelon-a{
background: #58a;
-webkit-clip-path: polygon(20% 0,80% 0,100% 100%,0 100%);
}

渐变方案
到现在为止,利用渐变已经实现了条纹背景、复杂背景、图像边框以及下面的梯形。不得不说渐变真是一把利器。只要注意多层渐变的覆盖,设置好每层渐变的大小和位置即可。
.echelon-b{
background: linear-gradient(120deg,transparent 40px,#58a 0) top left,
linear-gradient(-120deg,transparent 40px,#58a 0) top right;
background-size: 50% 100%;
background-repeat: no-repeat;
}

3D旋转方案
在现实的三维世界中旋转一个矩形,然后其在平面上的投影就是一个梯形。利用这个思路,可以尝试使用3D旋转来模拟一个梯形。
如果对元素直接使用transform: perspective(.5em) rotateX(5deg);,元素内部也会进行旋转,这并不是我们想要的结果:
这时候可以利用伪元素来生成一个矩形,设置其堆叠次序,然后旋转为一个矩形。
仔细观察上面的图还会发现,内部的文字不居中了而且也变矮了,这是由于元素是以自身的中心线为轴进行空间旋转的。因此投影到2D屏幕上尺寸会发生变化,导致高度缩减,宽度增加。
为了控制它的尺寸,这时候可以指定transform-origin为bottom,当它在3D空间旋转时,可以把它的底边固定住。保证宽度不增加,然后再通过变形属性,即scaleY()来保证高度不会变低。
下面是完整的代码:
.echelon-c{
position: relative;
display: inline-block;
}
.echelon-c:before{
content: "";
position: absolute;
top: 0;right: 0;bottom: 0;left: 0;
z-index: -1;
background: #fb3;
transform: scaleY(1.7) perspective(.5em) rotateX(5deg);
transform-origin: bottom;
}

梯形导航
上面说了3种方式主要是用于实现导航,这里就简单模拟了一个梯形的导航。详细过程就不说了,直接上代码。
一定要注意在使用3D旋转时的元素的堆叠顺序。
<nav>
<a href="#">Home</a>
<a href="#" class="selected">Contact</a>
<a href="#">About</a>
</nav>
<main>Content</main>
nav{
position: relative;
padding-left: 1em;
}
nav > a {
position: relative;
display: inline-block;
padding: .3em 1em 0;
color: inherit;
text-decoration: none;
margin: 0 -.3em;
}
nav>a::before,main{
border: .1em solid rgba(0,0,0,.4);
}
nav a::before{
content: "";
position: absolute;
top: 0;right: 0;bottom: 0;left: 0;
z-index: -1;
border-bottom: none;
border-radius: .5em .5em 0 0;
background: #ccc linear-gradient(hsla(0,0%,100%,.6),hsla(0,0%,100%,0));
box-shadow: 0 .15em white inset;
transform: scale(1.1,1.3) perspective(.5em) rotateX(5deg);
transform-origin: bottom;
}
main {
width: 30em;
display: block;
margin-bottom: 1em;
background: #eee;
padding: 1em;
border-radius: .15em;
}
nav a:hover{
z-index: 2;
}
nav a:hover::before{
background-color: #eee;
margin-bottom: -1px;
}

饼图
一般在统计图表、进度指示器、定时器等场景中会用到饼图。我们希望能够很简单的创建出对应百分比的饼图,比如创建一个20%的饼图,只需一行代码,不引入外部插件。
<div class="pie">20%</div>
可以用下面这种方式解决。
基于transform的解决方案
首先要利用之前的border-radius创建一个圆,然后利用渐变遮盖半个圆。即:
<div class="pie-test"></div>
.pie-test{
width: 100px;
height: 100px;
border-radius: 50%;
background: yellowgreen;
background-image: linear-gradient(to right,transparent 50%,#655 0);
}

然后设置伪元素,对右半圆进行遮盖
.pie-test::before{
content: "";
display: block;
margin-left: 50%;
height: 100%;
border: 1px dashed black;
}
设置border只是为了演示方便

对于伪元素,有3件微小的工作需要它完成
- 第一件,希望它能遮盖有半侧的黑色部分,设置
background-color:inherit即可。 - 第二件,希望它绕着圆形的圆心旋转,设置
transform-origin: left或者0 50% - 第三件,不希望它是矩形的,可以设置.pie-test为
overflow:hidden或者给伪元素设置合理的border-radius
伪元素:很惭愧,就做了三件微小的工作,谢谢大家!希望下次能控制天气~
如果想要显示比例20%,只要旋转.2turn即可
具体代码如下:
.pie-test::before{
content: "";
display: block;
margin-left: 50%;
height: 100%;
border: 1px dashed black;
background-color: inherit;
transform-origin: left;
border-radius: 0 100% 100% 0 / 50% 50%;
transform: rotate(.2turn);
}

如果我们想要60%的比例,设置.6turn会出现下面的结果:

这时候可以把50%-100%看做另外一个问题,设置一个黑色的伪元素,让它在0-.5turn范围内旋转,此时,要得到一个60%比率的饼图,代码如下:
.pie-test::before{
content: "";
display: block;
margin-left: 50%;
height: 100%;
border: 1px dashed black;
background-color: #655;
transform-origin: left;
border-radius: 0 100% 100% 0 / 50%;
transform: rotate(.1turn);
}

利用上面的代码可以用动画来做一个从0-100%的进度指示器了
这里给出完整代码:
<div class="pie-a"></div>
@keyframes spin{
to {transform: rotate(.5turn);}
}
@keyframes bg{
50% {background: #655;}
}
.pie-a{
width: 100px;
height: 100px;
border-radius: 50%;
background: yellowgreen;
background-image: linear-gradient(to right,transparent 50%,#655 0);
}
.pie-a::before{
content: '';
display: block;
margin-left: 50%;
height: 100%;
background-color: inherit;
transform-origin: 0 50%;
border-radius: 0 100% 100% 0 / 50%;
animation: spin 3s linear infinite,
bg 6s step-end infinite;
}

下面再来实现最初的需求,核心是利用动画。
一个负的延时值是合法的,它意味着动画会立即开始播放,但会自动前进到延时值的绝对处,这样在视觉上就好像动画跳过指定时间直接播放了。
然后再让它暂停到我们需要的位置即可。
我们可以设置一个长达100S的动画,便于计算。
代码如下:
<div class="pie">0%</div>
<div class="pie">40%</div>
<div class="pie">80%</div>
@keyframes spin{
to {transform: rotate(.5turn);}
}
@keyframes bg{
50% {background: #655;}
}
.pie{
position: relative;
display: inline-block;
width: 100px;
line-height: 100px;
border-radius: 50%;
background: yellowgreen;
background-image: linear-gradient(to right,transparent 50%,#655 0);
color: transparent;
}
.pie::before{
content: '';
position: absolute;
top: 0;left: 50%;
width: 50%;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-state: paused;
animation-delay: inherit;
}
<script type="text/javascript">
function $$(selector,context){
context = context || document;
var elements = context.querySelectorAll(selector);
return Array.prototype.slice.call(elements);
}
$$('.pie').forEach(function(pie){
var p = pie.textContent;
pie.style.animationDelay = '-' + parseFloat(p) + 's';
});
</script>

SVG解决方案
还有一种是利用SVG来实现,主要原理的是stroke-dasharray属性,使它的虚线间隙超过圆的周长,利用描边来表示百分比。下面不在细说,直接给出实现代码:
<div class="pie-svg">20%</div>
<div class="pie-svg">80%</div>
<script type="text/javascript">
function $$(selector,context){
context = context || document;
var elements = context.querySelectorAll(selector);
return Array.prototype.slice.call(elements);
}
$$('.pie-svg').forEach(function(pie){
var p = parseFloat(pie.textContent);
var NS = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(NS, "svg");
var circle = document.createElementNS(NS, "circle");
var title = document.createElementNS(NS, "title");
circle.setAttribute("r", 16);
circle.setAttribute("cx", 16);
circle.setAttribute("cy", 16);
circle.setAttribute("stroke-dasharray", p + " 100");
svg.setAttribute("viewBox", "0 0 32 32");
title.textContent = pie.textContent;
pie.textContent = '';
svg.appendChild(title);
svg.appendChild(circle);
pie.appendChild(svg);
});
</script>

上面直接利用JavaScript完成自动化。也可以自己编写SVG标签。
svg{
width: 100px;height: 100px;
transform: rotate(-90deg);
background: yellowgreen;
border-radius: 50%;
}
circle{
fill: yellowgreen;
stroke: #655;
stroke-width: 32;
}