利用mask實現手電筒照亮效果

語言: CN / TW / HK

5.gif

你看到一個效果,是不是會想著自己去實現一下。我就是這樣。之前看到過一個視訊,一個人拿著火把在一個迷宮裡走路,火把只能照亮周圍的空間。我在想,如果用前端來實現這個功能,要怎麼去實現?一直沒有想到合適的思路。
最近看到一個css的屬性叫mask。感覺利用這個mask可以實現想要的效果。試了一下,果然實現了。
這篇文章,就講解一下實現過程。

mask

我們先來看一下css中的mask屬性。mask屬性允許使用者通過遮罩或者裁切特定區域的圖片的方式來隱藏一個元素的部分或者全部可見區域。
也就是說,我們利用mask,可以對DOM元素顯示的部分進行裁剪。
舉個例子,我們想斜著只想顯示右半邊的圖。可以這麼寫: css .img-box-test { width: 665px; height: 400px; background: url(./gqq.jpeg) no-repeat; background-size: contain; mask: linear-gradient(225deg, #000 50%, transparent 50%); -webkit-mask: linear-gradient(225deg, #000 50%, transparent 50%); } 1.png

程式碼中linear-gradient 表示裁剪一個線, 225deg表示漸變是從右上角往左下角變化。#000 50% 表示右上角(開始的位置)到50%進度時是#000,這裡是有透明度有效,你改成其他顏色效果一樣。transparent 50%表示從50%位置到左下角(結束位置),都是透明的,所以效果就是如圖,右上角顯示圖片本身的效果,左下角為透明,顯示背景顏色。

中間是圓圈

那如果要顯示一個圈呢,只顯示中間的圖片內容。 css .img-box-test { mask: radial-gradient( circle, transparent 0%, rgba(0,0,0,0.2) 10%, rgba(0,0,0,1) 20%, #000 100% ); -webkit-mask: radial-gradient( circle, #000 0%, #000 10%, transparent 40%, transparent 100% ); } 2.png

說下程式碼,radial-gradient表示要畫一個圓形。可以是正圓形,也可以是橢圓形。circle表示畫一個正圓。從圓形(0%位置)開始到10%位置,畫#000,這裡也是隻有透明度生效,也就是圖片本身的內容。10%40%,走漸變,從不透明漸變成全透明。 最後從40%100%,全部透明,顯示背景顏色。

遮罩顯示部分圖片

好了,到這一步,我們就可以開始實現效果了。這裡有兩個思路: 1. 圖片在一個父節點div中,圖片做上面的裁剪顯示,父節點div顯示黑色背景。 2. 圖片節點做一個::after偽元素,偽元素覆蓋到圖片上,對偽元素做裁剪。

我這裡用的是第二個思路。為什麼選第二個,單純因為我喜歡。。。大家如果想自己寫著試試,可以用第一個思路去實現下。實現了可以在評論下面回覆我,讓我看看你們的實現效果。

思路2的實現程式碼: ```css .img-box { position: relative; width: 665px; height: 400px; background: url(./gqq.jpeg) no-repeat; background-size: contain; } .img-box::after { content: ''; position: absolute; left: 0; top: 0; width: 665px; height: 400px; z-index: 1; background-color: #000;

mask: radial-gradient( circle, transparent 0%, rgba(0,0,0,0.2) 20%, rgba(0,0,0,1) 40%, #000 100% ); -webkit-mask: radial-gradient( circle, transparent 0%, rgba(0,0,0,0.2) 20%, rgba(0,0,0,1) 40%, #000 100% ); } ``` 3.png

裁剪的放大縮小

為了實現光圈可以移動,以及光圈可以放大縮小。我這裡需要使用到CSS變數。我分別設定了3個變數: 1. --radius: 表示光圈的半徑 2. --x: 表示圓形的x軸距離 3. --y: 表示圓形的y軸距離 然後在::after中設定這幾個變數: 這裡,為了讓光圈大小變化,我直接用了transition來做。後面通過其他CSS去改變--radius的值,就能自動實現光圈大小的縮放動畫。

```css .img-box::after { --radius: 20%; --x: 330px; --y: 200px; content: ''; position: absolute; left: 0; top: 0; width: 665px; height: 400px; z-index: 1; background-color: #000; transition: --radius 300ms ease-in;

mask: radial-gradient( circle at var(--x) var(--y), transparent 0%, rgba(0,0,0,0.2) var(--radius), rgba(0,0,0,1) calc(var(--radius) + 15%), #000 100% ); -webkit-mask: radial-gradient( circle at var(--x) var(--y), transparent 0%, rgba(0,0,0,0.2) var(--radius), rgba(0,0,0,1) calc(var(--radius) + 15%), #000 100% ); } .img-box-big::after { --radius: 15%; } .img-box-small::after { --radius: 5%; transition-duration: 100ms; } ```

光圈跟隨隨便移動

好了,終於到最後一步了。到這裡,就要開始用JS來監聽滑鼠移動了。 先把基礎結構寫好,在圖片節點上,監聽滑鼠移動事件,如果有滑鼠一動就會執行方法。 ```javascript const imgBox = document.getElementById('imgBox');

function moveLightRing(x, y) { // 跟隨移動 }

imgBox.addEventListener('mouseenter', (e) => { moveLightRing(e.offsetX, e.offsetY); }); imgBox.addEventListener('mousemove', (e) => { moveLightRing(e.offsetX, e.offsetY); }); imgBox.addEventListener('mouseout', (e) => { moveLightRing(e.offsetX, e.offsetY); }); `` 在moveLightRing`方法中,要考慮幾個事情: 1. 因為滑鼠不移動,光圈會變大,滑鼠移動,光圈會變小。所以少量的移動,不能算作移動。 2. 移動和停止移動的時候都要新增一個樣式用於改變光圈的半徑。

所以moveLightRing的程式碼如下: ```javascript function moveLightRing(x, y) { // 短時間內的短距離移動,不算移動 const newTime = new Date().getTime(); if (lastMove.time + 10 > newTime && lastMove.x + 10 > x && lastMove.y + 10 > y ) { return; } // 記錄上一次的移動情況 lastMove.time = newTime; lastMove.x = x; lastMove.y = y;

// 移動,設定樣式 if (imgBox.className.indexOf('img-box-small') === -1) { imgBox.className = 'img-box img-box-small'; } // TODO: 修改after偽元素樣式的--x, --y的值

// 持續移動,不設定。不移動了,100ms後,設定光圈變大。 clearTimeout(st); st = setTimeout(() => { imgBox.className = 'img-box img-box-big'; }, 100); } ```

這裡有一個問題:我沒法對::after偽元素進行樣式設定。

解決對偽元素樣式設定的問題

現在還有一個問題,我沒法對::after偽元素進行樣式設定。網上查了資料,都是說設定一個class,然後通過class對偽元素進行設定。 但是我這個場景是需要不斷修改屬性值的。這種方案肯定不適用。

然後我突然想到,能不能利用屬性繼承。我們都知道,CSS中,父元素的某些屬性,是可以被子元素繼承。那麼這裡的CSS自定義屬性是否可以被繼承呢。我查了下,能被繼承,這樣我們的問題就解決了。 4.png

修改後程式碼如下: javascript function moveLightRing(x, y) { // 修改父節點的自定義樣式的值為當前位置 imgBox.style.setProperty('--x', x + 'px'); imgBox.style.setProperty('--y', y + 'px'); }

css .img-box { --x: 330px; --y: 200px; } .img-box::after { --radius: 20%; --x: inherit; --y: inherit; }

功能完成,最後看下最終效果: 5.gif

結束

好了,本文到此結束,希望本文對你有所幫助 :-) 最近新弄了一個🌏號:寫程式碼的浩,求關注 😄。後面會逐步把掌握的前端知識以及職場知識沉澱下來。 如果還有什麼疑問或者建議,可以多多交流,原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

開啟掘金成長之旅!這是我參與「掘金日新計劃 · 2 月更文挑戰」的第 9 天,點選檢視活動詳情