乐趣区

关于前端:利用mask实现手电筒照亮效果

你看到一个成果,是不是会想着本人去实现一下。我就是这样。之前看到过一个视频,一个人拿着火把在一个迷宫里走路,火把只能照亮四周的空间。我在想,如果用前端来实现这个性能,要怎么去实现?始终没有想到适合的思路。
最近看到一个 css 的属性叫 mask。感觉利用这个mask 能够实现想要的成果。试了一下,果然实现了。
这篇文章,就解说一下实现过程。

mask

咱们先来看一下 css 中的 mask 属性。mask属性容许使用者通过遮罩或者裁切特定区域的图片的形式来暗藏一个元素的局部或者全副可见区域。
也就是说,咱们利用 mask,能够对 DOM 元素显示的局部进行裁剪。
举个例子,咱们想斜着只想显示右半边的图。能够这么写:

.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%);
}

代码中 linear-gradient 示意裁剪一个线,225deg 示意突变是从右上角往左下角变动。#000 50% 示意右上角 (开始的地位) 到 50% 进度时是 #000,这里是有透明度无效,你改成其余色彩成果一样。transparent 50% 示意从 50% 地位到左下角(完结地位),都是通明的,所以成果就是如图,右上角显示图片自身的成果,左下角为通明,显示背景色彩。

两头是圆圈

那如果要显示一个圈呢,只显示两头的图片内容。

.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%
  );
}

说下代码,radial-gradient示意要画一个圆形。能够是正圆形,也能够是椭圆形。circle示意画一个正圆。从圆形 (0% 地位) 开始到 10% 地位,画 #000,这里也是只有透明度失效,也就是图片自身的内容。10%40%,走突变,从不通明渐变成全透明。最初从 40%100%,全副通明,显示背景色彩。

遮罩显示局部图片

好了,到这一步,咱们就能够开始实现成果了。这里有两个思路:

  1. 图片在一个父节点 div 中,图片做下面的裁剪显示,父节点 div 显示彩色背景。
  2. 图片节点做一个 ::after 伪元素,伪元素笼罩到图片上,对伪元素做裁剪。

我这里用的是第二个思路。为什么选第二个,单纯因为我喜爱。。。大家如果想本人写着试试,能够用第一个思路去实现下。实现了能够在评论上面回复我,让我看看你们的实现成果。

思路 2 的实现代码:

.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%
  );
}

裁剪的放大放大

为了实现光圈能够挪动,以及光圈能够放大放大。我这里须要应用到 CSS 变量。我别离设置了 3 个变量:

  1. --radius: 示意光圈的半径
  2. --x: 示意圆形的 x 轴间隔
  3. --y: 示意圆形的 y 轴间隔
    而后在 ::after 中设置这几个变量:
    这里,为了让光圈大小变动,我间接用了 transition 来做。前面通过其余 CSS 去扭转 --radius 的值,就能主动实现光圈大小的缩放动画。
.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 来监听鼠标挪动了。
先把根底构造写好,在图片节点上,监听鼠标挪动事件,如果有鼠标一动就会执行办法。

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 的代码如下:

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 自定义属性是否能够被继承呢。我查了下,能被继承,这样咱们的问题就解决了。

批改后代码如下:

function moveLightRing(x, y) {
  // 批改父节点的自定义款式的值为以后地位
  imgBox.style.setProperty('--x', x + 'px');
  imgBox.style.setProperty('--y', y + 'px');
}
.img-box {
  --x: 330px;
  --y: 200px;
}
.img-box::after {
  --radius: 20%;
  --x: inherit;
  --y: inherit;
}

性能实现,最初看下最终成果:

完结

好了,本文到此结束,心愿本文对你有所帮忙 :-)
最近新弄了一个🌏号:写代码的浩,求关注 😄。前面会逐渐把把握的前端常识以及职场常识积淀下来。
如果还有什么疑难或者倡议,能够多多交换,原创文章,文笔无限,满腹经纶,文中若有不正之处,万望告知。

退出移动版