CSS mask 實現滑鼠跟隨鏤空效果

XboxYan發表於2021-11-22

偶然在某思看到這樣一個問題,如何使一個div的部分割槽域變透明而其他部分模糊掉?,最後實現效果是這樣的

237330258-6181fcdb471cf

進一步,還能實現任意形狀的鏤空效果

Kapture 2021-11-20 at 13.44.26

滑鼠經過的地方清晰可見,其他地方則是模糊的。

可能一開始無從下手,不要急,可以先從簡單的、類似的效果開始,一步一步嘗試,一起看看吧。

一、普通半透明的效果

比如平時開發中碰到更多的可能是一個半透明的效果,有點類似於探照燈(滑鼠外面的地方是半透明遮罩,看起來會暗一點)。如下:

image-20211117200548416

那先從這種效果開始吧,假設有這樣一個佈局:

<div class="wrap" id="img">
    <img class="prew" src="https://tva1.sinaimg.cn/large/008i3skNgy1gubr2sbyqdj60xa0m6tey02.jpg">
</div>

那麼如何繪製一個鏤空的圓呢?先介紹一種方法

其實很簡單,只需要一個足夠大的投影就可以了,原理如下

image-20211117195737723

這裡可以用偽元素::before來繪製,結構更加精簡。用程式碼實現就是

.wrap::before{
  content:'';
  position: absolute;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  left: 50%;
  top: 50%;
  transform: translate(-50%,-50%); /*預設居中*/
  box-shadow: 0 0 0 999vw rgba(0, 0, 0, .5); /*足夠大的投影*/
}

可以得到這樣的效果

image-20211117200548416

二、藉助 CSS 變數傳遞滑鼠位置

按照以往的經驗,可能會在 js 中直接修改元素的 style 屬性,類似這樣

img.addEventListener('mousemove', (ev) => {
    img.style.left = '...';
    img.style.top = '...';
})

但是這樣互動與業務邏輯混雜在一起,不利於後期維護。其實,我們只需要滑鼠的座標,在 CSS 中也能完全實現跟隨的效果。

這裡藉助 CSS 變數,那一切就好辦了!假設滑鼠的座標是 [--x,--y](範圍是[0, 1]),那麼遮罩的座標就可以使用 calc計算了

.wrap::before{
  left: calc(var(--x) * 100%);
  top: calc(var(--y) * 100%);
}

然後滑鼠座標的獲取可以使用 JS 來計算,也比較容易,如下

img.addEventListener('mousemove', (ev) => {
    img.style.setProperty('--x', ev.offsetX / ev.target.offsetWidth);
    img.style.setProperty('--y', ev.offsetY / ev.target.offsetHeight);
})

這樣,半透明效果的鏤空效果就完成了

Kapture 2021-11-17 at 20.26.27

完整程式碼可以訪問: backdrop-shadow (codepen.io)

三、漸變也能實現半透明的效果

除了上述陰影擴充套件的方式,CSS 徑向漸變也能實現這樣的效果

繪製一個從透明到半透明的漸變,如下

.wrap::before{
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    background: radial-gradient( circle at center, transparent 50px, rgba(0,0,0,.5) 51px);
}

可以得到這樣的效果

image-20211117200548416

然後,把滑鼠座標對映上去就可以了。從這裡就可以看出 CSS 變數的好處,無需修改 JS,只需要在CSS中修改漸變中心點的位置就可以實現了

.wrap::before{
  background: radial-gradient( circle at calc(var(--x) * 100% )  calc(var(--y) * 100% ), transparent 50px, rgba(0,0,0,.5) 51px);
}

Kapture 2021-11-18 at 19.51.30

四、背景模糊的效果嘗試

CSS 中有一個專門針對背景(元素後面區域)的屬性:backdrop-filter。使用方式和 filter完全一致!

backdrop-filter: blur(10px);

下面是 MDN 中的一個示意效果

image-20211119191341911

backdrop-filter是讓當前元素所在區域後面的內容模糊,要想看到效果,需要元素本身半透明或者完全透明;而filter是讓當前元素自身模糊。有興趣的可以檢視這篇文章: CSS backdrop-filter簡介與蘋果iOS毛玻璃效果 « 張鑫旭-鑫空間-鑫生活 (zhangxinxu.com)

需要注意的是,這種模糊與背景的半透明度沒有任何關係,哪怕元素本身是透明的,仍然會有效果。例如下面是去除背景後的效果 ,整塊都是模糊的

image-20211119193956128

如果直接運用到上面的例子會怎麼樣呢?

1. 陰影實現

在上面第一個例子中新增 backdrop-filter

.wrap::before{
  content:'';
  position: absolute;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  left: 50%;
  top: 50%;
  transform: translate(-50%,-50%); /*預設居中*/
  box-shadow: 0 0 0 999vw rgba(0, 0, 0, .5); /*足夠大的投影*/
  backdrop-filter: blur(5px)
}

得到效果如下

Kapture 2021-11-19 at 19.20.57

可以看到圓形區域是模糊的,正好和希望的效果相反。其實也好理解,只有圓形區域才是真實的結構,外面都是陰影,所以最後作用的範圍也只有圓形部分

2. 漸變實現

現在在第二個例子中新增 backdrop-filter

.wrap::before{
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    background: radial-gradient( circle at calc(var(--x) * 100% )  calc(var(--y) * 100% ), transparent 50px, rgba(0,0,0,.5) 51px);
      backdrop-filter: blur(5px)
}

效果如下

Kapture 2021-11-19 at 19.31.22

已經全部都模糊了,只是圓形區域外暗一些。由於::before的尺寸佔據整個容器,所以整個背後都變模糊了,圓形外部比較暗是因為半透明漸變的影響。

總之還是不能滿足我們的需求,需要尋求新的解決方式。

五、CSS MASK 實現鏤空

與其說是讓圓形區域不模糊,還不如說是把那塊區域給鏤空了。就好比之前是一整塊磨砂玻璃,然後通過 CSS MASK 打了一個圓孔,這樣透過圓孔看到後面肯定是清晰的。

可以對第二個例子稍作修改,通過徑向漸變繪製一個透明圓,剩餘部分都是純色的遮罩層,示意如下

image-20211120113029155

用程式碼實現就是

.wrap::before{
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    -webkit-mask: radial-gradient( circle at calc(var(--x, .5) * 100% )  calc(var(--y, .5) * 100% ), transparent 50px, #000 51px);
      background: rgba(0,0,0,.3);
      backdrop-filter: blur(5px)
}

這樣就實現了文章開頭的效果

237330258-6181fcdb471cf

完整程式碼可以檢視:backdrop-mask (codepen.io)

六、CSS MASK COMPOSITE 實現更豐富的鏤空效果

除了使用徑向漸變繪製遮罩層以外,還可以通過 CSS MASK COMPOSITE(遮罩合成)的方式來實現。標準關鍵值如下(firefox支援):

/* Keyword values */
mask-composite: add; /* 疊加(預設) */
mask-composite: subtract; /* 減去,排除掉上層的區域 */
mask-composite: intersect; /* 相交,只顯示重合的地方 */
mask-composite: exclude; /* 排除,只顯示不重合的地方 */

遮罩合成是什麼意思呢?可以類比 photoshop 中的形狀合成,幾乎是一一對應的

image-20211120123004278

-webkit-mask-composite 與標準下的值有所不同,屬性值非常多,如下(chorme 、safari 支援)

-webkit-mask-composite: clear; /*清除,不顯示任何遮罩*/
-webkit-mask-composite: copy; /*只顯示上方遮罩,不顯示下方遮罩*/
-webkit-mask-composite: source-over; 
-webkit-mask-composite: source-in; /*只顯示重合的地方*/
-webkit-mask-composite: source-out; /*只顯示上方遮罩,重合的地方不顯示*/
-webkit-mask-composite: source-atop;
-webkit-mask-composite: destination-over;
-webkit-mask-composite: destination-in; /*只顯示重合的地方*/
-webkit-mask-composite: destination-out;/*只顯示下方遮罩,重合的地方不顯示*/
-webkit-mask-composite: destination-atop;
-webkit-mask-composite: xor; /*只顯示不重合的地方*/

是不是一臉懵?這裡做了一個對應的效果圖,如果不太熟練,使用的時候知道有這樣一個功能,然後對著找就行了

image-20211120130421281

回到這裡,可以繪製一整塊背景和一個圓形背景,然後通過遮罩合成排除(mask-composite: exclude)打一個孔就行了,實現如下

.wrap::before{
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    -webkit-mask: url("data:image/svg+xml,%3Csvg width='50' height='50' viewBox='0 0 50 50' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='25' cy='25' r='25' fill='%23C4C4C4'/%3E%3C/svg%3E"), linear-gradient(red, red);
      -webkit-mask-size: 50px, 100%;
      -webkit-mask-repeat: no-repeat;
      -webkit-mask-position: calc(var(--x, .5) * 100% + var(--x, .5) * 100px - 50px )  calc(var(--y, .5) * 100% + var(--y, .5) * 100px - 50px ), 0;
      -webkit-mask-composite: xor;   /*只顯示不重合的地方, chorem 、safari 支援*/
      mask-composite: exclude; /* 排除,只顯示不重合的地方, firefox 支援 */
      background: rgba(0,0,0,.3);
      backdrop-filter: blur(5px)
}

需要注意-webkit-mask-position中的計算,這樣也能很好的實現這個效果

237330258-6181fcdb471cf

完整程式碼可以檢視:backdrop-mask-composite (codepen.io)

你可能已經發現,上述例子中的圓是通過 svg 繪製的,還用到了遮罩合成,看著好像更加繁瑣了。其實呢,這是一種更加萬能的解決方式,可以帶來無限的可能性。比如我需要一個星星⭐️的鏤空效果,很簡單,先通過一個繪製軟體畫一個

image-20211120131056453

然後把這段 svg 程式碼轉義一下,這裡推薦使用張鑫旭老師的SVG線上壓縮合並工具

image-20211120131335734

替換到剛才的例子中就可以了

.wrap::before{
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    -webkit-mask: url("data:image/svg+xml,%3Csvg width='96' height='91' viewBox='0 0 96 91' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M48 0l11.226 34.55h36.327l-29.39 21.352L77.39 90.45 48 69.098 18.61 90.451 29.837 55.9.447 34.55h36.327L48 0z' fill='%23C4C4C4'/%3E%3C/svg%3E"), linear-gradient(red, red);
      -webkit-mask-size: 50px, 100%;
      -webkit-mask-repeat: no-repeat;
      -webkit-mask-position: calc(var(--x, .5) * 100% + var(--x, .5) * 100px - 50px )  calc(var(--y, .5) * 100% + var(--y, .5) * 100px - 50px ), 0;
      -webkit-mask-composite: xor;   /*只顯示不重合的地方, chorem 、safari 支援*/
      mask-composite: exclude; /* 排除,只顯示不重合的地方, firefox 支援 */
      background: rgba(0,0,0,.3);
      backdrop-filter: blur(5px)
}

星星鏤空實現效果如下

Kapture 2021-11-20 at 13.35.28

完整程式碼可以檢視:backdrop-star (codepen.io)

再比如一個心形❤,實現效果如下

Kapture 2021-11-20 at 13.44.26

完整程式碼可以檢視:backdrop-heart (codepen.io)

只有想不到,沒有做不到

七、總結和說明

以上實現了一個滑鼠跟隨鏤空的效果,從簡單到複雜,從單一到通用,雖然藉助了一點點 JS ,但是僅僅是“工具人”的角色,互動邏輯全部都由 CSS 完成,下面總結一下:

  1. 足夠大的陰影是一個實現圓形鏤空效果的小技巧
  2. CSS 漸變也能輕易的繪製出圓形鏤空背景
  3. 藉助 CSS 變數可以很方便的利用滑鼠位置實現想要的效果
  4. backdrop-filter 可以想象成磨砂玻璃的功能
  5. CSS Mask 可以給磨砂玻璃打孔,實現鏤空的效果
  6. 藉助遮罩合成特性和SVG,可以實現任意形狀的鏤空效果

CSS MASK 還是非常強大的,有必要還是多掌握一下。最後,如果覺得還不錯,對你有幫助的話,歡迎點贊、收藏、轉發❤❤❤

相關文章