为了账号安全,请及时绑定邮箱和手机立即绑定

利用mask实现手电筒照亮效果

标签:
Html5 CSS3

图片描述
你看到一个效果,是不是会想着自己去实现一下。我就是这样。之前看到过一个视频,一个人拿着火把在一个迷宫里走路,火把只能照亮周围的空间。我在想,如果用前端来实现这个功能,要怎么去实现?一直没有想到合适的思路。
最近看到一个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;
}

功能完成,最后看下最终效果:
图片描述

结束

好了,本文到此结束,希望本文对你有所帮助 :-)
最近新弄了一个🌏号:写代码的浩,求关注 😄。后面会逐步把掌握的前端知识以及职场知识沉淀下来。
如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Web前端工程师
手记
粉丝
1
获赞与收藏
4

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消