四年前,看了CSS3 Click Chart这篇文章之后,我第一次发现了calc() ,我当然非常高兴能够看到,基本的数学运算-加法,减法,乘法和除法-能够在CSS中应用。
大部分人可能会觉着预处理就可以实现逻辑运算。但是预处理器只能同单位的运算,如角度单位,时间单位,频率单位,分辨率单元和固定长度单位。而calc()
可以实现混合单位的逻辑运算。
1turn
总是360deg
,100grad
始终是90deg
,而3.14rad
总是180deg
。1S
始终是1000毫秒
,和1kHz
时总是1000Hz
的,1英寸
总是2.54厘米
或25.4毫米
或96px
,1dppx
始终等于96DPI
。这就是为什么预处理器能够在它们之间转换,进行混合运算。但是,因为缺少上下文关系,预处理器是不能处理1em
或1%
或1vmin
或1ch
是多少像素的。
我们先看一个简单的例子:
div {
font-size: calc(3em + 5px);
padding: calc(1vmax + -1vmin);
transform: rotate(calc(1turn - 32deg));
background: hsl(180, calc(2*25%), 65%);
line-height: calc(8/3);
width: calc(23vmin - 2*3rem);
}
在一些情况下,我们可能需要在calc()
函数里传变量。这个在主流的预处理器可以实现。
首先,用 Sass,我们就像任何其他原生的CSS功能一样,插入变量:
$a: 4em
height: calc(#{$a} + 7px)
LESS写法如下:
@a: 4em;
height: ~"calc(@{a} + 7px)";
还有Stylus:
a = 4em
height: "calc(%s + 7px)" % a
我们也可以使用原生的CSS变量,但要注意的是,只有在Firefox 31+
支持,其他的浏览器暂时还不支持CSS的变量呢。
--a: 4em;
height: calc(var(--a) + 7px);
为了能够使得calc()
函数正常的起作用,有以下几点要注意的事情。首先,除以零显然是行不通的。函数名和括号之间不可有空格。加号和减号运算符之间必须用空格分隔。
下面是几种错误的写法:
calc(50% / 0)
calc (1em + 7px)
calc(2rem+2vmin)
calc(2vw-2vh)
calc()
函数的参数必须是数字,有或无指定单位,都是可以的。虽然基本的支持是非常好的,但是我们可能还是会遇到一些麻烦。接下来让我们看几个例子,看下有哪些兼容性问题,以及是否是最佳的解决方案。
进一步了解运算属性
实现一个彩虹渐变。例子链接:
background: linear-gradient(#f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00);
但是,这些十六进制值看着不易理解。如果使用HSL()
和calc()
,就会更清晰些:
background: linear-gradient(hsl(calc(0*60), 100%, 50%),
hsl(calc(1*60), 100%, 50%),
hsl(calc(2*60), 100%, 50%),
hsl(calc(3*60), 100%, 50%),
hsl(calc(4*60), 100%, 50%),
hsl(calc(5*60), 100%, 50%),
hsl(calc(6*60), 100%, 50%));
可悲的是,在Firefox或Internet Explorer(IE)下,HSL()
,RGB()
,HSLA()
或RGBA()
里使用calc()
不起作用。也就是说demo
只在WebKit浏览器下运行。因此,在实践中,在这一点上,最好让预处理来执行运算。而且使用预处理还有一个优点是,循环生成列表:
$n: 6;
$l: ();
@for $i from 0 through $n {
$l: append($l, hsl($i*360/$n, 100%, 50%), comma);
}
background: linear-gradient($l);
为弹性元素添加渐变背景色
比方说,我们希望背景上顶部和底部有固定的1em
的条纹。唯一的问题是我们不知道元素的高度。一种解决办法是使用两个渐变:
background:
linear-gradient(#e53b2c 1em, transparent 1em),
linear-gradient(0deg, #e53b2c 1em, #f9f9f9 1em);
但是,如果我们使用calc()
就只需要一个渐变 就能搞定:
background:
linear-gradient(#e53b2c 1em, #f9f9f9 1em,
#f9f9f9 calc(100% - 1em),
#e53b2c calc(100% - 1em));
这种写法在所有支持calc()
和渐变的浏览器里都可以工作正常,因为涉及的混合单位,它和预处理器的逻辑运算是不能等效的。另外,我们可以通过定义变量使其更易于维护:
$s: 1em;
$c: #e53b2c;
$bg: #f9f9f9;
background:
linear-gradient($c $s,
$bg $s,
$bg calc(100% - #{$s}),
$c calc(100% - #{$s}));
注:出于一些原因,在chrome
和opera
里,有一个条纹比另一条略微的模糊、细。
斜条纹渐变
比方说,我们希望它的实际对角线的两侧延伸粗斜条纹。我们使用百分比来实现:
background:
linear-gradient(to right bottom,
transparent 42%, #000 0, #000 58%,
transparent 0);
在这种情况下,条纹的宽度将取决于元素本身的大小。有时候,我们正好需要用这个。比如,如果我们想用CSS
实现一个旗帜。在旗帜上添加一点绿色,黄色和蓝色渐变,标志爱好者可能认识-这是一面坦桑尼亚国旗。
background:
linear-gradient(to right bottom,
#1eb53a 38%, #fcd116 0,
#fcd116 42%, #000 0,
#000 58%, #fcd116 0,
#fcd116 62%, #00a3dd 0);
坦桑尼亚国旗(查看大图)
但是,如果我们想要我们的对角线条纹不依赖于元素的大小,而是固定的宽度呢?那么,我们就需要用到calc()
,在起点的时候,在50%的固定条的宽度加上的一半,在终点的时候,50%减去一半固定条纹的宽度,。如果我们想条纹的宽度为4em
,这样来写:
background:
linear-gradient(to right bottom,
transparent calc(50% - 2em),
#000 0,
#000 calc(50% + 2em),
transparent 0);
您可以调整窗口大小来测试一下这个demo。元素的大小是通过视口的大小来设定的,但是,视口改变,对角条纹始终保持相同的宽度。
已知大小中定位子元素
你可能已经看到了在父元素中间固定元素的技巧:
position: absolute;
top: 50%;
left: 50%;
margin: -2em -2.5em;
width: 5em;
height: 4em;
有了calc()
,我们就可以摆脱掉margin:
position: absolute;
top: calc(50% - 2em);
left: calc(50% - 2.5em);
width: 5em;
height: 4em;
我们还可以使用变量来定义宽度和高度:
$w: 5em;
$h: 4em;
position: absolute;
top: calc(50% - #{.5*$h});
left: calc(50% - #{.5*$w});
width: $w;
height: $h;
需要注意的是,使用偏移量(距顶部,距左侧)的初始定位是可以的,但如果以后你打算让元素的位置移动,那么你应该使用变换。这是因为更改转换只需要合成,但更改偏移是要重新布局,触发重绘的,因此,会影响性能。
系统的坐标在网格中的起点
由于position
有四个属性值,我一直没热衷使用过calc()
来定位相对于元素的右侧或底部的背景。但是,calc()
是背景相对的某个定位的元素中间的绝佳解决方案。
几年前,我发现自己想在一个起点固定的网格上创建一个表示坐标的背景。
网格坐标系统(查看大版)
该网格部分很容易实现:
background-image:
linear-gradient(#e53b2c .5em, transparent .5em) /* horizontal axis */,
linear-gradient(90deg, #e53b2c .5em, transparent .5em) /* vertical axis */,
linear-gradient(#333 .25em, transparent .25em) /* major horizontal gridline */,
linear-gradient(90deg, #333 .25em, transparent .25em) /* major vertical gridline */,
linear-gradient(#777 .125em, transparent .125em) /* minor horizontal gridline */,
linear-gradient(90deg, #777 .125em, transparent .125em) /* minor vertical gridline */;
background-size:
100vw 100vh, 100vw 100vh,
10em 10em, 10em 10em,
1em 1em, 1em 1em;
但是,但是我们怎样把起点放到中间,而不是在左上角?
首先,background-position: 50% 50%
这个写法是不正确的,它会在相对于元素50% 50%这个点的基础上再定位出 50% 50%的渐变点,而且top
和left
两个方向的渐变线是分开计算的。这个问题的解决方法是,使用 calc()
来重新定位这些渐变,目的是使这些渐变尽量的相对于在整个viewport
的中心位置,之后只要再向上和左两个方向位移坐标轴的一半或者栅格线的一半宽度即可。
background-position:
0 calc(50vh - .25em), calc(50vw - .25em),
0 calc(50vh - .125em), calc(50vw - .125em),
0 calc(50vh - .0625em), calc(50vw - .0625em);
同样,我们可以通过使用变量使其更易于维护:
演示链接 system of coordinates + grid #2 by Ana Tudor (@thebabydino) on CodePen.
保持长宽比和视窗尺寸
在构建HTML
幻灯片的时候,我总是希望在一个窗口下,沿着某一个轴的中心拖拽,图片能有固定的比例。
比例箱动画
我们假设幻灯片的大小比例为 4:3,我是一个宽屏显示器上演示。幻灯片要想覆盖视口,左,右肯定会有一些留白。
比例盒:情况1(查看大版)
视觉高度为100vh
。已知的高度,宽高比,可以得到宽度,公式为4/3 * 100vh
。我们想要让他在中间的话,需要从偏移一半视口的宽度(100vw / 2
),再减去幻灯片的宽度的一半(4/3 * 100vh / 2
)。这时我们就需要的calc()
,因为我们有混合单位。
.slide {
position: absolute;
left: calc(100vw/2 - 4/3*100vh/2);
width: calc(4/3*100vh);
height: 100vh;
}
然而,当我们的窗口小于4:3的时候。在这种情况下,幻灯片盖住了水平方向的窗口,在顶部和底部留有一定的空间。
比例盒:情况2(查看大版)
覆盖了视水平意味着宽度为100vw
。根据宽高比,我们可以得出高度为 3/4*100vw
。最后,顶部偏移量和刚刚的算法一样,也就是 100vh/2 - 3/4*100vw/2
。
@media (max-aspect-ratio: 4/3) {
.slide {
top: calc(100vh/2 - 3/4*100vw/2);
left: auto; /* Undo style set outside media query */
width: 100vw;
height: calc(3/4*100vh);
}
}
当然,我们还可以用变量来定义宽高比例。这里有一个在线的demo,可以通过调整窗口大小来测试。
$a: 4;
$b: 3;
.slide {
position: absolute;
top: 0;
left: calc(50vw - #{$a/$b/2*100vh});
width: $a/$b*100vh;
height: 100vh;
@media (max-aspect-ratio: #{$a}/#{$b}) {
top: calc(50vh - #{$b/$a/2*100vw});
left: 0;
width: 100vw;
height: $b/$a*100vw;
}
}
另外,我们还可以使用比全局变量更好的方式,使用mixin实现:
@mixin proportional-box($a: 1, $b: $a) {
position: absolute;
top: 0;
left: calc(50vw - #{$a/$b/2*100vh});
width: $a/$b*100vh;
height: 100vh;
@media (max-aspect-ratio: #{$a}/#{$b}) {
top: calc(50vh - #{$b/$a/2*100vw}); left: 0;
width: 100vw; height: $b/$a*100vw;
}
}
.slide {
@include proportional-box(4, 3);
}
注意,变量 $a
和 $b
必须是整数,才能正常的实现媒体查询。
以上在主流浏览器目前所有版本的支持。然而,直到最近, WebKit
浏览器都不支持在calc()
函数中使用视口单位,不过,这个问题已被分别在Safari 8
和Chrome 34
,Opera
浏览器中修复。
在幻灯片中间添加文字
关于幻灯片演示,我有两件事要说。
首先是为幻灯片没有真正覆盖整个视口的边缘,因为可能会被切断。这是一个容易解决。我简单地设置他们箱尺寸为边界盒上他们还设置了边界。
第一件事是,幻灯片并没有真正的覆盖窗口边缘,因为可能会被切断掉边框。这个很容易解决。只需要设置 box-sizing
为 border-box
就可以了。
第二件事是给空白的幻灯片上添加一行文字,来标识。
期望的结果(查看大版)
我不想使用绝对定位,所以我想我会用设置了合适的line-height
。
如果幻灯片的高度包含边框,覆盖了窗口的整个高度,那么行高就是 100vh
减去两个边框宽度:
$slide-border-width: 5vmin;
.slide {
/* The other styles */
box-sizing: border-box;
border: solid $slide-border-width dimgrey;
h1 {
line-height: calc(100vh - #{2*$slide-border-width});
}
}
在这种情况下,包括边界,相对于窗口水平垂直居中,那么它的高度就是$b/$a*100vw
。那么,标题的line-height
将是减去幻灯片的边框宽度的两倍:
line-height: calc(#{$b/$a*100vw} - #{2*$slide-border-width});
这是我最初的想法,理论上,应该是可行的。它确实在WebKit浏览器和IE可以。但事实证明,在Firefox
里,,calc()
值下,行高和其他一些属性不起作用。这个问题已解决。这样的话, calc()
就不是最佳的解决方案。幸运的是,还有很多其他的方法来解决这个问题(Flexbox的,绝对定位等等)。
在窗口上的固定点
有件事情,我特别热衷,那就是CSS 3D
-尤其是使用CSS
创建几何3D
形状。如果我创建一个形状,通常会放到窗口的中间位置。
我会设置元素的 perspective
, 还有这个图形的父级元素。这个是透视图,这里我们不做详细介绍。 如果您想了解更多关于他们是如何定位的,可以看我的CSS-技巧博客文章。
设置一个 perspective
,是为了确保我们看到的一切,越近物体越大,越远物体越小。这个属性perspective
接受长度值,值越小,对比度越大,离我们更近,反之,渐远同理。
现在,我们来说一个非常简单的3D
形状 - 一个立方体,例如 - 就在我们屏幕的中央。它看起来并不十分3D
:这是太对称,正面是完全不透明的,我们只能看到最前面的面。
立方体(查看大版)
我们可以将其旋转一点,旋转30度
吧,围绕其y
轴线(即穿过立方体中间的垂直轴)或围绕其点x
轴线。这样看起来更好,但我们只能看到两个面。此外,我们看到物体旋转了,但这不是我们的目的。
旋转立方体(查看大版)
还有件事,我们可以做调整的就是视觉点。这个属性叫perspective-origin
。它的默认值是50% 50%
。这是相对于场景,我们知道,50% 50%
的视觉点会将形状定位到中心点。现在,我们希望这个向上和向右。那么只需要设置perspective-origin: 100% 0
就可以了。但这里有一个问题:我们看到的立方体展示效果将取决于窗口的尺寸(你可以测试这个通过调整视口)。
改变的窗口大小,我们看到的立方体效果。
perspective-origin
定义为 100% 0
,是从右上角看过去,但立方体是总是都在场景的中部。因为这个原因,改变窗口的尺寸将改变为 50% 50%
(立方体被定位的位置),和 100% 0
(我们设置的视角起点)之间。
可以使用calc()
来设置perspective-origin
,从默认值的50%
加上或者减去一个固定值。
perspective-origin: calc(50% + 15em) calc(50% - 10em);
解决方案
您可以通过调整视口测试。
你会用calc()吗
你已经使用calc()
了?如果是的话,那么你用在了什么情况下?