現代 CSS 之高階圖片漸隱消失術

2022-12-06 12:00:23

在過往,我們想要實現一個圖片的漸隱消失。最常見的莫過於整體透明度的變化,像是這樣:

<div class="img"></div>

div {
    width: 300px;
    height: 300px;
    background: url(image.jpg);
    transition: .4s;
}
.img:hover {
    opacity: 0;
}

但是,CSS 的功能如此強大的今天。我們可以利用 CSS 實現的漸隱效果已經不再是如此的簡單。

想想看,下面這樣一個效果,是 CSS 能夠實現的麼?

答案是肯定的!本文就將一步一步,從零開始,僅僅使用一個標籤,實現上述的圖片漸隱效果。

這裡,有兩個核心的點:

  1. 如何將一張圖片切割的這麼細,切割成這麼多塊?
  2. 基於上述 (1)的基礎上,又該如何分別控制這些小塊的獨立隱藏和展示呢?

莫慌,讓我們一步一步來解決他們。

強大的 Mask

首先,我們需要用到 Mask。

在 CSS 中,mask 屬性允許使用者通過遮罩或者裁切特定區域的圖片的方式來隱藏一個元素的部分或者全部可見區域。

語法

最基本,使用 mask 的方式是藉助圖片,類似這樣:

{
    /* Image values */
    mask: url(mask.png);                       /* 使用點陣圖來做遮罩 */
    mask: url(masks.svg#star);                 /* 使用 SVG 圖形中的形狀來做遮罩 */
}

當然,使用圖片的方式後文會再講。藉助圖片的方式其實比較繁瑣,因為我們首先還得準備相應的圖片素材,除了圖片,mask 還可以接受一個類似 background 的引數,也就是漸變。

類似如下使用方法:

{
    mask: linear-gradient(#000, transparent)                      /* 使用漸變來做遮罩 */
}

那該具體怎麼使用呢?一個非常簡單的例子,上述我們創造了一個從黑色到透明漸變色,我們將它運用到實際中,程式碼類似這樣:

下面這樣一張圖片,疊加上一個從透明到黑色的漸變,

{
    background: url(image.png) ;
    mask: linear-gradient(90deg, transparent, #fff);
}

應用了 mask 之後,就會變成這樣:

這個 DEMO,可以先簡單瞭解到 mask 的基本用法。

這裡得到了使用 mask 最重要結論:圖片與 mask 生成的漸變的 transparent 的重疊部分,將會變得透明。

值得注意的是,上面的漸變使用的是 linear-gradient(90deg, transparent, #fff),這裡的 #fff 純色部分其實換成任意顏色都可以,不影響效果。

CodePen Demo -- 使用 MASK 的基本使用

使用 mask 實現 hover 隱藏圖片

瞭解了 mask 的簡單用法後,我們來看這樣一個非常簡單的例子,我們改造下上述的第一個 DEMO。

<div class="img"></div>
div {
    width: 300px;
    height: 300px;
    background: url(image.jpg);
}
.img:hover {
    mask: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0));
}

是的,利用 Mask,我們同樣也可以得到近似的消失效果:

如果對於 Mask 你還不瞭解,你需要首先看看這篇:奇妙的 CSS MASK

當然,對於現在這個效果,有個很大的缺陷,那就是缺少了動畫。圖片是瞬間消失的。所以,我們還需要給上述的藉助 mask 實現的圖片消失效果新增上動畫。

而這,就需要用上 CSS @property 了。

強大的 CSS @property

CSS @property,大家應該不那麼陌生了。

@property CSS at-rule 是 CSS Houdini API 的一部分, 它允許開發者顯式地定義他們的 CSS 自定義屬性,允許進行屬性型別檢查、設定預設值以及定義該自定義屬性是否可以被繼承。

如果你對 CSS @property 還有所疑惑,建議你先快速讀一讀這篇文章 -- CSS @property,讓不可能變可能

回到我們的正題,如果我們想給上述使用 Mask 的程式碼,新增上動畫,我們期望程式碼大概是這樣:

div {
    width: 300px;
    height: 300px;
    background: url(image.jpg);
    mask: linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 1));
}
.img:hover {
    mask: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0));
}

這裡,mask 的是從 mask: linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 1))mask: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0)) 變化的。

但是實際上,這樣並不會產生任何的動畫效果。

原因在於,我們 Mask 屬性本身是不支援過渡動畫的!

但是,利用上 CSS @property,整個效果就不一樣了。藉助,CSS @property,我們改造一下程式碼:

@property --m-0 {
   syntax: "<number>";
   initial-value: 1;
   inherits: false;
}
div {
    width: 300px;
    height: 300px;
    background: url(image.jpg);
    mask: linear-gradient(90deg, rgba(0, 0, 0, var(--m-0)), rgba(0, 0, 0, var(--m-0)));
    transition: --m-0 0.5s;
}
div:hover {
    --m-0: 0;
}

我們利用 CSS @property 定義了一個名為 --m-0 的變數,然後,我們將整個動畫過渡效果賦予了這個變數,而不是整個 mask。

利用這個小技巧,我們就可以成功的實現基於 mask 屬性的動畫效果:

藉助多重 mask 分割圖片

到了這一步,後面的步驟其實就很明朗了。

由於 mask 擁有和 background 一樣的特性。因此,mask 是可以有多重 mask 的。也就是說,我們可以設定多個不同的 mask 效果給同一個元素。

什麼意思呢?上面的效果只有一重 mask,我們稍微新增一些 mask 程式碼,讓它變成 2 重 mask:

@property --m-0 {
   syntax: "<number>";
   initial-value: 1;
   inherits: false;
}
@property --m-1 {
   syntax: "<number>";
   initial-value: 1;
   inherits: false;
}
div {
    mask: 
        linear-gradient(90deg, rgba(0, 0, 0, var(--m-0)), rgba(0, 0, 0, var(--m-0))),
        linear-gradient(90deg, rgba(0, 0, 0, var(--m-1)), rgba(0, 0, 0, var(--m-1)));
    mask-size: 50% 100%;
    mask-position: left, right;
    mask-repeat: no-repeat;
    transition: 
        --m-0 0.3s,
        --m-1 0.25s 0.15s;
}
div:hover {
    --m-0: 0;
    --m-1: 0;
}

這樣,我們的步驟大概是:

  1. 首先將 mask 一分為二,左右兩邊各一個
  2. 然後,設定了兩個基於 CSS @property 的變數,--m-0--m-0
  3. 然後,給它們設定了不同的過渡時間和過渡延遲時間
  4. 在 hover 的一瞬間,再將這兩個變數的值,都置為 0,也就是實現 linear-gradient(90deg, rgba(0, 0, 0, 1), rgba(0, 0, 0, 1))linear-gradient(90deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0)) 的變化,用於隱藏對應 mask 塊
  5. 由於設定了不同的過渡時間和延遲時間,整體上看上去,整個動畫就分成了兩部分

看看效果:

繼續切割為 4 重 mask

好,既然 2 重 mask 效果沒問題,那麼我們可以再進一步,將整個效果切割為 4 個 mask。程式碼還是如法炮製,這裡我再貼上核心程式碼:

@property --m-0 {
   syntax: "<number>";
   initial-value: 1;
   inherits: false;
}
@property --m-1 {
   syntax: "<number>";
   initial-value: 1;
   inherits: false;
}
@property --m-2 {
   syntax: "<number>";
   initial-value: 1;
   inherits: false;
}
@property --m-3 {
   syntax: "<number>";
   initial-value: 1;
   inherits: false;
}
div {
    mask: 
        linear-gradient(90deg, rgba(0, 0, 0, var(--m-0)), rgba(0, 0, 0, var(--m-0))),
        linear-gradient(90deg, rgba(0, 0, 0, var(--m-1)), rgba(0, 0, 0, var(--m-1))),
        linear-gradient(90deg, rgba(0, 0, 0, var(--m-2)), rgba(0, 0, 0, var(--m-2))),
        linear-gradient(90deg, rgba(0, 0, 0, var(--m-3)), rgba(0, 0, 0, var(--m-3)));
    mask-size: 50% 50%;
    mask-repeat: no-repeat;
    mask-position: left top, right top, left bottom, bottom right;
    transition: 
        --m-0 0.3s,
        --m-1 0.15s 0.1s,
        --m-2 0.25s 0.21s,
        --m-3 0.19s 0.15s;
}
div:hover {
    --m-0: 0;
    --m-1: 0;
    --m-2: 0;
    --m-3: 0;
}

這樣,我們就可以得到 4 塊分割圖片的 mask 消失效果:

好,再依次類推,我們就可以得到分割為 9 塊的,分割為 16 塊的。由於程式碼太多,就簡單看看效果:

CodePen Demo -- 基於 @property 和 mask 的圖片漸隱消失術

基於 SCSS 簡化程式碼

那麼,如果我們要分割為 100 塊呢?或者 400 塊呢?還要手寫這些程式碼嗎?

當然不需要,由於上面的程式碼的規律非常的明顯,我們可以藉助前處理器很好的封裝整個效果。從而快速的實現切割成任意規則塊數的效果。

完整的程式碼如下:

$count: 400;
$sqrt: 20;
$per: 100% / $sqrt;
$width: 300px;
$perWid: 15;

@for $i from 1 to ($count + 1) {
    @property --m-#{$i} {
       syntax: "<number>";
       initial-value: 1;
       inherits: false;
    }
}
@function bgSet($n) {
    $bg : radial-gradient(rgba(0, 0, 0, var(--m-1)), rgba(0, 0, 0, var(--m-1)));
    
    @for $i from 2 through $n {         
        $bg: $bg, radial-gradient(rgba(0, 0, 0, var(--m-#{$i})), rgba(0, 0, 0, var(--m-#{$i})));
    }
    
    @return $bg;
}
@function positionSet($n) {
    $bgPosition: ();

    @for $i from 0 through ($n) {   
        @for $j from 0 through ($n - 1) {  
            $bgPosition: $bgPosition, #{$i * $perWid}px #{$j * $perWid}px;
        }
    }
    
    @return $bgPosition;
}
@function transitionSet($n) {
    $transition: --m-1 0.1s 0.1s;

    @for $i from 1 through $n {   
        $transition: $transition, --m-#{$i} #{random(500)}ms #{random(500)}ms;
    }
    
    @return $transition;
}
div {
    width: $width;
    height: $width;
    background: url(image.jpg);
    mask: bgSet($count);
    mask-size: $per $per;
    mask-repeat: no-repeat;
    mask-position: positionSet($sqrt); 
    transition: transitionSet($count);
}
div:hover {
    @for $i from 1 through $count {         
        --m-#{$i}: 0;
    }
}

這裡,簡單解釋一下,以生成 400 塊小塊為例子:

  1. 最上面的 SCSS 變數定義中,
    • $count 是我們最終生成的塊數
    • $sqrt 是每行以及每列會擁有的塊數
    • $per 是每一塊佔整體圖片元素的百分比值
    • $width 是整個圖片的寬高值
    • $perWid 是每一塊的寬高值
  2. 利用了最上面的一段迴圈函數,批次的生成 CSS @property 變數,從 --m-0--m-400
  3. @function bgSet($n) {} 是生成 400 塊 mask 片段
  4. @function positionSet($n) 是生成 400 塊 mask 的 mask-position,也就是生成 400 段不同定位,讓 400 塊 mask 剛好覆蓋整個圖片
  5. @function transitionSet($n) {} 是隨機設定每個塊的動畫時間和延遲時間
  6. 程式碼最下面,還有一段迴圈函數,生成 400 個 CSS @property 變數的 hover 值,當 hover 的時候,全部變成 0

這樣,我們就實現了 400 分塊的漸隱效果。效果如下:

CodePen Demo -- 基於 @property 和 mask 的圖片漸隱消失術

調整過渡變數,控制方向

當然,上面我們的對每一個小塊的 transition 的過渡時間和過渡延遲時間的設定,都是隨機的:

@function transitionSet($n) {
    $transition: --m-1 0.1s 0.1s;

    @for $i from 1 through $n {   
        $transition: $transition, --m-#{$i} #{random(500)}ms #{random(500)}ms;
    }
    
    @return $transition;
}

我們完全可以通過一定的控制,讓過渡效果不那麼隨機,譬如有一定的方向感。

下面,我們通過讓動畫的延遲時間與 $i,也就是 mask 小塊的 index 掛鉤:

@function transitionSet($n) {
    $transition: --m-1 0.1s 0.1s;

    @for $i from 1 through $n {   
        $transition: $transition, --m-#{$i} #{100 + random(500)}ms #{($i / 50) * random(100)}ms;
    }
    
    @return $transition;
}

那麼,整個動畫的方向就是從左往右逐漸消失:

CodePen Demo -- 基於 @property 和 mask 的圖片漸隱消失術 2

當然,有意思的是,這個效果,不僅僅能夠運用在圖片上,它其實可以作用在任何元素之上!

譬如,我們有的只是一段純文字,同樣適用這個效果:

CodePen Demo -- 基於 @property 和 mask 的文字漸隱消失術

總結

到這裡,簡單總結一下。本文,我們核心利用了 CSS @propery 和 mask,實現了一些原本看上去需要非常多 div 才能實現或者是需要藉助 Canvas 才能實現的效果。同時,我們藉助了 SCSS 前處理器,在尋找到規律後,極大的簡化了 CSS 程式碼的書寫量。

到今天,強大的 CSS 已經允許我們去做越來越多更有意思的動效,CSS @propery 和 mask 這兩個屬性在現代 CSS 發揮了非常重要的作用,非常建議大家認真掌握以下這兩個屬性。

最後

好了,本文到此結束,希望本文對你有所幫助