Как сделать эффект как у Windows 10 (свечение кнопок)

Хочу сделать эффект наведение как у windows 10. Вот такой:

введите сюда описание изображения

Видите? У него есть свечение border, и внутренние свечение.

Т.е если навожу на кнопку, то курсор мыши горит, и как бы внутри освещает кнопку. А так-же рядом начинают светиться как блики border. Ну кто может проверьте и поймете. Я думаю вы знаете такой эффект. Лучше конечно показать видео но я не могу.

Я нашел примеры и вот как я сделал внутренние свечение:

//Вот этот код легкий
$(document).ready(function(){
    $(".btn-effect").on('mousemove', function(event){
        var thisElement = $(this); //Берем элемент над которым висит мышь
        var rect = thisElement.offset(); //Берем текущие координаты кнопки относительно окна
        //Расчитываем координаты мыши внутри кнопки
        var x = event.clientX - rect.left; 
        var y = event.clientY - rect.top;
        
        //Делаем radial-gradient рисуем круг, и перетаскиваем его по за мышью
        thisElement.css("background", `radial-gradient(circle at ${x}px ${y}px ,    rgba(255,255,255,0.2),rgba(36,36,36,1) )`);
        //Делаем border-image, вместе картинки рисуем круг, и сужаем границы картинки. И так-же перетаскивает за мышью.
          thisElement.css("border-image", `radial-gradient(20% 75% at ${x}px ${y}px ,rgba(255,255,255,0.7),rgba(255,255,255,0.1) ) 9 / 1px / 0px stretch `);
    });
    
    //Если мышк уведена то делаем прежние стили
    $(".btn-effect").on('mouseout', function(event){
        var thisElement = $(this);
        thisElement.css("background", `#181818`);
        thisElement.css("border-image", `none`);
    });
});

//Дальше сложнее и я мало что понял.
const offset = 69;
const angles = []; //in deg
for (let i = 0; i <= 360; i += 45) {
  angles.push((i * Math.PI) / 180);
}
let nearBy = [];

function clearNearBy() {
    nearBy.splice(0, nearBy.length).forEach((e) => (e.style.borderImage = null));
}


//Понял только то что мы тут создаем окружность вокруг мыши, и далее проверяем элементы которые вошли в окружность. И если он найден то добавляем стили кнопки. И изменяем опять-же border-image.
const body = document.querySelector(".container-effect");
    body.addEventListener("mousemove", (e) => {
        const x = e.x; //x position within the element.
        const y = e.y; //y position within the element.

        clearNearBy();
        nearBy = angles.reduce((acc, rad, i, arr) => {
            const cx = Math.floor(x + Math.cos(rad) * offset);
            const cy = Math.floor(y + Math.sin(rad) * offset);
            const element = document.elementFromPoint(cx, cy);
                if (element !== null) {
                    if (
                    element.classList.contains("btn-effect")
                    ) {
                        const brect = element.getBoundingClientRect();
                        const bx = x - brect.left; //x position within the element.
                        const by = y - brect.top; //y position within the element.
                        if (!element.style.borderImage)
                        element.style.borderImage = `radial-gradient(${offset * 2}px ${
                            offset * 2
                        }px at ${bx}px ${by}px ,rgba(255,255,255,0.7),rgba(255,255,255,0.1),transparent ) 9 / 1px / 0px stretch `;
                        return [...acc, element];
                }
            }
            return acc;
        }, []);
    });
    
    
body{
    background-color: #1c1c1c;
    font-family: verdana, trebuchet, sans-serif;
}
.container-effect{
    display: flex;
    gap: 10px;
    padding: 30px;
}

.btn-effect{
    display: flex;
    justify-content: center;
    padding: 15px;
    width: 120px;
    border-radius: 5px;
    background-color: #181818;
    margin-top: 20px;
    cursor: pointer;
    border: 1px solid transparent;
}

.btn-effect .text{
     color: #fff;
     font-size: 12px;
}
<!DOCTYPE html>
<html>
<head>
    <title>Эффект</title>
  <script src="https://code.jquery.com/jquery-3.4.1.min.js" ></script>
</head>
<body>
  <div class="container-effect">
      <div class="btn-effect">
          <div class="text">эффект</div>
          <div class="btn"></div>
       </div>
      <div class="btn-effect">
          <div class="text">эффект</div>
          <div class="btn"></div>
       </div>
   </div>
</body>
</html>

Вроде все работает. Но если вы проверите пример, то иногда блик может моргать. Это из-за дочерних элементов. Когда мы находим его, <div class="text">эффект</div> или другой, то стили сразу-же сбрасываются и из-за проверки не обрабатываются. Может вы как то улучшите код или скажите что я делаю не так?


Ответы (3 шт):

Автор решения: Rudi

"Но если вы проверите пример, то иногда блик может моргать. Это из-за дочерних элементов." Не, это не из-за этого.. Вот где ты написал "//Дальше сложнее и я мало что понял." Есть расчёт углов for (let i = 0; i <= 360; i += 45) и там у тебя деление было 45 соответственно появлялись "слепые зоны". Если поставишь от ~ 1 - 20 их не будет и моргать перестанет.

//Вот этот код легкий
$(document).ready(function() {
  $(".btn-effect").on('mousemove', function(event) {
    var thisElement = $(this); //Берем элемент над которым висит мышь
    var rect = thisElement.offset(); //Берем текущие координаты кнопки относительно окна
    //Расчитываем координаты мыши внутри кнопки
    var x = event.clientX - rect.left;
    var y = event.clientY - rect.top;

    //Делаем radial-gradient рисуем круг, и перетаскиваем его по за мышью
    thisElement.css("background", `radial-gradient(circle at ${x}px ${y}px ,    rgba(255,255,255,0.2),rgba(36,36,36,1) )`);
    //Делаем border-image, вместе картинки рисуем круг, и сужаем границы картинки. И так-же перетаскивает за мышью.
    thisElement.css("border-image", `radial-gradient(20% 75% at ${x}px ${y}px ,rgba(255,255,255,0.7),rgba(255,255,255,0.1) ) 9 / 1px / 0px stretch `);
  });

  //Если мышк уведена то делаем прежние стили
  $(".btn-effect").on('mouseout', function(event) {
    var thisElement = $(this);
    thisElement.css("background", `#181818`);
    thisElement.css("border-image", `none`);
  });
});

//Дальше сложнее и я мало что понял.
const offset = 69;
const angles = []; //in deg
for (let i = 0; i <= 360; i += 20) {
  angles.push((i * Math.PI) / 180);
}
let nearBy = [];

function clearNearBy() {
  nearBy.splice(0, nearBy.length).forEach((e) => (e.style.borderImage = null));
}


//Понял только то что мы тут создаем окружность вокруг мыши, и далее проверяем элементы которые вошли в окружность. И если он найден то добавляем стили кнопки. И изменяем опять-же border-image.
const body = document.querySelector(".container-effect");
body.addEventListener("mousemove", (e) => {
  const x = e.x; //x position within the element.
  const y = e.y; //y position within the element.

  clearNearBy();
  nearBy = angles.reduce((acc, rad, i, arr) => {
    const cx = Math.floor(x + Math.cos(rad) * offset);
    const cy = Math.floor(y + Math.sin(rad) * offset);
    const element = document.elementFromPoint(cx, cy);
    if (element !== null) {
      if (
        element.classList.contains("btn-effect")
      ) {
        const brect = element.getBoundingClientRect();
        const bx = x - brect.left; //x position within the element.
        const by = y - brect.top; //y position within the element.
        if (!element.style.borderImage)
          element.style.borderImage = `radial-gradient(${offset * 2}px ${
                            offset * 2
                        }px at ${bx}px ${by}px ,rgba(255,255,255,0.7),rgba(255,255,255,0.1),transparent ) 9 / 1px / 0px stretch `;
        return [...acc, element];
      }
    }
    return acc;
  }, []);
});
body {
  background-color: #1c1c1c;
  font-family: verdana, trebuchet, sans-serif;
}

.container-effect {
  display: flex;
  gap: 10px;
  padding: 30px;
}

.btn-effect {
  display: flex;
  justify-content: center;
  padding: 15px;
  width: 120px;
  border-radius: 5px;
  background-color: #181818;
  margin-top: 20px;
  cursor: pointer;
  border: 1px solid transparent;
}

.btn-effect .text {
  color: #fff;
  font-size: 12px;
}
<!DOCTYPE html>
<html>

<head>
  <title>Эффект</title>
  <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
</head>

<body>
  <div class="container-effect">
    <div class="btn-effect">
      <div class="text">эффект</div>
      <div class="btn"></div>
    </div>
    <div class="btn-effect">
      <div class="text">эффект</div>
      <div class="btn"></div>
    </div>
  </div>
</body>

</html>

→ Ссылка
Автор решения: Qwertiy

document.querySelector('div').addEventListener('mousemove', e => {
  var div = e.target.closest('div')
  var { x, y } = div.getBoundingClientRect()
  div.style.setProperty('--mousex', e.clientX - x + 'px')
  div.style.setProperty('--mousey', e.clientY - y + 'px')
})
* {
  box-sizing: border-box;
}

section {
  margin-top: 1em;
  background: black;
  padding: 2em;
}

div {
  width: min-content;
  padding: 1px;
  display: flex;
  gap: 1px;
  background: #444;
  --mousex: 0px;
  --mousey: 0px;
}

div:hover {
  background: radial-gradient(circle at var(--mousex) var(--mousey), white, #444 3em);
}

button {
  background: black;
  border: 1px solid transparent;
  font-size: 1rem;
  line-height: 2rem;
  min-width: 2rem;
  color: white;
  padding: .5em 1em;
}

#with-buttons:checked ~ section button:hover {
  background: rgba(0, 0, 0, .75);
}

#with-gap:checked ~ section div {
  gap: calc(1em + 2px);
  overflow: hidden;
}

#with-gap:checked ~ section button {
  outline: 1em solid black;
  outline-offset: 1px;
}
<input type=checkbox id=with-buttons><label for=with-buttons>&nbsp;Подсвечивать внутри кнопки</label><br>
<input type=checkbox id=with-gap><label for=with-gap>&nbsp;Зазор между кнопками</label>

<section>
  <div>
    <button>A</button>
    <button>B</button>
    <button>C</button>
    <button>D</button>
  </div>
</section>

→ Ссылка
Автор решения: Laukhin Andrey

Предлагаю альтернативный и более эффективный подход.

Вместо того, чтобы для каждой кнопки (и границы) вычислять и задавать индивидуальный градиент на основе позиции, можно задать единый общий фон с градиентом. Слой сверху содержит кнопки. Кнопки и их границы являются "окнами", через которые просвечивает градиент.

Загвоздка в том, как сделать "дырявый" блок? Сначала я копался с mix-blend-mode и даже что-то получилось, но в итоге зашёл в тупик.

Тогда я взял grid, а вместо штатного gap сделал обёртки для кнопок с бордюрами. Позднее, позаимствовал у @Qwertiy идею с outline. Получился макет, в котором можно управлять "светопропусканием" кнопок и границ через CSS. Роль JS отводится только для позиционирования градиента от положения курсора.

Вот что из этого получилось:

let menu = document.getElementById('menu');

menu.addEventListener('mousemove', function(e) {
  let x = e.pageX - e.currentTarget.clientLeft;
  let y = e.pageY - e.currentTarget.clientTop;

  menu.style.background = `radial-gradient(circle at ${x}px ${y}px, var(--light-color), var(--bkg-color) 100px`;
});
#menu {
  --light-color: rgb(170, 170, 170);  
  --bkg-color: rgb(36, 36, 36);
  --btn-color: rgb(60, 60, 60);
  --btn-hover: rgba(70, 70, 80, 0.8);
  --brd-hover: rgba(170, 170, 170, 1);
  --brd-size: 2px;
  --gap: 10px;
  
  display: grid;
  grid-template: repeat(3, 80px) / repeat(5, 80px);
  border: var(--gap) solid var(--bkg-color);
  width: fit-content;
  user-select: none;
  gap: var(--gap);
}

#menu:not(:hover) { background: var(--bkg-color) !important; }

#menu >* {
  box-sizing: border-box;
  outline: calc(var(--gap) / 2 + 0.5px) solid var(--bkg-color);
}

.button {
  font-size: 14px;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  background-clip: padding-box;
  border: var(--brd-size) solid transparent;
  background-color: var(--btn-color);
  overflow: hidden;
}

.button:hover {
  backdrop-filter: blur(10px);
  border: var(--brd-size) solid var(--brd-hover);
  background-color: var(--btn-hover);
}

.empty {
  background: var(--bkg-color);
}

.btn-s2 { grid-column: span 2; }
.btn-s4 { grid-column: span 2; grid-row: span 2; }
.material-symbols-sharp { font-size: 24px !important; }
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp:opsz,wght,FILL,GRAD@24,400,0,0" />

<div id="menu">
    <div class="button">button</div>
    <div class="button">button</div>
    <div class="button">button</div>
    <div class="button btn-s2">Long button</div>
    <div class="button"><span class="material-symbols-sharp">description</span></div>
    <div class="empty"></div>
    <div class="button btn-s4">Big button</div>
    <div class="button">button</div>
    <div class="button"><span class="material-symbols-sharp">download</span></div>
    <div class="button"><span class="material-symbols-sharp">terminal</span></div>
    <div class="button"><span class="material-symbols-sharp">calculate</span></div>
</div>

Можно настраивать все параметры в CSS (цвет освещения, фон меню и кнопок).
P.S. в пустые ячейки нужно вставлять блок .empty, иначе будут просвечивать тоже.

→ Ссылка