开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情
自适应椭圆
难题
你可能注意到过,给任何正方形元素设置一个足够大的 border-radius ,就可以把它变成一个圆形。所用到的 CSS 代码如下所示:
div{
background: #fb3;
width: 200px;
height: 200px;
border-radius: 100px; /* >= 正方形边长的一半 */
}
你可能还注意到了,如果指定任何大于 100px 的半径,仍然可以得到一个圆形。
规范特别指出了这其中的原因“:当任意两个相邻圆角的半径之和超过 容器 的尺寸时,用户代理必须按比例减小各个边框半径所使用的值,直到它们不会相互重叠为止。”
www.cnblogs.com/happymental… border-radius 文章.
不过,我们往往不愿意对一个元素指定固定的宽度和高度,因为我们希望它能根据其内容自动调整并适应,而内容的长短不可能在事先就知道。即使是在设计一个静态网站的时候(元素的内容可以预先确定),我们也可能 需要在某个时刻改变其内容;或者我们为它准备了一款尺寸略有差异的回退字体,而不同字体对相同内容的渲染结果很可能是不同的。在这个案例中,我们通常期望达到这个效果:如果它的宽高相等,就显示为一个圆;如果宽 高不等,就显示为一个椭圆。可是,我们前面的代码并不能满足这个期望。
当宽度大于高度时,我们得到的形状:
解决
border-radius 有一个鲜为人知的真相:它可以单独指定水平和垂直半径,只要用一个斜杠( / )分隔这两个值即可。这个特性允许我们在拐角处创建椭圆圆角。因此,如果我们有一个尺寸为 200px * 150px 的元素,就可以把它圆角的两个半径值分别指定为元素宽高的一半,从而得到一个精确的椭圆:
div{
border-radius: 100px / 75px;
}
当然这段写死的代码还是有一定问题的,无法做到根据内容的长短做出相应的响应式操作。
当然我们知道,border-radius 不仅可以接受长度值,还可以接受百分比值。这个百分比值会基于元素的尺寸进行解析,即宽度用于水平半径的解析,而高度用于垂直半径的解析。这意味着相同的百分比可能会计算出不同的水平和垂直半径。因此,如果要创建一个自适应的椭圆,我们可以把这两个半径值都设置为 50%。 最终就会达到我们想要的效果。
半椭圆
现在我们已经知道如何用 CSS 来生成一个自适应的椭圆了,接下来很自然地就会问到:我们是否还能生成其他常见的形状呢,比如椭圆的一部分?让我们先来思考一下半椭圆。
幸运的是, border-radius 的语法比我们想像中灵活得多。你可能会惊讶地发现 border-radius 原来是一个简写属性。我们可以为元素的每个角指定不同的值,而且还有两种方法可以做到这一点。第一种方法就是使用它所对应的各个展开式属性:
border-top-left-radius
border-top-right-radius
border-bottom-right-radius
border-bottom-left-radius
当然,我们也可以一次性向 border-radius 传 四个不同的值以空格隔开。
在W3C上查border-radius属性时,会发现上面描述的语法是这样的:
border-radius: 1-4 length|% / 1-4 length|%;
这是什么意思呢,我也看不懂,后来百度了解到,这是border-radius的完整写法,我们平时的写法其实都是简写,平时我们写的border-radius : 50px,其实完整的写法应该是:
border-radius : 50px 50px 50px 50px / 50px 50px 50px 50px
“/”前的四个数值表示圆角的水平半径,后面四个值表示圆角的垂直半径,什么是水平半径和垂直半径呢,见下图
子弹头 椭圆
div{
border-radius: 100% 0 0 100% / 50%;
}
四分之一椭圆
div{
border-radius: 100% 0 0 0;
}
Simurai 以 其 精 湛 的 手 法 将 border-radius 发 挥 到 了 极 致,其 糖 果 按 钮 simurai.com/archive/but… 展示了各种奇妙的形状
平行四边形
平行四边形其实是矩形的超集:它的各条边是两两平行的,但各个角则不一定都是直角。在视觉设计中,平行四边形往往可以传达出一种动感。
我们使用 skew() 属性来对 图形进行变换。
div{
transform: skewX(-45deg);
}
我们的按钮,在应用任何变形样式之前:
变形成功,但是,这导致它的内容也发生了斜向变形,这很不好看,而且难读:
当然这并不是我们想要的
嵌套元素方案
我们可以对内容再应用一次反向的 skew() 变形,从而抵消容器的变形效果,最终产生我们所期望的结果。不幸的是,这意味着我们将不得不使用一层额外的 HTML 元素来包裹内容
.div_1 {
width: 200px;
height: 50px;
line-height: 50px;
background-color: aqua;
text-align: center;
transform: skew(-45deg);
}
.div_1_1 {
transform: skew(45deg);
}
伪元素方案
另一种思路是把所有样式(背景、边框等)应用到伪元素上,然后再对伪元素进行变形。 我们希望伪元素保持良好的灵活性,可以自动继承其宿主元素的尺寸,甚至当宿主元素的尺寸是由其内容来决定时仍然如此。一个简单的办法是给宿主元素应用 position: relative 样式,并为伪元素设置 position: absolute,然后再把所有偏移量设置为零,以便让它在水平和垂直方向上都被拉伸至宿主元素的尺寸。代码看起来是这样的:
.button {
position: relative;
/* 其他的文字颜色、内边距等样式…… */
}
.button::before {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
}
这是一个很美好的技巧,即节约了代码又达到了同样的效果。
菱形
在视觉设计中,把图片裁切为菱形是一种常见的设计手法,但在 CSS 中还没有一种简单直观的方法来实现它。事实上,直到最近,这种效果才基本成为可能。当网页设计师想要实现这种设计风格时,他们通常不希望在图像处理软件中预先把图片裁好。显然不用说你也知道,这个方法的可维护性并不好。如果未来有人想修改图片风格,将很难增加其他效果,而且最终往往会搞得一团糟。
基于变形的方案
<div class="picture">
<img src="adam-catlace.jpg" alt="..." />
</div>
.picture {
width: 400px;
transform: rotate(45deg);
overflow: hidden;
}
.picture > img {
max-width: 100%;
transform: rotate(-45deg);
}
主要问题在于 width: 100% 这条声明,100% 会被解析为容器(.div_3)的边长。但是,我们想让图片的宽度与容器的对角线相等,而 不是与边长相等。没错,我们用到勾股定理了。45度角斜边长为直角边的根号2倍,向上取整为 142%,因为我们不希望因为计算的舍入问题导致图片在实际显示时稍小(但稍大是没问题的,反正我们都是在裁切图片嘛)。
最终,经过多方实验,决定用 scale() 变形样式来把这个图片放大,实际上会更加合理,原 因如下:
- 我们希望图片的尺寸属性保留 100% 这个值,这样当浏览器不支持变形样式时仍然可以得到一个合理的布局。
- 通过 scale() 变形样式来缩放图片时,是以它的中心点进行缩放的(除非我们额外指定了 transform-origin 样式)。通过 width 属性来放大图片时,只会以它的左上角为原点进行缩放,从而迫使我们动用额外的负外边距来把图片的位置调整回来。
.picture {
width: 400px;
transform: rotate(45deg);
overflow: hidden;
}
.picture > img {
max-width: 100%;
transform: rotate(-45deg) scale(1.42);
}
裁切路径方案
上面的方法确实可以奏效,但它基本上是一个 hack。这个方法需要一层额外的 HTML 标签,这不够简洁;代码本身也不够直观;它甚至还不够健壮 —— 如果我们碰巧要处理一张非正方形的图片,这个小把戏就会原形毕露
clip-path 属性
MDN:
可以创建一个只有元素的部分区域可以显示的剪切区域。区域内的部分显示,区域外的隐藏。剪切区域是被引用内嵌的URL定义的路径或者外部svg的路径,或者作为一个形状例如circle().。clip-path属性代替了现在已经弃用的剪切 clip 属性。
blog.csdn.net/yufengaotia… 通俗易懂的参考
我们将会使用 polygon()(多边形)函数来指定一个菱形。实际上,它允许我们用一系列(以逗号分隔的)坐标点来指定任意的多边形。我们甚至可以使用百分比值,它们会解析为元素自身的尺寸。代码如下所示:
clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
但完全不需要多一层 HTML 标签和八行难以破译的 CSS 代码,只需要清清爽爽的一行代码就可以搞定。
当然,它也可以参与动画。
.div_4 {
clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
transition: 1s clip-path;
}
.div_4:hover {
clip-path: polygon(0 0, 100% 0,
100% 100%, 0 100%);
}
在一度痛快之后,我们不得不承认这个 属性 目前的兼容性 还很差,但我们确实得知道这个属性的存在,万一以后就普及了呢。
切角
把角切掉不仅是为了省钱,它还是一种非常流行的设计风格,尤其是在最近,当扁平化设计的风头完全盖过拟物化之后,这种效果就愈发流行了。当切角效果只应用在元素的某一侧,且切角的尺寸刚好达到元素高度的 50% 时,就会得到一个箭头形状,这在按钮和面包屑导航中的应用非常普遍
上图是一个使用了切角效果的按钮,箭头形状很好地强调了它自身的含义
我们或许已经想到,使用三角形盖住元素的顶角来模拟切角效果(当网页背景是纯色时),或者使用一张或多张已经切过角的图片来作为整个元素的背景。
很显然这些方法不够灵活。我们还有更好的方法吗?
解决方案
假设我们只需要一个角被切掉的效果,以右下角为例。这其中最大的窍门在于充分利用渐变的一大特性。
.div_5 {
width: 200px;
height: 100px;
background: #58a;
background:
linear-gradient(-45deg, transparent 15px, #58a 0);
}
注意点:
“如果某个色标的位置值比整个列表中在它之前的色标的位置值都要小,则该色标的位置值会被设置为它前面所有色标位置值的最大值。”
——CSS 图像(第三版)
第三行声明并不是必需的,加上它是将其作为回退机制
当然,一个角的情况十分简单,当需要两个角时如何选择呢。
这是我们最初的代码
.div_6 {
width: 200px;
height: 100px;
background: #58a;
background:linear-gradient(-45deg, transparent 15px, #58a 0),
linear-gradient(45deg, transparent 15px, #655 0);
}
新的尝试
background: #58a;
background:
linear-gradient(-45deg, transparent 15px, #58a 0)
right,
linear-gradient(45deg, transparent 15px, #655 0)
left;
background-size: 50% 100%;
又失败了,尽管我们已经用了background-size,但这两层渐变仍然是相互覆盖的,经过多次研究后,终于发现了问题所在,
background-repeat需要关闭。
最终成功达到效果
这时我们知道如何处理四个缺角同时存在的情况了。
这是四个切角同时存在的情况
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 right,
linear-gradient(45deg, transparent 15px, #58a 0)
bottom left;
background-size: 50% 50%;
background-repeat: no-repeat;
当然,还有更多其他形状类型的切角,svg帮我们实现了,这里就不讨论了
梯形标签页
当我们拿到如下图这种需求,我们该如何是好呢,
一般情况下我们可以做出梯形,但是想要产生圆角却不容易。
解决方案
三维立体旋转让我们解决了这个问题。
.div_8 {
width: 200px;
height: 50px;
line-height: 50px;
text-align: center;
background-color: aqua;
transform: perspective(.5em) rotateX(5deg);
}
我们会发现文字也跟着旋转了,当然我们可以像之前一样采用同样的两种方式,一个内部加一个div 另一种把梯形变形放在伪元素上。
这里直接用第二种方式,并设置了 transform-origin 属性让它在 3D 空间中旋转时,可以把它的底边固定住。经过一番调整,我们把梯形设置为这样
.div_8::before {
content: '';
/* 用伪元素来生成一个矩形 */
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: -1;
background: #58a;
transform: scaleY(1.3) perspective(.5em) rotateX(3deg);
transform-origin: bottom;
}
值得注意的是 这里我们采用了 scaleY 来撑篙伪元素容器,而没用采用 margin-top 是考虑到回退机制,一旦 transform 不支持,盒子会保持原有的样子,而 若使用 margin-top 则会出现顶部大量留白的问题。
而我们这里使用 css3 是为了 达到圆角的目的。
这是一个成品图
尽管使用这个优点不少,但这个技巧也不是完美无缺的。它存在一个非常大的 缺点:斜边的角度依赖于元素的宽度。因此,当元素的内容长度不等时,想要得到斜度一致的梯形就很伤脑筋了。不过,对于宽度变化不大的多个元素(比如导航菜单)来说,这个方法还是非常管用的。在这种场景下,斜度的差异非常难以察觉。
简单的饼图(简单的尝试)
饼图在日常开发中十分常见,我们能否用 css3 实现一些简单的饼图效果呢。
基于 transform 的解决方案
首先我们需要准备一个 div
<div class="div_9"></div>
以及样式
.div_9 {
width: 100px;
height: 100px;
border-radius: 50%;
background: yellowgreen;
}
我们的饼图是绿色的,并将采用棕色( #655)来显示比率,这里有个思路是使用伪元素 覆盖上去。
我们一步步尝试:
background-image:
linear-gradient(to right, transparent 50%, #655 0);
我们先给容器盒子加上了线性渐变,设置整个右侧的背景色 #655
我们准备采用伪元素进行遮罩。 伪元素样式
.div_9::before {
content: '';
display: block;
margin-left: 50%;
height: 100%;
border-radius: 0 100% 100% 0 / 50%;
background-color: inherit;
transform-origin: left;
}
接下来我们可以通过 rotate() 来露出比例。
看起来听完美的,但这只支持0-50%的比例。
如果把 50%~100% 的比率看作另外一个问题,我们就会发现,可以使用上述技巧的一个反向版本来实现这个范围内的比率:设置一个棕色的伪元素,让它在 0 至 .5turn 的范围内旋转。因此,要得到一个 60% 比率的饼图,伪元素的代码可能是这样的:
.div_10::before {
content: '';
display: block;
margin-left: 50%;
height: 100%;
border-radius: 0 100% 100% 0 / 50%;
background: #655;
transform-origin: left;
transform: rotate(.1turn);
}
由于已经找到了实现任意比率的方法,我们甚至可以用 CSS 动画来实现一个饼图从 0 变化到 100% 的动画,从而得到一个炫酷的进度指示器:
@keyframes spin {
to { transform: rotate(.5turn); }
}
@keyframes bg {
50% { background: #655; }
}
.pie::before {
content: '';
display: block;
margin-left: 50%;
height: 100%;
border-radius: 0 100% 100% 0 / 50%;
background-color: inherit;
transform-origin: left;
animation: spin 3s linear infinite,
bg 6s step-end infinite;
}
这个效果很棒,但是我们想要的是指定数字,如何动态的显示区域呢,
我们将使用上面那个动画,但动画必须处于暂停状态。跟常规情形下我们让动画动起来的做法不一样,这里我们要用负的动画延时来直接跳至动画中的任意时间点,并且定格在那里。先来看看负的 animation-delay 在规范中的解释。
因为我们的动画是暂停的,所以动画的第一帧(由负的 animation-delay 值定义)将是唯一显示出的那一帧。在饼图上显出的比率就是我们的 animation-delay 值在总的动画持续时间中所占的比率。举例来说,如果动画持续时间定为 6s,我们只需要把animation-delay 设置为 -1.2s,就能显示出 20% 的比率。为了简化这个计算过程,我们可以设置一个长达 100s 的持续时间。别忘了,这里的动画是永远处在暂停状态的,因此我们指定的持续时间并不会产生其他副作用。
还有一个问题,我们使用到了伪元素,但是js没有办法直接操作 伪元素的 样式,我们需要借助伪元素的宿主元素来进行操作。把 animation-delay 设置在 宿主元素上,在伪元素中直接使用 animation-delay: inherit;,就可以完美获取动画效果。
经过上述讨论,代码可以是下面这样
<div class="div_12" style="animation-delay: -20s">50</div>
let div = document.querySelectorAll('.div_12');
div.forEach((pie) => {
let p = parseFloat(pie.textContent);
pie.style.animationDelay = '-' + p + 's';
});
.div_12 {
position: relative;
width: 200px;
height: 200px;
line-height: 100px;
border-radius: 50%;
background: yellowgreen;
background-image:
linear-gradient(to right, transparent 50%, #655 0);
color: transparent;
text-align: center;
}
.div_12::before {
content: '';
display: block;
margin-left: 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;
}
当然这仅仅也只是研究阶段,真正的饼图还是需要canvas的介入,但是如果你只是想要一些简单的效果也没有考虑到兼容性的问题,则可以使用这种方式。
conic-gradient
圆锥渐变
或许,你觉得上面的方法太繁琐了,这里有更简便的方案
.div_13 {
width: 200px;
height: 200px;
border-radius: 50%;
background: yellowgreen;
background: conic-gradient(yellowgreen 0, yellowgreen 30%, #655 30%, #655 50%, #f60 50%);
}
conic-gradient 属性很好的帮我们处理好了这个事情。