Use mask to achieve flashlight lighting effect


When you see an effect, do you think about realizing it yourself? This is how I am. I saw a video before, a person walking in a maze with a torch, the torch can only illuminate the surrounding space. I was thinking, if the front end is used to realize this function, how to realize it? Never thought of a suitable idea.
I recently saw a css attribute called mask. I feel that using this mask can achieve the desired effect. I tried it, and it worked.
This article explains the implementation process.

mask

Let's first look at the mask property in css. The mask attribute allows users to hide part or all of an element's visible area by masking or cropping a specific area of ​​the image.
In other words, we can use mask to crop the part displayed by the DOM element.
For example, we want to display only the right half of the graph diagonally. It can be written like this:

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

In the code, linear-gradient means to cut a line, and 225deg means that the gradient changes from the upper right corner to the lower left corner. #000 50% means that when the upper right corner (the starting position) reaches 50% progress, it will be #000, here is the effect of transparency, if you change it to other colors, the effect will be the same. transparent 50% means that from the 50% position to the lower left corner (the end position), it is transparent, so the effect is as shown in the figure, the upper right corner shows the effect of the picture itself, and the lower left corner is transparent, showing the background color.

circle in the middle

So if you want to display a circle, only the content of the image in the middle will be displayed.

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

Speaking of the code, radial-gradient means to draw a circle. It can be a perfect circle or an ellipse. circle means to draw a perfect circle. From the circle (0% position) to the 10% position, draw #000, here also only the transparency takes effect, which is the content of the picture itself. From 10% to 40%, take a gradient, from opaque to fully transparent. Finally, from 40% to 100%, all transparent, showing the background color.

Mask to show part of the image

Well, at this point, we can start to achieve the effect. There are two ideas here:

  1. The picture is in a parent node div, the picture is cropped and displayed above, and the parent node div displays a black background.
  2. The picture node makes a ::after pseudo-element, the pseudo-element is overlaid on the picture, and the pseudo-element is cropped.

I am using the second idea here. Why choose the second one, simply because I like it. . . If you want to try writing it yourself, you can use the first idea to realize it. If you have achieved it, you can reply me under the comment, and let me see your implementation effect.

The implementation code of idea 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%
  );
}

Zoom in and out of cropping

In order to realize that the aperture can be moved, and the aperture can be zoomed in and out. I need to use CSS variables here. I set 3 variables separately:

  1. --radius: Indicates the radius of the aperture
  2. --x: Indicates the x-axis distance of the circle
  3. --y: Indicates the y-axis distance of the circle
    Then set these variables in ::after:
    Here, in order to change the size of the aperture, I directly use transition to do it. Later, by changing the value of --radius through other CSS, the zoom animation of the aperture size can be automatically realized.
.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;
}

Aperture follow random movement

Well, finally to the last step. At this point, we will start to use JS to monitor mouse movement.
Write the basic structure first. On the image node, monitor the mouse movement event, and execute the method if there is a mouse movement.

const imgBox = document.getElementById('imgBox');

function moveLightRing(x, y) {
  // Follow the move
}

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

In the moveLightRing method, there are several things to consider:

  1. Because the mouse does not move, the aperture will become larger, and the mouse will move, and the aperture will become smaller. So a small amount of movement cannot be counted as movement.
  2. Add a style to change the radius of the aperture when moving and stopping.

So the code for moveLightRing is as follows:

function moveLightRing(x, y) {
  // Movement of short distances in a short period of time is not counted as movement
  const newTime = new Date().getTime();
  if (lastMove.time + 10 > newTime
    && lastMove.x + 10 > x
    && lastMove.y + 10 > y
  ) {
    return;
  }
  // Record the last movement
  lastMove.time = newTime;
  lastMove.x = x;
  lastMove.y = y;

  // move, style
  if (imgBox.className.indexOf('img-box-small') === -1) {
    imgBox.className = 'img-box img-box-small';
  }
  // TODO: Modify the --x, --y values ​​of the after pseudo-element style

  // Keep moving, don't set. Stop moving, after 100ms, set the aperture to become larger.
  clearTimeout(st);
  st = setTimeout(() => {
    imgBox.className = 'img-box img-box-big';
  }, 100);
}

Here's the problem: I can't style the ::after pseudo-element.

Solve the problem of styling pseudo elements

Now there's one more problem, I can't style the ::after pseudo-element. After checking the information on the Internet, it is said to set a class, and then set the pseudo-element through the class. But my scenario requires constant modification of attribute values. This solution is certainly not applicable.

Then it suddenly occurred to me whether I could use property inheritance. We all know that in CSS, some properties of parent elements can be inherited by child elements. So can the CSS custom properties here be inherited? I checked and it can be inherited, so our problem is solved.

The modified code is as follows:

function moveLightRing(x, y) {
  // Modify the value of the custom style of the parent node to the current position
  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;
}

The function is complete, and finally look at the final effect:

Finish

Well, this is the end of this article, I hope this article is helpful to you :-)
Recently, I got a new 🌏 account: Hao who writes code, please pay attention πŸ˜„. Later, I will gradually accumulate the front-end knowledge and workplace knowledge I have mastered.
If you have any questions or suggestions, you can communicate more. Original articles have limited writing skills and lack of knowledge. If there are any inaccuracies in the article, please let me know.

Tags: Front-end Javascript css3

Posted by Heatmizer20 on Sat, 25 Feb 2023 01:32:28 +1030