利用mask實現(xiàn)手電筒照亮效果

你看到一個效果,是不是會想著自己去實現(xiàn)一下。我就是這樣。之前看到過一個視頻,一個人拿著火把在一個迷宮里走路,火把只能照亮周圍的空間。我在想,如果用前端來實現(xiàn)這個功能,要怎么去實現(xiàn)?一直沒有想到合適的思路。 ?
最近看到一個css的屬性叫mask
。感覺利用這個mask
可以實現(xiàn)想要的效果。試了一下,果然實現(xiàn)了。 ?
這篇文章,就講解一下實現(xiàn)過程。
mask
我們先來看一下css中的mask
屬性。mask
屬性允許使用者通過遮罩或者裁切特定區(qū)域的圖片的方式來隱藏一個元素的部分或者全部可見區(qū)域。 ?
也就是說,我們利用mask
,可以對DOM元素顯示的部分進行裁剪。 ?
舉個例子,我們想斜著只想顯示右半邊的圖??梢赃@么寫:
1.img-box-test?{
2??width:?665px;
3??height:?400px;
4??background:?url(./gqq.jpeg)?no-repeat;
5??background-size:?contain;
6??mask:?linear-gradient(225deg,?#000?50%,?transparent?50%);
7??-webkit-mask:?linear-gradient(225deg,?#000?50%,?transparent?50%);
8}

代碼中linear-gradient
表示裁剪一個線, 225deg
表示漸變是從右上角往左下角變化。#000 50%
表示右上角(開始的位置)到50%進度時是#000
,這里是有透明度有效,你改成其他顏色效果一樣。transparent 50%
表示從50%位置到左下角(結(jié)束位置),都是透明的,所以效果就是如圖,右上角顯示圖片本身的效果,左下角為透明,顯示背景顏色。
中間是圓圈
那如果要顯示一個圈呢,只顯示中間的圖片內(nèi)容。
1.img-box-test?{
2??mask:?radial-gradient(
3????circle,
4????transparent?0%,
5????rgba(0,0,0,0.2)?10%,
6????rgba(0,0,0,1)?20%,
7????#000?100%
8??);
9??-webkit-mask:?radial-gradient(
10????circle,
11????#000?0%,
12????#000?10%,
13????transparent?40%,
14????transparent?100%
15??);
16}

說下代碼,radial-gradient
表示要畫一個圓形??梢允钦龍A形,也可以是橢圓形。circle
表示畫一個正圓。從圓形(0%位置)開始到10%位置,畫#000
,這里也是只有透明度生效,也就是圖片本身的內(nèi)容。10%
到40%
,走漸變,從不透明漸變成全透明。 最后從40%
到100%
,全部透明,顯示背景顏色。
遮罩顯示部分圖片
好了,到這一步,我們就可以開始實現(xiàn)效果了。這里有兩個思路:
圖片在一個父節(jié)點div中,圖片做上面的裁剪顯示,父節(jié)點div顯示黑色背景。
圖片節(jié)點做一個
::after
偽元素,偽元素覆蓋到圖片上,對偽元素做裁剪。
我這里用的是第二個思路。為什么選第二個,單純因為我喜歡。。。大家如果想自己寫著試試,可以用第一個思路去實現(xiàn)下。實現(xiàn)了可以在評論下面回復(fù)我,讓我看看你們的實現(xiàn)效果。
思路2的實現(xiàn)代碼:
1.img-box?{
2??position:?relative;
3??width:?665px;
4??height:?400px;
5??background:?url(./gqq.jpeg)?no-repeat;
6??background-size:?contain;
7}
8.img-box::after?{
9??content:?'';
10??position:?absolute;
11??left:?0;
12??top:?0;
13??width:?665px;
14??height:?400px;
15??z-index:?1;
16??background-color:?#000;
17
18??mask:?radial-gradient(
19????circle,
20????transparent?0%,
21????rgba(0,0,0,0.2)?20%,
22????rgba(0,0,0,1)?40%,
23????#000?100%
24??);
25??-webkit-mask:?radial-gradient(
26????circle,
27????transparent?0%,
28????rgba(0,0,0,0.2)?20%,
29????rgba(0,0,0,1)?40%,
30????#000?100%
31??);
32}

裁剪的放大縮小
為了實現(xiàn)光圈可以移動,以及光圈可以放大縮小。我這里需要使用到CSS變量。我分別設(shè)置了3個變量:
--radius
: 表示光圈的半徑--x
: 表示圓形的x軸距離--y
: 表示圓形的y軸距離
然后在::after
中設(shè)置這幾個變量:
這里,為了讓光圈大小變化,我直接用了transition
來做。后面通過其他CSS去改變--radius
的值,就能自動實現(xiàn)光圈大小的縮放動畫。
1.img-box::after?{
2??--radius:?20%;
3??--x:?330px;
4??--y:?200px;
5??content:?'';
6??position:?absolute;
7??left:?0;
8??top:?0;
9??width:?665px;
10??height:?400px;
11??z-index:?1;
12??background-color:?#000;
13??transition:?--radius?300ms?ease-in;
14
15??mask:?radial-gradient(
16????circle?at?var(--x)?var(--y),
17????transparent?0%,
18????rgba(0,0,0,0.2)?var(--radius),
19????rgba(0,0,0,1)?calc(var(--radius)?+?15%),
20????#000?100%
21??);
22??-webkit-mask:?radial-gradient(
23????circle?at?var(--x)?var(--y),
24????transparent?0%,
25????rgba(0,0,0,0.2)?var(--radius),
26????rgba(0,0,0,1)?calc(var(--radius)?+?15%),
27????#000?100%
28??);
29}
30.img-box-big::after?{
31??--radius:?15%;
32}
33.img-box-small::after?{
34??--radius:?5%;
35??transition-duration:?100ms;
36}
光圈跟隨隨便移動
好了,終于到最后一步了。到這里,就要開始用JS來監(jiān)聽鼠標移動了。
先把基礎(chǔ)結(jié)構(gòu)寫好,在圖片節(jié)點上,監(jiān)聽鼠標移動事件,如果有鼠標一動就會執(zhí)行方法。
1const?imgBox?=?document.getElementById('imgBox');
2
3function?moveLightRing(x,?y)?{
4??//?跟隨移動
5}
6
7imgBox.addEventListener('mouseenter',?(e)?=>?{
8??moveLightRing(e.offsetX,?e.offsetY);
9});
10imgBox.addEventListener('mousemove',?(e)?=>?{
11??moveLightRing(e.offsetX,?e.offsetY);
12});
13imgBox.addEventListener('mouseout',?(e)?=>?{
14??moveLightRing(e.offsetX,?e.offsetY);
15});
在moveLightRing
方法中,要考慮幾個事情:
因為鼠標不移動,光圈會變大,鼠標移動,光圈會變小。所以少量的移動,不能算作移動。
移動和停止移動的時候都要添加一個樣式用于改變光圈的半徑。
所以moveLightRing
的代碼如下:
1function?moveLightRing(x,?y)?{
2??//?短時間內(nèi)的短距離移動,不算移動
3??const?newTime?=?new?Date().getTime();
4??if?(lastMove.time?+?10?>?newTime
5????&&?lastMove.x?+?10?>?x
6????&&?lastMove.y?+?10?>?y
7??)?{
8????return;
9??}
10??//?記錄上一次的移動情況
11??lastMove.time?=?newTime;
12??lastMove.x?=?x;
13??lastMove.y?=?y;
14
15??//?移動,設(shè)置樣式
16??if?(imgBox.className.indexOf('img-box-small')?===?-1)?{
17????imgBox.className?=?'img-box?img-box-small';
18??}
19??//?TODO:?修改after偽元素樣式的--x,?--y的值
20
21??//?持續(xù)移動,不設(shè)置。不移動了,100ms后,設(shè)置光圈變大。
22??clearTimeout(st);
23??st?=?setTimeout(()?=>?{
24????imgBox.className?=?'img-box?img-box-big';
25??},?100);
26}
這里有一個問題:我沒法對::after
偽元素進行樣式設(shè)置。
解決對偽元素樣式設(shè)置的問題
現(xiàn)在還有一個問題,我沒法對::after
偽元素進行樣式設(shè)置。網(wǎng)上查了資料,都是說設(shè)置一個class
,然后通過class對偽元素進行設(shè)置。 但是我這個場景是需要不斷修改屬性值的。這種方案肯定不適用。
然后我突然想到,能不能利用屬性繼承。我們都知道,CSS中,父元素的某些屬性,是可以被子元素繼承。那么這里的CSS自定義屬性是否可以被繼承呢。我查了下,能被繼承,這樣我們的問題就解決了。

修改后代碼如下:
1function?moveLightRing(x,?y)?{
2??//?修改父節(jié)點的自定義樣式的值為當前位置
3??imgBox.style.setProperty('--x',?x?+?'px');
4??imgBox.style.setProperty('--y',?y?+?'px');
5}1.img-box?{
2??--x:?330px;
3??--y:?200px;
4}
5.img-box::after?{
6??--radius:?20%;
7??--x:?inherit;
8??--y:?inherit;
9}
功能完成,最后看下最終效果:

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