Пишем разметку:
Пишем стиль для разметки:
Пишем js:
Смотрим наш результат:
Код:
<button class="prism">Click Me</button>
<button class="prism">Click Me</button>
SCSS:
.prism {
--intensity: 1;
--scaleFactor: 1.01;
--blur: 5;
--redTop: 0;
--redLeft: 0;
--greenTop: 0;
--greenLeft: 0;
--blueTop: 0;
--blueLeft: 0;
position: relative;
top: 0;
color: var(--fg);
background: var(--bg);
border: 2px solid #333;
transition: border-color 0.2s linear, top 0.1s ease;
& > .red, & > .green, & > .blue {
position: absolute;
width: calc(var(--scaleFactor) * 100%);
height: calc(var(--scaleFactor) * 100%);
z-index: -1;
border-radius: inherit;
filter: blur(calc(var(--blur) * 1px));
mix-blend-mode: screen;
opacity: var(--intensity);
}
& > .red {
top: calc(var(--redTop) * 1px);
left: calc(var(--redLeft) * 1px);
background: rgb(255, 0, 0);
}
& > .green {
top: calc(var(--greenTop) * 1px);
left: calc(var(--greenLeft) * 1px);
background: rgb(0, 255, 0);
}
& > .blue {
top: calc(var(--blueTop) * 1px);
left: calc(var(--blueLeft) * 1px);
background: rgb(0, 0, 255);
}
&:focus {
outline: none;
border-color: #fff;
}
&:active {
top: -4px;
}
}
button {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: none;
border-radius: var(--border-radius, 100em);
padding: 1em 2em;
font-weight: bolder;
&:hover {
cursor: pointer;
}
& + button {
margin-left: 1em;
}
}
body {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg);
}
:root {
--bg: #222;
--fg: #fff;
--border-radius: 9em;
font-size: 30px;
}
JavaScript:
const buttons = document.querySelectorAll(".prism");
let center = vec2.fromValues(innerWidth * 0.5, innerHeight * 0.5);
let mousePos = vec2.clone(center);
const minDist = 0;
const maxDist = 400;
const minOffset = 0;
const maxOffset = 25;
const rotationalScale = 1;
const TAU = Math.PI * 2;
function handleMouseMove(event) {
vec2.set(mousePos, event.clientX, event.clientY);
const angle = mousePos[0] * mousePos[1] * 0.00005;
// Get the distance of each button to the mouse
buttons.forEach(button => {
const rect = button.getBoundingClientRect();
if(isOnScreen(rect)) {
const buttonCenter = vec2.fromValues(
rect.left + (rect.right - rect.left) * 0.5,
rect.top + (rect.bottom - rect.top) * 0.5,
);
const distValue = vec2.dist(buttonCenter, mousePos);
const dist = clamp(distValue, minDist, maxDist);
const offset = map(maxDist - dist, minDist, maxDist, maxOffset, minOffset);
const intensity = map(maxDist - dist, 0, maxDist, 0, 1);
button.style.setProperty('--intensity', intensity * intensity);
const scaleFactor = map(maxDist - dist, 0, maxDist, 1.01, 1.05);
const offsetCorrection = map(maxDist - dist, 0, maxDist, 0, maxOffset * 0.5);
button.style.setProperty('--scaleFactor', scaleFactor);
const blur = map(dist, 0, maxDist, 5, 50);
button.style.setProperty('--blur', blur);
const redTopValue = Math.sin((angle + 0) * rotationalScale) * offset - offsetCorrection * -0.19;
const redLeftValue = Math.cos((angle + 0) * rotationalScale) * offset - offsetCorrection;
button.style.setProperty('--redTop', redTopValue);
button.style.setProperty('--redLeft', redLeftValue);
const greenTopValue = Math.sin((angle + 2) * rotationalScale) * offset - offsetCorrection * 0.5;
const greenLeftValue = Math.cos((angle + 2) * rotationalScale) * offset - offsetCorrection * 0.5;
button.style.setProperty('--greenTop', greenTopValue);
button.style.setProperty('--greenLeft', greenLeftValue);
const blueTopValue = Math.sin((angle + 4) * rotationalScale) * offset - offsetCorrection;
const blueLeftValue = Math.cos((angle + 4) * rotationalScale) * offset - offsetCorrection;
button.style.setProperty('--blueTop', blueTopValue);
button.style.setProperty('--blueLeft', blueLeftValue);
}
});
}
addEventListener("mousemove", throttled(handleMouseMove));
function handleResize() {
vec2.set(center, innerWidth * 0.5, innerHeight * 0.5);
}
addEventListener("resize", throttled(handleResize));
function init() {
buttons.forEach(button => {
button.innerHTML = `<span class='red'></span><span class='green'></span><span class='blue'></span>${button.innerHTML}`;
});
}
init();
// USEFUL FUNCTIONS -----------------
function throttled(fn) {
let didRequest = false;
return param => {
if (!didRequest) {
window.requestAnimationFrame(() => {
fn(param);
didRequest = false;
});
didRequest = true;
}
};
}
function isOnScreen(rect) {
return rect.top < window.innerHeight && rect.bottom > 0;
}
function clamp(value, min = 0, max = 1) {
return value <= min ? min : value >= max ? max : value;
}
function map(value, min1, max1, min2, max2) {
return (value - min1) * (max2 - min2) / (max1 - min1) + min2;
}