Как сделать эффект как у 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 шт):
"Но если вы проверите пример, то иногда блик может моргать. Это из-за дочерних элементов." Не, это не из-за этого.. Вот где ты написал "//Дальше сложнее и я мало что понял." Есть расчёт углов 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>
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> Подсвечивать внутри кнопки</label><br>
<input type=checkbox id=with-gap><label for=with-gap> Зазор между кнопками</label>
<section>
<div>
<button>A</button>
<button>B</button>
<button>C</button>
<button>D</button>
</div>
</section>
Предлагаю альтернативный и более эффективный подход.
Вместо того, чтобы для каждой кнопки (и границы) вычислять и задавать индивидуальный градиент на основе позиции, можно задать единый общий фон с градиентом. Слой сверху содержит кнопки. Кнопки и их границы являются "окнами", через которые просвечивает градиент.
Загвоздка в том, как сделать "дырявый" блок? Сначала я копался с 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, иначе будут просвечивать тоже.
