1. 程式人生 > >CSS變數在前端複雜佈局和互動上的探索

CSS變數在前端複雜佈局和互動上的探索

《DRY Switching with CSS Variables: The Difference of One Declaration》BY ANA TUDOR ONDECEMBER 5, 2018(注:原文)

寫在前面:

本文將和各位老鐵一起探索如何使用CSS變數來降低複雜佈局和互動的程式碼編寫難度,並使其更易於維護。這裡即將分享兩篇該系列的文章,本篇是分享css變數的探索應用的各種用例。

進入文章前,我們先看下圖,一張網站常用的響應式資訊圖表佈局圖,當然實現的方式很多,如果我告訴您一個CSS宣告會在以下影象中對寬屏情況(左測)和第二個(右測)之間產生差異,用一個CSS宣告實現在寬螢幕的情況下對奇數項和偶數項進行區分,從而來實現下面效果,你會不會覺得有點驚喜?

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編寫)


為什麼CSS變數很有用

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

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偽元素),從而動態修改所有這些屬性的計算值。

如果沒有CSS變數,我們將不得不再次在:after偽元素上設定所有這些屬性(border-color、transform-origin、background、anima -delay),並可能出現一些錯誤,甚至忘記設定其中一些屬性。

一般情況下切換的工作原理

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

在陰陽載入器的特定情況下,我們在兩半(偽元素)之間改變的所有屬性對於開關的一個狀態變為零值而對於另一個狀態變為非零值。

如果我們希望當開關關閉( --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)呢?

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

假設我們希望一個元素在開關關閉時具有灰色 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更改字体大小。


觸發切換

一、基於元素的切換

這意味著某些元件和其他元件的開關關閉。例如,這可以通過奇偶校驗來確定。假設我們希望所有偶數元素都旋轉並且具有橙色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))之外的所有專案開啟它。我們也可以僅針對標題或僅針對具有特定屬性的元素進行翻轉。

二、狀態切換

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

$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())

三、基於媒體查詢的切換

另一種可能性是切換由媒體查詢觸發,例如,當方向改變時或從一個視口範圍轉到另一個視口範圍時。 比方說,我們有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))
  }
}
複製程式碼

這給了我們最終的結果!【本例完整原始碼】


實用案例

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

一、響應橫條

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

在這種情況下,我們的實際元素是前面較小的矩形,而後面的數字正方形和較大的矩形分別使用: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});
複製程式碼


二、懸停並注重效果

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屬性

三、響應資訊圖

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

在這種情況下,我們為每個專案(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)*
            
           

相關推薦

CSS變數前端複雜佈局互動探索

《DRY Switching with CSS Variables: The Difference of One Declaration》BY ANA TUDOR ONDECEMBER 5, 2018(注:原文) 寫在前面: 本文將和各位老鐵一起探索如何使用CSS變數來降低複雜佈局和互動的程式碼編寫難度,並使

關於前端html圖片檔案後臺接收方法

前些時間專案和h5互動時涉及到了檔案上傳的一個功能,但是h5在寫上傳的時候總是上傳不上,所以花時間專門看了下web端的檔案上傳記錄下。第一種是前端寫的,拿來修改了下,這種方法在上傳的時候需要用到jquery.js和ajaxfileupload.js://獲取圖片本地urlfu

CSS雙飛翼佈局聖盃佈局margin-left:-100%;等內容理解

網上搜索雙飛翼佈局和聖盃佈局都會有許多不錯的文章,在這裡給大家推薦一篇部落格 看完之後,我來補充一點細節的東西!!! 如何理解margin-left:-100%及margin-left:300px; 看完這個圖解,上面的問題就應該沒問題了!!具體的雙飛翼佈局方法請參

Css之聖盃佈局雙飛翼佈局

聖盃佈局和雙飛翼佈局是我再一次京東面試中聽到的詞彙,簡單來說,這種佈局其實都是我們CSS+div佈局的入門級,但是大家肯恩都不知道這個名詞,高大上的名詞一般都會顯示你的知識面及專業度 閒話不多說,原理講起來: 事實上,聖盃佈局其實和雙飛翼佈局是一回事。它們實現的都是三欄佈局

CSS 商品的列表佈局網格佈局的切換

最近做的一個專案是需要商品不同的形式排列,一開始使用的是a標籤直接跳轉到另一個頁面,後來發現可以用在同一個頁面切換class實現,下面看下我是如何實現的因為兩種頁面展示的資料都是一樣的,看下效果圖先寫好兩個不同的css佈局:我這裡使用的是scss,只有最外層的樣式名稱不一樣,

前端面試htmlcss

結構 意義 phone sea height 超鏈接 並且 另一個 一個數 寫出幾種IE6 BUG的解決方法 1.雙邊距BUG float引起的 使用display2.3像素問題 使用float引起的 使用dislpay:inline -3px3.超鏈接hover 點擊後失

監聽沒綁+佈局平板不匹配

一.在Android Studio上編計算器時,textview監聽沒綁上,比如定義變數et_input在Java檔案中,應該把 private TextView et_input;    //定義了textview控制元件對應的變數 et_input = (TextView) findViewById(

理解 CSS 佈局塊級格式化上下文

CSS佈局中有一些概念是你一旦理解它那麼就會極大的提升你的CSS技能的。這篇文章是關於塊級格式化上下文的BFC。也許你從未聽說過這個術語,但是如果你曾經用CSS做過佈局,那麼你也許知道它是什麼。理解什麼是BFC,它為什麼會起作用以及如何建立一個有用的BFC可以幫助你理解CSS佈局是怎樣工作的

編寫HTMLCSS前端開發中不一定熟悉JavaScript

作為前端開發人員,HTML、css、JavaScript是必備的知識技能,但是現實工作工作中並非所有的前端都知道JavaScript,根據外國一個網站的匿名調查發現,有17%的開發人員不知道JavaScript,只有51% 的開發人員熟悉JavaScript。其實前端開發最主要的動態開發就是Java

web前端學習(二)html學習筆記部分(10)-- HTML5構建應用佈局頁面

1.2.25  HTML5構建應用佈局和頁面 1.2.25.1  HTML5在移動開發中的準則   1.儘量使用單頁面開發   2.慎重選擇前端UI框架   3.動畫、特效使用準則(60fps)     瀏覽器消耗最小的css屬性       位置  -&n

理解 CSS 佈局塊級格式上下文

本文的目的是介紹一些概念來幫你增強 CSS 碼力。如標題所示這篇文章主要是講塊級格式上下文BFCBlock Formatting Context。你可能沒聽過這個術語但只要你曾經使用 過CSS 佈局你就能明白它。理解 BFC 是什麼、它如何工作、如何建立一個 BFC 是非常有用的這些能幫你更好的理解

web前端課程技術分享之關於rem佈局vw佈局的理解

 通過rem來實現響應式佈局:   首先來看,什麼是rem單位。rem是一個靈活的、可擴充套件的單位,由瀏覽器轉化畫素並顯示。與em單位不同,rem單位無論巢狀層級如何,都只相對於瀏覽器的根元素(HTML元素)的font-size。預設情況下,h

CSS 兩欄佈局三欄佈局

兩欄佈局 基礎部分的程式碼: <div class="container"> <div class="left">left</div> <div class="right">right</div>

頁面佈局css盒模型

1.頁面佈局練習 高度已知,寫出三欄佈局,其中左欄、右欄寬度各為300px 中間自適應 浮動 缺點:需要處理清除浮動 絕對定位 缺點:佈局脫離文件流 flex 缺點:相容性 表格佈局 缺點:一欄超出高度,其他跟著超出 網格佈局:相容性 高度未知的情況

前端每日實戰5.用 CSS 創作一個立體滑動 toggle 互動控制元件

原文地址:https://segmentfault.com/a/1190000014638655   HTML程式碼: <html> <head> <link rel="stylesheet" href="index.css"&g

複雜度分析():如何分析、統計演算法的執行效率資源消耗

一、什麼是複雜度分析? 1.資料結構和演算法本身解決的是“快”和“省”的問題,即如何讓程式碼執行得更快,如何讓程式碼更省儲存空間。 2.因此從執行時間和佔用空間兩個維度來評估資料結構和演算法的效能 3.分別用時間複雜度和空間複雜度兩個概念來描述效能問題,二者統稱為複雜度

使用vlayout輕鬆構建複雜佈局頁面&拉載入

設計 拿到了新專案的設計圖,如圖所示: 樂烹APP-首頁.png 構建這個頁面,如果沒有滑動到底部載入更多功能,可以考慮用scollerview中一點點畫布局。如果需要載入 更多的話,就必須要考慮列表項的回收與複用的問題,scollerview顯然不能滿足,這時候我們就需要recyclervi

使用vlayout輕鬆構建複雜佈局頁面&拉載入2

設計 拿到了新專案的設計圖,如圖所示: 樂烹APP-首頁.png 構建這個頁面,如果沒有滑動到底部載入更多功能,可以考慮用scollerview中一點點畫布局。如果需要載入 更多的話,就必須要考慮列表項的回收與複用的問題,scollerview顯然不能滿足,這時

複雜度分析():如何分析、統計演算法的執行效率資源消耗?

Tip:各平臺的 markdown 解析標準不同,會有些數學符號無法識別,比如^n^: 表示n次方,~y~: 表示y 的底數。 什麼是複雜度分析? 演算法複雜度是指演算法在編寫成可執行程式後,執行時所需要的資源,資源包括時間資源和記憶體資源。 為什麼需要複雜度分

前端頁面檔案拖拽傳模組html/css/js程式碼示例

最近給衛生局做一個表格上傳/視覺化系統,算是小有成果。今天把專案中的檔案拖拽上傳模組分離出來,做了一個獨立的小demo,並把相關程式碼打包上傳到了我的github中,為了其他學習者和開發者提供拙見。 由於程式碼中我的註釋很詳盡,所以具體邏輯實現及不介紹