Reputation: 3553
So Windows has this incredible effect when you place your mouse near a 'tile' like so:
Just for curiosty/fun, what is the best way to emulate such an effect using css and/or JS? I'm using a grid layout to create the tiles, but since I'd rather solve the problem than get into specifics, let's simplify things with these assumptions:
a) The positions of the tiles are fixed and won't change during while the user is on the page
b) It is possible to get the row and column number of any tile using JS
c) All tiles have the same dimensions
d) The tile container is completely filled with no empty tiles-slots
So at first I put a mousemove
event listener on the tile container, and for every move the mouse makes, I loop through all tiles, find all whose edges lie in the proximity of the mouse, and apply the light up effect to them appropriately. But this started taking too much time as the number of tiles increased.
Then I tried using applying some calculations with the mouse coordinates relative to the tile container, and extracting "nearby" tiles based on the coordinates, but that's causing too many issues for me, perhaps due to some incorrect maths.
Trying to figure out a fast-ish method to emulate Window's tile light-up-effect-thingy, even a crude copy is fine as long as it looks cool enough. Any assistance would be much appreciated :)
Upvotes: 5
Views: 609
Reputation: 979
This border/background effect is called "reveal" and is part of the Fluent Design System. Luckily, you aren't the first wanting to implement this. You can take a look at this project: https://github.com/d2phap/fluent-reveal-effect. It's an implementation of the reveal effect and works pretty well.
If you want to solve it yourself, I think the mousemove
event is the right one to choose. When the mouse moves, get the distance from the mouse position to every element with reveal effect and determine if they are near enough to have the reveal effect. If an element is near enough, just render a circular gradient on the position of the mouse and set that at the background/border for every element in the "reveal radius". This should work well enough for a few dozens items.
In addition to mousemove, you will also need to handle mouseleave
and mouseenter
to not have the reveal be stuck at the border and you also need to listen to scrolling events as otherwise your reveal also will be stuck at the previous position.
An even more performant way of doing this (though I am not entirely sure how to implement it) would be to have an image where you draw the reveal effect as if it is one huge button. Then you just "cut out" the surface and set the background for buttons to cutouts of the image based on their position. The issue is creating the surface and applying it to your controls correctly so I would opt for the first option if there are not too many items.
Upvotes: 3
Reputation: 8368
Here's an admittedly hacky solution. Though, if your menu has static dimensions, it should do the trick:
const container = document.getElementById('container');
const glow = document.getElementById('glow');
container.addEventListener('mousemove', e => {
glow.style.left = e.pageX + 'px';
glow.style.top = e.pageY + 'px';
});
container.addEventListener('mouseenter', () => {
glow.style.display = 'block';
})
container.addEventListener('mouseleave', () => {
glow.style.display = 'none';
})
body {
margin: 0;
background-color: #111111;
}
#container {
display: grid;
grid-template-columns: repeat(3, 150px);
grid-template-rows: repeat(3, 100px);
gap: 10px;
position: relative;
width: fit-content;
overflow: hidden;
}
#container>* {
background: rgb(77, 77, 77);
}
#glow {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 200px;
background: radial-gradient(#fff, transparent 70%);
transform: translate(-50%, -50%);
z-index: -1;
}
#container .vertical {
position: absolute;
height: 100%;
width: 8px;
top: 0;
background: #111111;
}
.vertical-one {
left: 151px;
}
.vertical-two {
left: 311px;
}
#container .horizontal {
position: absolute;
width: 100%;
height: 8px;
left: 0;
background: #111111;
}
.horizontal-one {
top: 101px;
}
.horizontal-two {
top: 211px;
}
<div id="container">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div id="glow"></div>
<div class="vertical vertical-one"></div>
<div class="vertical vertical-two"></div>
<div class="horizontal horizontal-one"></div>
<div class="horizontal horizontal-two"></div>
</div>
It's essentially a div with a white radial-gradient which follows the cursor position as it moves. The hacky part is using bars which are the same color as the background in between each item. Not what I'd call a scalable solution, but, it works.
Upvotes: 6