CSS變量在前端複雜佈局和交互上的探索


寫在前面:

本文將和各位老鐵一塊兒探索如何使用CSS變量來下降複雜佈局和交互的代碼編寫難度,並使其更易於維護。這裏即將分享兩篇該系列的文章,本篇是分享css變量的探索應用的各類用例。(ps:看懂和理解本文須要有必定的前端基礎)
css

進入文章前,咱們先看下圖,一張網站經常使用的響應式信息圖表佈局圖,固然實現的方式不少,若是我告訴您一個CSS聲明會在如下圖像中對寬屏狀況(左測)和第二個(右測)之間產生差別,用一個CSS聲明實如今寬屏幕的狀況下對奇數項和偶數項進行區分,從而來實現下面效果,你會不會以爲有點驚喜?html

On the left, a screenshot of the wide screen scenario. Each item is limited in width and its components are arranged on a 2D 2x2 grid, with the first level heading occupying an entire column, either the one on the right (for odd items) or the one on the left (for even items). The second level heading and the actual text occupy the other column. The shape of the first level heading also varies depending on the parity — it has the top left and the bottom right corners rounded for the odd items and the other two corners rounded for the even items. On the right, a screenshot of the narrower scenario. Each item spans the full viewport width and its components are placed vertically, one under another — first level heading, second level heading below and, finally, the actual text.

或者僅僅一個CSS聲明就能區別下面的壓縮和擴展的狀況?
前端

Animated gif. Shows a green button with a magnifier icon. Clicking this button makes it slide right and its background to turn red while a text search field slides out of it to the left and the magnifier morphs into a close (crossmark) icon.

有點意思吧,接下來,咱們就一塊兒探索CSS變量在複雜佈局和交互的一些小運用。關於CSS變量是什麼以及如何開始使用它們的文章已經不少了,因此咱們不會在這裏深刻討論。咱們將直接深刻了解CSS變量爲何對實現這些狀況和其餘狀況有用,而後咱們將進一步詳細解釋如何實現各類狀況。咱們將從頭編寫一個實際示例,一步一步地編寫,最後,您將經過使用相同技術的更多演示得到一些引人注目的東西。因此讓咱們開始吧!(ps:本文樣式都是scss編寫)《DRY Switching with CSS Variables: The Difference of One Declaration》BY ANA TUDOR ONDECEMBER 5, 2018(注:原文)git


爲何CSS變量頗有用

對我而言,CSS變量的最大優勢是它們以邏輯,數學和輕鬆的方式爲事物的樣式打開了大門。看下面這個陰陽圖,實際上是使用loader元素的兩個僞元素建立兩個半部分github

Animated gif. The yin and yang symbol is rotating while its two lobes alternate increasing and decreasing in size - whenever one is increasing, it squishes the other one down.

旋轉☯符號,兩個半的大小逐漸增大和減少。咱們使用相同的backgroundborder-colortransform-originanimation-delay值兩半。這些值都取決於--i最初設置爲0兩半(僞元素)的開關變量,但隨後咱們將其更改1爲後半部分(:after僞元素),從而動態修改全部這些屬性的計算值。sass

若是沒有CSS變量,咱們將不得再也不次在:after僞元素上設置全部這些屬性(border-color、transform-origin、background、anima -delay),並可能出現一些錯誤,甚至忘記設置其中一些屬性。
bash

通常狀況下切換的工做原理

1、在零和非零值之間切換

在陰陽加載器的特定狀況下,咱們在兩半(僞元素)之間改變的全部屬性對於開關的一個狀態變爲零值而對於另外一個狀態變爲非零值。
ide

若是咱們但願當開關關閉( --i: 0)時咱們的值爲零,而當開關打開()時咱們的值爲非零 --i:1,那麼咱們將它與開關值( var(--i))相乘。這樣,若是咱們的非零值應該是 30deg,咱們有一個角度值,咱們有:
  • 當開關
    關閉
    --i: 0)時, calc(var(--i)*30deg)計算到 0*30deg = 0deg
  • 當開關
    打開
    --i: 1)時, calc(var(--i)*30deg)計算到 1*30deg = 30deg

您能夠看到如下所示的概念:
函數

GIF动画。 显示如何将开关值从0更改为1更改两个框的旋转。 当开关关闭时(其值为0),第一个框旋转到30度,当开关打开时,它的第一个框不旋转或旋转到0度(其值为1)。 这意味着我们的旋转值为calc((1  -  var( -  i))* 30deg),其中 -  i是开关值。 当开关关闭时(其值为0),第二个盒子不旋转或旋转到0度,当开关打开时,它旋转到30度(其值为1)。 这意味着我们的旋转值为calc(var( -  i)* 30deg), -  i是开关值。

在零和非零值之間切換(現場演示,因爲 calc()不處理角度值而沒有Edge支持)
對於裝載機的特定狀況下,咱們使用HSL值 border-colorbackground-color。HSL表明色調,飽和度,亮度,而且能夠藉助於雙錐體(其由粘合在一塊兒的基部的兩個錐體組成)在視覺上最佳地表示。


两个锥体的基部在中间粘在一起,一个顶点朝下,一个朝上。 色调是循环的,分布在双锥的中心(垂直)轴周围。 饱和轴从中心轴朝向双锥体表面水平移动 - 它在轴上为0%,在表面上为100%。 亮度轴从黑色顶点垂直移动到白色顶点 - 在黑色顶点处为0%,在白色顶点处为100%。

色調圍繞着雙色調, 至關於 360°在兩種狀況下都給咱們一個紅色

显示红色为0°(相当于360Â°ï¼Œå› ä¸ºè‰²è°ƒæ˜¯å¾ªçŽ¯çš„ï¼‰ï¼Œé»„è‰²ä¸º60°,石灰为120°,青色为180°,蓝色为240°,品红色为300° 。


飽和度從 0%雙錐的垂直軸到 100%雙錐面上。當飽和度 0%(在雙錐的垂直軸上)時,色調再也不重要;對於同一水平面上的全部色調,咱們獲得徹底相同的灰色。「相同水平面」是指具備相同的亮度,其沿垂直軸雙錐增長,從去 0%在黑色雙圓錐體的頂點,以 100%在白色雙圓錐體的頂點。當亮度是 0%或者 100%,色調和飽和度都再也不重要時 - 咱們老是獲得黑色亮度值 0%和白色亮度值 100%。由於咱們只須要黑色和白色咱們的☯符號,色調和飽和度是不相關的,因此咱們清除它們,而後之間進行切換黑色,並白色經過切換之間的亮度 0%100%

.yin-yang {
  &:before, &:after {
    --i: 0;
    /* lightness of border-color when 
     * --i: 0 is (1 - 0)*100% = 1*100% = 100% (white)
     * --i: 1 is (1 - 1)*100% = 0*100% =   0% (black) */
    border: solid $d/6 hsl(0, 0%, calc((1 - var(--i))*100%));

    /* x coordinate of transform-origin when 
     * --i: 0 is 0*100% =   0% (left) 
     * --i: 1 is 1*100% = 100% (right) */
    transform-origin: calc(var(--i)*100%) 50%;

    /* lightness of background-color when 
     * --i: 0 is 0*100% =   0% (black) 
     * --i: 1 is 1*100% = 100% (white) */
    background: hsl(0, 0%, calc(var(--i)*100%));
    /* animation-delay when
     * --i: 0 is 0*-$t = 0s 
     * --i: 1 is 1*-$t = -$t */
    animation: s $t ease-in-out calc(var(--i)*#{-$t}) infinite alternate;
  }

  &:after { --i: 1 }
}
複製代碼


可是,若是咱們想要在開關關閉(--i: 0)時具備非零值而在開關打開時具備另外一個不一樣的非零值(--i: 1)呢?工具

1、在兩個非零值之間切換

假設咱們但願一個元素在開關關閉時具備灰色 background#ccc),在開關打開時 --i: 0
具備橙色 background#f90)( --i: 1)。咱們要作的第一件事是從十六進制切換到更易於管理的格式,如 rgb()hsl()。咱們能夠經過使用諸如Lea Verou的CSS Colors之類的工具或經過DevTools手動完成此操做。若是咱們 background在元素上有一個集合,咱們能夠經過 Shift按住鍵來循環瀏覽格式,同時單擊DevTools中值前面的正方形(或圓圈)。這適用於Chrome和Firefox,但它彷佛不適用於Edge。

GIF动画。 演示如何通过DevToolså¾ªçŽ¯æ ¼å¼åŒ–ï¼ˆåå…­è¿›åˆ¶/ RGB / HSL)。 在Chrome和Firefox中,我们通过按住Shift键并单击<color>值前面的方框或圆圈来完成此操作。

更妙的是,若是咱們使用無禮的話,咱們能夠提取成分 red()/ green()/ blue()hue()/

saturation()/lightness()函數。雖然rgb()多是更爲人所知的格式,但我更傾向於hsl()由於我發現它更直觀,並且經過查看代碼,我更容易瞭解視覺效果。所以,咱們使用如下函數提取

hsl()兩個值的等價物的三個組成部分($c0: #ccc當開關關閉$c1: #f90時和開關打開時):

$c0: #ccc;
$c1: #f90;

$h0: round(hue($c0)/1deg);
$s0: round(saturation($c0));
$l0: round(lightness($c0));

$h1: round(hue($c1)/1deg);
$s1: round(saturation($c1));
$l1: round(lightness($c1))
複製代碼

請注意,咱們已經四捨五入的結果hue(),saturation()並lightness()做爲他們可能返回大量的小數,咱們要保持咱們生成的代碼乾淨。咱們還將hue()函數的結果除以1deg,由於在這種狀況下返回值是度值,而Edge僅支持CSS hsl()函數內的無單位值。一般,在使用Sass時,咱們可使用度數值,而不只僅是hsl()函數內部hue的單位值,由於Sass將其視爲Sass hsl()函數,它被編譯爲hsl()具備無單位色調的CSS 函數。可是在這裏,咱們內部有一個動態CSS變量,所以Sass將此函數視爲CSShsl() 沒有編譯成其餘任何東西的函數,所以,若是hue有一個單元,則不會從生成的CSS中刪除它。

如今咱們有:

  • 若是開關關閉(--i: 0),咱們background是 hsl($h0, $s0, $l0)
  • 若是開關打開(--i: 1),咱們background是 hsl($h1, $s1, $l1)

咱們能夠將咱們的兩個背景寫成:

  • 若是開關關閉( --i: 0), hsl(1*$h0 + 0*$h1, 1*$s0 + 0*$s1, 1*$l0 + 1*$l1)
  • 若是開關打開( --i: 1), hsl(0*$h0 + 1*$h1, 0*$s0 + 1*$s1, 0*$l0 + 1*$l1)

在這裏,咱們記--j的互補值--i(當--i是0,--j是1,當--i是1,--j是0)。

GIF动画。 显示如何将开关值从0更改为1更改框的背景。 当开关关闭(其值为零)和橙色(色调$ h1,饱和度$ s1和亮度$ l1)时,背景为灰色(色调$ h0,饱和度$ s0和亮度$ l0)打开(其值为1)。 这意味着我们有一个色调值calc(var( -  j)*#{$ h0} + var( -  i)*#{$ h1}),一个饱和度值为calc(var( -  j)* #{$ s0} + var( -  i)*#{$ s1})和亮度值calc(var( -  j)*#{$ l0} + var( -  i)*#{$ l1 })),其中--i是switch变量。

上述公式適用於在任意兩個HSL值之間切換。可是,在這種特殊狀況下,咱們能夠簡化它,由於當開關關閉時咱們有一個純灰色(--i: 0)。

 考慮到RGB模型,純灰度值具備相等的紅色,綠色和藍色值。 當考慮HSL模型時,色調是可有可無的(咱們的灰色看起來對於全部色調都是相同的),飽和度老是0%只有亮度很重要,決定了咱們的灰色是多麼亮或暗。 在這種狀況下,咱們能夠始終保持非灰色值的色調(咱們對「on」狀況所具備的色調$h1)。

 因爲任何灰度值(咱們對「關閉」狀況所具備的)的飽和度 $s0始終是0%,將其乘以0或者1老是給出咱們0%。所以,考慮到var(--j)*#{$s0}咱們的公式中的術語老是如此0%,咱們能夠放棄它,咱們的飽和公式減小到「on」狀況$s1和switch變量飽和之間的乘積--i。 這使得輕盈成爲咱們仍然須要應用完整配方的惟一組成部分

--j: calc(1 - var(--i));
background: hsl($h1, 
                calc(var(--i)*#{$s1}), 
                calc(var(--j)*#{$l0} + var(--i)*#{d1l}))
複製代碼

以上內容可在此演示中進行測試。 相似地,假設咱們想要font-size一些文本2rem在咱們的開關關閉(--i: 0)和10vw開關打開(--i: 1)時。應用相同的方法,咱們有:

font-size: calc((1 - var(--i))*2rem + var(--i)*10vw)
複製代碼

GIF动画。 显示如何将开关值从0更改为1更改字体大小。


觸發切換

1、基於元素的切換

這意味着某些元件和其餘元件的開關關閉。例如,這能夠經過奇偶校驗來肯定。假設咱們但願全部偶數元素都旋轉而且具備橙色background而不是初始灰色元素

.box {
  --i: 0;
  --j: calc(1 - var(--i));
  transform: rotate(calc(var(--i)*30deg));
  background: hsl($h1, 
                  calc(var(--i)*#{$s1}), 
                  calc(var(--j)*#{$l0} + var(--i)*#{$l1}));
  
  &:nth-child(2n) { --i: 1 }
}
複製代碼

截图。 连续显示一组正方形,偶数正方形旋转并具有橙色背景而不是初始灰色背景。 这是通过使变换和背景属性都依赖于切换变量来实现的 - å®ƒéšç€å¥‡å¶æ ¡éªŒå˜åŒ–ï¼šå®ƒæœ€åˆä¸º0,但是我们将偶数项更改为1。

由項目奇偶校驗觸發的切換(實時演示,因爲calc()不適用於角度值而在Edge中不能徹底正常工做)

在奇偶校驗的狀況下,咱們爲每隔一個項目(:nth-child(2n))打開開關,但咱們也能夠爲前七個項目(:nth-child(7n)),前兩個項目(:nth-child(-n + 2)),爲除了第一個和最後兩個(:nth-child(n + 3):nth-last-child(n + 3))以外的全部項目打開它。咱們也能夠僅針對標題或僅針對具備特定屬性的元素進行翻轉。

2、狀態切換

這意味着當元素自己(或父元素或其先前的兄弟元素之一)處於一種狀態時關閉開關,而當它是另外一種狀態時關閉。在上一節的交互式示例中,在檢查或取消選中元素以前的複選框時,交換機被翻轉。咱們還可使用白色連接,在聚焦或懸停時能夠放大並變成橙色:

$c: #f90;
$h: round(hue($c)/1deg);
$s: round(saturation($c));
$l: round(lightness($c));

a {
  --i: 0;
  transform: scale(calc(1 + var(--i)*.25));
  color: hsl($h, $s, calc(var(--i)*#{$l} + (1 - var(--i))*100%)); 
  &:focus, &:hover { --i: 1 }
}
複製代碼

由於white任何hsl()具備亮度100%(色調和飽和度都可有可無)的值,咱們能夠經過始終保持:focus/ :hover狀態的色調和飽和度而且僅改變亮度來簡化事物。

GIF动画。 显示白色链接,当悬停或聚焦时会增长并变为橙色。

由狀態變化觸發的切換(現場演示,因爲功能calc()內部不支持的值,Edge中功能不全scale())

3、基於媒體查詢的切換

另外一種可能性是切換由媒體查詢觸發,例如,當方向改變時或從一個視口範圍轉到另外一個視口範圍時。 比方說,咱們有white一個標題font-size的1rem最高320px,但隨後變爲橙色($c)和font-size變5vw,並開始與視縮放width。

h5 {
  --i: 0;
  color: hsl($h, $s, calc(var(--i)*#{$l} + (1 - var(--i))*100%));
  font-size: calc(var(--i)*5vw + (1 - var(--i))*1rem);
  @media (min-width: 320px) { --i: 1 }
}
複製代碼

從頭開始編寫一個更復雜的例子-搜索

咱們在這裏剖析的例子是本文開頭展現的擴展搜索

GIF动画。 æ˜¾ç¤ºå¸¦æœ‰æ”¾å¤§é•œå›¾æ ‡çš„ç»¿è‰²æŒ‰é’®ã€‚ å•å‡»æ­¤æŒ‰é’®å¯ä½¿å…¶å‘å³æ»‘åŠ¨ï¼Œå…¶èƒŒæ™¯å˜ä¸ºçº¢è‰²ï¼Œè€Œæ–‡æœ¬æœç´¢å­—æ®µå‘å·¦æ»‘åŠ¨ï¼Œæ”¾å¤§é•œå˜ä¸ºå…³é—­ï¼ˆäº¤å‰æ ‡è®°ï¼‰å›¾æ ‡ã€‚

ps:從可用性的角度來看,在網站上設置這樣的搜索框可能不是最好的主意,由於人們一般指望搜索框後面的按鈕觸發搜索,而不是關閉搜索欄,但它仍然是一個有趣的編碼練習,這就是爲何我選擇在這裏剖析它。

首先,個人想法是僅使用表單元素來完成它。
所以,HTML結構以下所示:

<input id='search-btn' type='checkbox'/>
<label for='search-btn'>Show search bar</label>
<input id='search-bar' type='text' placeholder='Search...'/>
複製代碼

咱們在這裏作的最初是隱藏文本input,而後在選中複選框以前將其顯示出來 - 讓咱們深刻了解它是如何工做的! 首先,咱們使用基本重置並flex在咱們input和label元素的容器上設置佈局。在咱們的例子中,這個容器是body,但也多是另外一個元素。咱們也絕對定位複選框並將其移出視線(視口外)。

*, :before, :after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font: inherit
}

html { overflow-x: hidden }

body {
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto;
  min-width: 400px;
  min-height: 100vh;
  background: #252525
}

[id='search-btn'] {
  position: absolute;
  left: -100vh
}
複製代碼


咱們把複選框label變成一個大的綠色圓形按鈕,並使用一個大的負數值移動它的文本內容的視線text-indent和overflow: hidden。

$btn-d: 5em; /* 同上 */[for='search-btn'] {
  overflow: hidden;
  width: $btn-d;
  height: $btn-d;
  border-radius: 50%;
  box-shadow: 0 0 1.5em rgba(#000, .4);
  background: #d9eb52;
  text-indent: -100vw;
  cursor: pointer;
}
複製代碼


接下來,咱們經過如下方式修改實際搜索欄:

  • 給它明確的維度
  • 提供 background正常狀態
  • background爲其聚焦狀態定義不一樣的光暈
  • 使用 border-radius至關於其一半的左側角落 height
  • 清理佔位符一點

$btn-d: 5em;
$bar-w: 4*$btn-d;
$bar-h: .65*$btn-d;
$bar-r: .5*$bar-h;
$bar-c: #ffeacc;/* 同上 */

[id='search-bar'] {
  border: none;
  padding: 0 1em;
  width: $bar-w;
  height: $bar-h;
  border-radius: $bar-r 0 0 $bar-r;
  background: #3f324d;
  color: #fff;
  font: 1em century gothic, verdana, arial, sans-serif;
  &::placeholder {
    opacity: .5;
    color: inherit;
    font-size: .875em;
    letter-spacing: 1px;
    text-shadow: 0 0 1px, 0 0 2px
  }	
  &:focus {
    outline: none;
    box-shadow: 0 0 1.5em $bar-c, 0 1.25em 1.5em rgba(#000, .2);
    background: $bar-c;
    color: #000;
  }
}
複製代碼



此時,搜索欄的右邊緣與按鈕的左邊緣重合。可是,咱們想要一些重疊 - 假設重疊使得搜索欄的右邊緣與按鈕的垂直中線重合。鑑於咱們align-items: center在容器上有一個flexbox佈局(body在咱們的例子中),由咱們的兩個項目組成的組件(條和按鈕)保持水平中間對齊,即便咱們設置margin一個或另外一個或在這兩個項目之間。(在最左邊的項目的左側或最右邊的項目的右側是一個不一樣的故事,但咱們如今不會進入那個。)

æ’å›¾æ˜¾ç¤ºæ¡å½¢åŠ ä¸ŠæŒ‰é’®ç»„ä»¶å¤„äºŽåˆå§‹çŠ¶æ€ï¼ˆæ¡å½¢å›¾çš„å³è¾¹ç¼˜ä¸ŽæŒ‰é’®çš„å·¦è¾¹ç¼˜é‡åˆï¼‰ä¸Žé‡å çŠ¶æ€ï¼ˆæ¡å½¢å›¾çš„å³è¾¹ç¼˜ä¸ŽæŒ‰é’®çš„åž‚ç›´ä¸­çº¿é‡åˆï¼‰ã€‚ 在这两种情况下,组件都是中间对齐的。

這是.5*$btn-d半個按鈕直徑的重疊,至關於按鈕的半徑。咱們margin-right在欄上將其設爲負數。咱們還調整padding條形圖的右側,以便咱們補償重疊:

$btn-d: 5em;
$btn-r: .5*$btn-d;

/* 同上 */

[id='search-bar'] {
  /* 同上 */
  margin-right: -$btn-r;
  padding: 0 calc(#{$btn-r} + 1em) 0 1em;
}
複製代碼


除了條形按照DOM順序中的按鈕,因此它放在它的頂部,當咱們真正想要按鈕在頂部。
幸運的是,這有一個簡單的解決方案(至少如今 - 之後還不夠,但讓咱們一次處理一個問題)。

[for='search-btn'] {
  /* 同上 */
  position: relative;
}
複製代碼


在這種狀態下,條形和按鈕組件的總寬度是條形寬度$bar-w加上按鈕的半徑$btn-r(按鈕直徑的一半$btn-d),由於按鈕的一半重疊。在摺疊狀態下,組件的總寬度就是按鈕直徑$btn-d。

æ’å›¾æ˜¾ç¤ºå¤„äºŽå±•å¼€çŠ¶æ€çš„æ¡å½¢åŠ æŒ‰é’®ç»„ä»¶ï¼ˆæ¡å½¢å›¾çš„å³è¾¹ç¼˜ä¸ŽæŒ‰é’®çš„åž‚ç›´ä¸­çº¿é‡åˆï¼‰å¹¶å¤„äºŽæŠ˜å çŠ¶æ€ï¼ˆæ¡å½¢æŠ˜å ï¼Œç»„ä»¶ç¼©å°ä¸ºæŒ‰é’®ï¼‰ã€‚ 在这两种情况下,组件都是中间对齐的。

因爲咱們但願在從展開狀態到摺疊狀態時保持相同的中心軸,咱們須要將按鈕向左移動擴展狀態(.5*($bar-w + $btn-r))減去按鈕半徑($btn-r)的組件寬度的一半。 咱們稱之爲這種轉變$x,咱們在按鈕上使用減號(由於咱們將按鈕向左移動,左邊是x軸的負方向)。因爲咱們但願條形圖摺疊到按鈕中,咱們$x在它上面設置相同的偏移,可是在正方向上(由於咱們將條形圖移動到右邊的 x軸)。 當未選中複選框時,咱們處於摺疊狀態,而當未選中複選框時,咱們處於展開狀態。這意味着transform當沒有選中複選框時,咱們的欄和按鈕會被CSS移動,而且咱們當前將它們置於其中(沒有transform)。 爲了作到這一點,咱們--i在複選框後面的元素上設置了一個變量- 按鈕(使用label複選框建立)和搜索欄。此變量0處於摺疊狀態(當兩個元素都移位且未選中複選框時)並1處於展開狀態(當咱們的條和按鈕位於它們當前佔據的位置時,沒有移位,而且複選框被選中)

$x: .5*($bar-w + $btn-r) - $btn-r;

[id='search-btn'] {
  position: absolute;
  left: -100vw;	
  ~ * {
    --i: 0;
    --j: calc(1 - var(--i)) /* 1 when --i is 0, 0 when --i is 1 */
  }

  &:checked ~ * { --i: 1 }
}

[for='search-btn'] {
  /*同以前 */
  transform: translate(calc(var(--j)*#{-$x}));
}

[id='search-bar'] {
  /*同以前 */
  transform: translate(calc(var(--j)*#{$x}));
}
複製代碼

咱們如今有互動的東西!
單擊該按鈕可切換複選框狀態(由於該按鈕已使用 label複選框建立)。


除了如今按鈕有點難以點擊,由於它再次位於文本輸入下(由於咱們在條上設置了 transform
並創建了堆疊上下文)。修復很是簡單 - 咱們須要 z-index在按鈕上添加一個按鈕,而後將其移動到條形圖上方。

可是咱們還有另外一個更大的問題:咱們能夠看到右側按鈕下方的欄杆。爲了解決這個問題,咱們在欄上設置clip-path了一個inset()值。這指定了一個剪切矩形,藉助於元素頂部,右側,底部和左側邊緣的距離border-box。剪切矩形以外的全部內容都會被剪切掉,只顯示內部的內容

插图显示了inset()函数的四个值代表什么。 第一个是剪切矩形的上边缘相对于边框的上边缘的偏移。 第二个是剪切矩形的右边缘相对于边框的右边缘的偏移。 第三个是剪切矩形的下边缘相对于边框的下边缘的偏移。 第四个是剪切矩形的左边缘相对于边框的左边缘的偏移。 外面的一切

在上圖中,每一個距離都從邊框的邊緣向內移動。在這種狀況下,他們是積極的。但它們也能夠向外移動,在這種狀況下它們是負的,而且剪切矩形的相應邊緣在元素以外border-box。 起初,您可能認爲咱們沒有理由這樣作,但在咱們的特定狀況下,咱們會這樣作! 咱們但願從top(dt),bottom(db)和left(dl)的距離是負的,而且足夠大以包含在該狀態中box-shadow的元素外部延伸的border-box距離,:focus由於咱們不但願它被剪切掉。因此解決方案是建立一個剪切矩形,邊緣在元素以外border-box在這三個方向。 與右邊的距離(dr)是摺疊狀況下的整個條寬$bar-w減去按鈕半徑$btn-r(未選中複選框--i: 0),而且0在展開的狀況下(選中複選框--i: 1)。

$out-d: -3em;

[id='search-bar'] {
  /* 同以前 */
  clip-path: inset($out-d calc(var(--j)*#{$bar-w - $btn-r}) $out-d $out-d);
}
複製代碼

咱們如今有一個搜索欄和按鈕組件,在單擊按鈕時會展開和摺疊。

因爲咱們不但願兩個便之間發生忽然變化,咱們使用transition

[id='search-btn'] {
  /* 同以前 */

  ~ * {
    /* same as before */
    transition: .65s;
  }
}
複製代碼

咱們還但願咱們的按鈕background在摺疊的狀況下爲綠色(未選中複選框--i: 0),在展開的狀況下爲粉紅色(選中複選框--i: 1)。爲此,咱們使用與之前相同的技術:


[for='search-btn'] {
  /* 同以前 */
  $c0: #d9eb52; // green for collapsed state
  $c1: #dd1d6a; // pink for expanded state
  $h0: round(hue($c0)/1deg);
  $s0: round(saturation($c0));
  $l0: round(lightness($c0));
  $h1: round(hue($c1)/1deg);
  $s1: round(saturation($c1));
  $l1: round(lightness($c1));
  background: hsl(calc(var(--j)*#{$h0} + var(--i)*#{$h1}), 
                  calc(var(--j)*#{$s0} + var(--i)*#{$s1}), 
                  calc(var(--j)*#{$l0} + var(--i)*#{$l1}));
}
複製代碼

看看效果


咱們仍然須要作的是建立一個圖標,該圖標在摺疊狀態的放大鏡和展開狀態的「x」之間變形,以指示關閉動做。咱們使用:before和:after僞元素執行此操做。咱們首先肯定放大鏡的直徑以及圖標線寬度所表明的直徑。

$ico-d: .5*$bar-h;
$ico-f: .125;
$ico-w: $ico-f*$ico-d;
複製代碼

咱們絕對將僞元素放在按鈕中間,並考慮其尺寸。而後咱們把它們變成inherit父母的transition。咱們給:beforea background,由於這將是咱們的放大鏡的把手,使它成爲:after圓形border-radius並給它一個插圖box-shadow。

[for='search-btn'] {
  /* 同以前 */	
  &:before, &:after {
    position: absolute;
    top: 50%; left: 50%;
    margin: -.5*$ico-d;
    width: $ico-d;
    height: $ico-d;
    transition: inherit;
    content: ''
  }
	
  &:before {
    margin-top: -.4*$ico-w;
    height: $ico-w;
    background: currentColor
  }

  &:after {
    border-radius: 50%;
    box-shadow: 0 0 0 $ico-w currentColor
  } 
}
複製代碼

咱們如今能夠在按鈕上看到放大鏡組件,爲了使咱們的圖標看起來更像放大鏡,咱們的translate兩個組件都向外放大了放大鏡直徑的四分之一。這意味着所述手柄平移向右,在正方向X經過軸.25*$ico-d與所述主要部分的左側,在負方向X以相同的軸線.25*$ico-d。 咱們還scale手柄(該:before僞元素)水平至其一半width相對於其右邊緣(這意味着一個transform-origin的100%沿X軸)。 咱們只但願在摺疊狀態下發生這種狀況(複選框未選中,--i是0,所以--j是1),所以咱們將轉換量乘以--j並用於--j調整比例因子:

[for='search-btn'] {
  /* 同以前  */

  &:before {
    /* 同以前  */
    height: $ico-w;
    transform: 
      translate(calc(var(--j)*#{.25*$ico-d})) 
      scalex(calc(1 - var(--j)*.5))
  }
  &:after {
    /* same as before */
    transform: translate(calc(var(--j)*#{-.25*$ico-d}))
  } 
}
複製代碼

咱們如今處於摺疊狀態的放大鏡圖標:


因爲咱們但願旋轉兩個圖標組件45deg,咱們在按鈕自己上添加此旋轉:

[for='search-btn'] {
  /* 同以前  */
  transform: translate(calc(var(--j)*#{-$x})) rotate(45deg);
}
複製代碼

這仍然會離開擴展狀態,咱們須要將圓形:after僞元素轉換爲一條線。咱們經過縮放它順着這樣作X軸,將其border-radius從50%到0%。咱們使用的縮放係數是$ico-w咱們想要得到的線寬和$ico-d它在摺疊狀態下造成的圓的直徑之間的比率。咱們稱這個比例$ico-f。 由於咱們只但願作這在擴展狀態下,當複選框被選中,並--i是1,咱們使這兩個比例因子和border-radius依賴於--i和--j:

$ico-d: .5*$bar-h;
$ico-f: .125;
$ico-w: $ico-f*$ico-d;

[for='search-btn'] {
  /* 同以前  */	
  &:after{
    /* 同以前  */
    border-radius: calc(var(--j)*50%);
    transform: 
      translate(calc(var(--j)*#{-.25*$ico-d})) 
      scalex(calc(1 - var(--j)*.5))
  }
}
複製代碼


嗯,差很少,但並不徹底。縮放也縮水了插圖box-shadow沿X軸,因此咱們進行了修復與第二插圖影子,咱們(當複選框被選中,並在擴展狀態下只能獲得--i是1),所以,它的傳播和α取決於--i:

$ico-d: .5*$bar-h;
$ico-f: .125;
$ico-w: $ico-f*$ico-d;

[for='search-btn'] {
  /* 同以前 */
  --hsl: 0, 0%, 0%;
  color: HSL(var(--hsl));	
  &:after{
    /* 同以前 */
    box-shadow: 
      inset 0 0 0 $ico-w currentcolor, 
      /* collapsed: not checked, --i is 0, --j is 1
       * spread radius is 0*.5*$ico-d = 0
       * alpha is 0
       * expanded: checked, --i is 1, --j is 0
       * spread radius is 1*.5*$ico-d = .5*$ico-d
       * alpha is 1 */
      inset 0 0 0 calc(var(--i)*#{.5*$ico-d}) HSLA(var(--hsl), var(--i))
  }
}
複製代碼

這給了咱們最終的結果!【本例完整源碼】


實用案例

如下是一些使用相同技術的演示。咱們不會從頭開始構建這些 - 咱們只會介紹它們背後的基本思想

1、響應橫條

在左侧,是宽屏幕场景的屏幕截图。 在中间,是正常屏幕场景的屏幕截图。 右侧是窄屏幕场景的屏幕截图。

在這種狀況下,咱們的實際元素是前面較小的矩形,然後面的數字正方形和較大的矩形分別使用:before和:after僞元素建立。 數字方塊的背景是單獨的,並使用--slist對於每一個項目不一樣的中止列表變量進行設置。

<p style='--slist: #51a9ad, #438c92'><!-- 1st paragraph text --></p>
<p style='--slist: #ebb134, #c2912a'><!-- 2nd paragraph text --></p>
<p style='--slist: #db4453, #a8343f'><!-- 3rd paragraph text --></p>
<p style='--slist: #7eb138, #6d982d'><!-- 4th paragraph text --></p>
複製代碼

影響橫幅樣式的因素是平價,以及咱們是處於普遍,正常仍是狹窄的狀況。這些給咱們的開關變量

html {
  --narr: 0;
  --comp: calc(1 - var(--narr));
  --wide: 1;	
  @media (max-width: 36em) { --wide: 0 }
  @media (max-width: 20em) { --narr: 1 }
}
p {
  --parity: 0;  
  &:nth-child(2n) { --parity: 1 }
}
複製代碼

數字方塊絕對定位,它們的位置取決於奇偶校驗。若是--parity開關關閉(0),則它們在左側。若是它在(1)上,那麼它們就在右邊。 一個值left: 0%沿着其父元素的左邊緣與數字方塊的左邊緣left: 100%對齊,而一個值沿着父元素的右邊緣對齊其左邊緣。 爲了使數字方塊的右邊緣與其父邊緣的右邊緣對齊,咱們須要從前一個100%值中減去本身的寬度。(請記住,%偏移量的值是相對於父級的尺寸。)

left: calc(var(--parity)*(100% - #{$num-d}))
複製代碼

... $num-d編號方塊的大小在哪裏。 在寬屏幕的狀況下,咱們還向外推送編號1em- 這意味着減去1em咱們迄今爲止對於奇數項目(--parity關閉開關)1em的偏移量,並添加到目前爲止偶數項目的偏移量(--parity開啓時) )。 如今問題是......咱們如何切換標誌?最簡單的方法是使用的權力-1。遺憾的是,咱們在CSS中沒有冪函數(或冪函數運算符),即便它在這種狀況下很是有用:

pow(-1, var(--parity))複製代碼

這意味着咱們必須使它與咱們所擁有的(加法,減法,乘法和除法)一塊兒工做,這致使了一個奇怪的小公式......可是,嘿,它有效!

--sign: calc(1 - 2*var(--parity))
複製代碼

left: calc(var(--parity)*(100% - #{$num-d}) - var(--wide)*var(--sign)*1em)
複製代碼

咱們還width使用這些變量來控制段落,而且max-width咱們但願它具備上限而且僅在窄case(--narr: 1)中水平地徹底覆蓋其父級:

width: calc(var(--comp)*80% + var(--narr)*100%);
max-width: 35em;
複製代碼

這font-size也取決於咱們是否處於狹窄的狀況(--narr: 1)或不是(--narr: 0):

calc(.5rem + var(--comp)*.5rem + var(--narr)*2vw)
複製代碼

......對於:after僞元素(後面較大的矩形)的水平偏移也是如此,由於它們0在窄狀況(--narr: 1)中,而非零偏移,$off-x不然(--narr: 0)

right: calc(var(--comp)*#{$off-x}); 
left: calc(var(--comp)*#{$off-x});
複製代碼


2、懸停並注重效果

GIF动画。 在悬停/焦点上显示红色对角滑动带,覆盖黑色文本下方的白色按钮。 在mouseout / blur上,乐队以另一种方式滑出,而不是他们进入的方式。

這個效果是經過一個連接元素和它的兩個僞元素在對角:hover和:focus狀態上對角滑動建立的。連接的尺寸是固定的,其僞元素也是固定的,設置爲它們的父對角線$btn-d(在寬度和高度造成的直角三角形中斜邊計算)水平和父height垂直。

 該:before定位使得它的左下角恰逢其父,而:after被定位爲使得其右上角與其父一致。因爲二者都應與height其父級相同,所以經過設置top: 0和解決垂直位置bottom: 0。水平放置的處理方式與前一個示例徹底相同,使用--i切換變量來更改兩個僞元素之間的值,並使用--j其互補(calc(1 - var(--i))):

left: calc(var(--j)*(100% - #{$btn-d}))
複製代碼

咱們設定transform-origin的:before到它的左下角(0% 100%)和:after其右上角的(100% 0%),再次,與交換機的幫助--i和補充--j

transform-origin: calc(var(--j)*100%) calc(var(--i)*100%)
複製代碼

咱們將兩個僞元素旋轉到對角線和水平線之間的角度 $btn-a(也是由高度和寬度造成的三角形計算出來的,做爲二者之間比率的反正切)。經過這種旋轉,水平邊緣沿着對角線相交。 而後咱們按照本身的寬度向外移動它們。這意味着咱們將爲二者中的每個使用不一樣的符號,一樣取決於在:before和之間改變值的switch變量,:after就像前面的橫幅示例同樣:

transform: rotate($btn-a) translate(calc((1 - 2*var(--i))*100%))
複製代碼

在:hover和:focus,這個切換必須回去0。這意味着咱們經過互補乘以上面的平移量--q的開關變量--p這是0在正常狀態下和1在:hover或:focus狀態:

transform: rotate($btn-a) translate(calc(var(--q)*(1 - 2*var(--i))*100%))
複製代碼

爲了使僞元素在鼠標移出或失焦時以相反的方式滑出(不回溯它們進入的方式),咱們將switch變量設置--i爲--pfor :before的值,並將值設置--q爲for :after,反轉轉換的符號,確保只轉換transform屬性

3、響應信息圖

在左侧,是宽屏幕场景的屏幕截图。 æˆ‘ä»¬æœ‰ä¸€ä¸ªä¸‰è¡Œï¼Œä¸¤åˆ—ç½‘æ ¼ï¼Œç¬¬ä¸‰è¡ŒæŠ˜å ï¼ˆé«˜åº¦ä¸ºé›¶ï¼‰ã€‚ ç¬¬ä¸€çº§æ ‡é¢˜å æ®å³ä¾§çš„åˆ—ï¼ˆå¯¹äºŽå¥‡æ•°é¡¹ï¼‰æˆ–å·¦ä¾§çš„åˆ—ï¼ˆå¯¹äºŽå¶æ•°é¡¹ï¼‰ã€‚ ç¬¬äºŒçº§æ ‡é¢˜ä½äºŽå¦ä¸€åˆ—å’Œç¬¬ä¸€è¡Œï¼Œè€Œæ®µè½æ–‡æœ¬ä½äºŽç¬¬äºŒè¡Œçš„ç¬¬äºŒçº§æ ‡é¢˜ä¸‹æ–¹ã€‚ 右侧是较窄场景的屏幕截图。 åœ¨è¿™ç§æƒ…å†µä¸‹ï¼Œç¬¬ä¸‰è¡Œçš„é«˜åº¦è¶³ä»¥é€‚åˆæ®µè½æ–‡æœ¬ï¼Œä½†ç¬¬äºŒåˆ—æ˜¯æŠ˜å çš„ã€‚ ç¬¬ä¸€çº§å’Œç¬¬äºŒçº§æ ‡é¢˜åˆ†åˆ«å æ®ç¬¬ä¸€å’Œç¬¬äºŒè¡Œã€‚

在這種狀況下,咱們爲每一個項目(article元素)都有一個三行,兩列網格,第三行在寬屏幕場景中摺疊,第二列在窄屏幕場景中摺疊。在寬屏幕場景中,列的寬度取決於奇偶校驗。在窄屏場景中,第一列跨越元素的整個內容框,第二列具備寬度0。咱們在列之間也存在差距,但僅限於寬屏場景。【本例源碼

$col-1-wide: calc(var(--q)*#{$col-a-wide} + var(--p)*#{$col-b-wide});
$col-2-wide: calc(var(--q)*#{$col-b-wide} + var(--p)*#{$col-a-wide});
$row-1: calc(var(--i)*#{$row-1-wide} + var(--j)*#{$row-1-norm});
$row-2: calc(var(--i)*#{$row-2-wide} + var(--j)*#{$row-2-norm});
$row-3: minmax(0, auto);
$col-1: calc(var(--i)*#{$col-1-wide} + var(--j)*#{$col-1-norm});
$col-2: calc(var(--i)*#{$col-2-wide});

$art-g: calc(var(--i)*#{$art-g-wide});

html {
  --i: var(--wide, 1); 
  --j: calc(1 - var(--i));

  @media (max-width: $art-w-wide + 2rem) { --wide: 0 }
}
article {
  --p: var(--parity, 0);
  --q: calc(1 - var(--p));
  --s: calc(1 - 2*var(--p));
  display: grid;
  grid-template: #{$row-1} #{$row-2} #{$row-3}/ #{$col-1} #{$col-2};
  grid-gap: 0 $art-g;
  grid-auto-flow: column dense;

  &:nth-child(2n) { --parity: 1 }
}
複製代碼

既然咱們已經設置了grid-auto-flow: column dense,咱們能夠在寬屏幕狀況下只設置第一級標題來覆蓋整個列(第二個用於奇數項,第一個用於偶數項),並讓第二個級別標題和段落文本填寫第一個可用單元格。

grid-column: calc(1 + var(--i)*var(--q));
grid-row: 1/ span calc(1 + 2*var(--i));
複製代碼

對於每一個項目,一些其餘屬性取決於咱們是否處於寬屏幕方案中。 在寬屏幕狀況下,垂直margin,垂直和水平padding值,box-shadow偏移和模糊都更大:

$art-mv: calc(var(--i)*#{$art-mv-wide} + var(--j)*#{$art-mv-norm});
$art-pv: calc(var(--i)*#{$art-pv-wide} + var(--j)*#{$art-p-norm});
$art-ph: calc(var(--i)*#{$art-ph-wide} + var(--j)*#{$art-p-norm});
$art-sh: calc(var(--i)*#{$art-sh-wide} + var(--j)*#{$art-sh-norm});

article {
  margin: $art-mv auto;
  padding: $art-pv $art-ph;
  box-shadow: $art-sh $art-sh calc(3*#{$art-sh}) rgba(#000, .5);
}
複製代碼

咱們有一個非零border-width和border-radius寬屏幕的狀況

$art-b: calc(var(--i)*#{$art-b-wide});
$art-r: calc(var(--i)*#{$art-r-wide});
article {
  border: solid $art-b transparent;
  border-radius: $art-r;
}
複製代碼

在寬屏幕場景中,咱們限制項目width,但100%無論怎樣。

$art-w: calc(var(--i)*#{$art-w-wide} + var(--j)*#{$art-w-norm});

article {
  width: $art-w;
}
複製代碼

padding-box漸變的方向也隨奇偶校驗而變化:

background: 
  linear-gradient(calc(var(--s)*90deg), #e6e6e6, #ececec) padding-box, 
  linear-gradient(to right bottom, #fff, #c8c8c8) border-box;
複製代碼

以相似的方式,margin,border-width,padding,width,border-radius,background梯度方向,font-size或line-height對標題和段落文本還取決於咱們是不是在寬屏場景(在第一級標題的狀況下border-radius或background梯度方向,也在奇偶校驗)。

看效果:


最後

本篇文章主要介紹了使用CSS變量來驅動佈局和交互的切換的策略,其中值得注意的是:只適用於數值 - 長度,百分比,角度,持續時間,頻率,無單位數值等,下篇文章繼續分享前端複雜佈局和交互上的探索新的「意淫」技巧。本篇檢索和響應信息圖的 【源碼地址】
相關文章
相關標籤/搜索