Spotlight on hidden background

·

3 min read

The logical way of thinking about this is to have a background (colour, gradient or image) with an overlay that hides it. The spotlight then makes that part of the overlay transparent. But that's all we actually want to make it look like we're doing. Instead, we can have the background on top and hide most of it. Here's how to do it.

Initial set up

<div class="spotlight"></div>
html,
body {
  height: 100%;
  margin: 0;
}

body {
  position: relative;
  width: 100%;
  height: 100%;
  background-color: black;
}

This gives us a black background that fills the screen. The .spotlight div will be a circle that reveals the background.

The spotlight

.spotlight {
  position: absolute;
  inset: 0;
  background-image: linear-gradient(to bottom right, 
    red, orange, yellow, green, blue, indigo, violet,
    red, orange, yellow, green, blue, indigo, violet);
  clip-path: circle(5% at 50% 50%);
}

I've positioned the spotlight absolutely so we can move it with the mouse. It fills the screen and I've added a rainbow background gradient, since it's June and Pride month. It's not one where every colour perfectly blends into another, and there's some pink between the violet and red. But since we'll only get to see a little of it at a time, it doesn't matter.

The clip-path creates a small circle in the middle of the screen. Anything outside of it is hidden, so we only see the background inside that small circle.

Moving the spotlight

To move the spotlight, all we need to do is to update the clip-path circle position. The easiest way to do that is to use custom properties:

.spotlight {
  --left-circle: 50%;
  --top-circle: 50%;
  position: absolute;
  inset: 0;
  background-image: linear-gradient(to bottom right, 
    red, orange, yellow, green, blue, indigo, violet,
    red, orange, yellow, green, blue, indigo, violet);
  clip-path: circle(5% at var(--left-circle) var(--top-circle));
}

Then we can use the mousemove event listener to detect where the mouse is:

const spotlight = document.querySelector('.spotlight');

spotlight.addEventListener('mousemove', e => {
  spotlight.style.setProperty('--left-circle', e.clientX + 'px');
  spotlight.style.setProperty('--top-circle', e.clientY + 'px');
});

clientX and clientY will get us the horizontal and vertical mouse position, where (0, 0) is the top left. This means that if you move the mouse to the very top left of the screen the clip-path becomes clip-path: circle(5% at 0 0);

All you have to do is to move your mouse around you can see different parts of the background in the circle.

Adding some blur

The edge of the circle being less sharp would make it look better. A little more like a spotlight, which has a lot of light in the centre and less at the edges. However, adding blur to the edge is a little complicated when you have a clip-path.

If you didn't have a clip-path, then a box-shadow would do it. However, our .spotlight fills the screen, so we won't be able to see a shadow. A drop-shadow doesn't help because it has the same problem.

The solution is to add a drop-shadow to the element, but then have another inside it with the clip-path and background:

.spotlight {
  --left-circle: 50%;
  --top-circle: 50%;
  position: absolute;
  inset: 0;
  filter: drop-shadow(0 0 20px rgba(255, 255, 255, .75));

  &::before {
    content: '';
    position: absolute;
    inset: 0;
    background-image: linear-gradient(to bottom right, red, orange, yellow, green, blue, indigo, violet, red, orange, yellow, green, blue, indigo, violet);
    clip-path: circle(5% at var(--left-circle) var(--top-circle));
  }
}

And there you have a white blur around the circle.

Final code

Here is the final code: