1. 程式人生 > >從今天起開始使用 CSS 屬性 calc()

從今天起開始使用 CSS 屬性 calc()

四年前,看了CSS3 Click Chart這篇文章之後,我第一次發現了calc() ,我當然非常高興能夠看到,基本的數學運算-加法,減法,乘法和除法-能夠在CSS中應用。

大部分人可能會覺著預處理就可以實現邏輯運算。但是前處理器只能同單位的運算,如角度單位,時間單位,頻率單位,解析度單元和固定長度單位。而calc()可以實現混合單位的邏輯運算。

1turn總是360deg100grad始終是90deg,而3.14rad總是180deg1S始終是1000毫秒,和1kHz時總是1000Hz的,1英寸總是2.54釐米25.4毫米96px1dppx始終等於96DPI。這就是為什麼前處理器能夠在它們之間轉換,進行混合運算。但是,因為缺少上下文關係,前處理器是不能處理1em

1%1vmin1ch是多少畫素的。

我們先看一個簡單的例子:

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)";
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}));

注:出於一些原因,在chromeopera裡,有一個條紋比另一條略微的模糊、細。

斜條紋漸變

比方說,我們希望它的實際對角線的兩側延伸粗斜條紋。我們使用百分比來實現

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%的漸變點,而且topleft兩個方向的漸變線是分開計算的。這個問題的解決方法是,使用 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);

同樣,我們可以通過使用變數使其更易於維護:

在構建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 8Chrome 34Opera瀏覽器中修復。

關於幻燈片演示,我有兩件事要說。

首先是為幻燈片沒有真正覆蓋整個視口的邊緣,因為可能會被切斷。這是一個容易解決。我簡單地設定他們箱尺寸為邊界盒上他們還設定了邊界。
第一件事是,幻燈片並沒有真正的覆蓋視窗邊緣,因為可能會被切斷掉邊框。這個很容易解決。只需要設定 box-sizingborder-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() 了?如果是的話,那麼你用在了什麼情況下?