<button class="btn btn--1" data-dist="200" data-smooth="0.05"></button>
<button class="btn btn--2" data-dist="300" data-smooth="0.2"></button>
<button class="btn btn--3" data-dist="150" data-smooth="0.1"></button>
<button class="btn btn--4" data-dist="100" data-smooth="0.15"></button>
* {
box-sizing: border-box;
}
.btn {
display: flex;
justify-content: center;
align-items: center;
position: relative;
padding: 2em;
border-radius: 50%;
&::before {
content: "";
position: absolute;
}
}
.btn--1 {
margin-top: 10vh;
&::before {
content: "<1>";
}
}
.btn--2 {
margin-top: 25vh;
margin-left: 15vw;
&::before {
content: "<2>";
}
}
.btn--3 {
margin-top: 75vh;
margin-left: 50vw;
&::before {
content: "<3>";
}
}
.btn--4 {
margin-top: 120vh;
margin-left: 75vw;
&::before {
content: "<4>";
}
}
View Compiled
const lerp = (v0, v1, t) => v0 * (1 - t) + v1 * t;
class MagneticEl {
static {
this.addGlobalEvents();
this.beginRafLoop();
}
static instances = [];
static mouse = { x: 0, y: 0 };
constructor(selector, scroller = window) {
this.el = typeof selector === 'string' ? document.querySelector(selector) : selector;
this.scroller = scroller;
this.rect = this.calcPageRect();
this.position = { this.rect };
this.smooth = parseFloat(this.el.dataset.smooth) || 0.05;
this.dist = parseFloat(this.el.dataset.dist) || 200;
this.constructor.instances.push(this);
}
calcPageRect() {
const transform = this.el.style.transform;
this.el.style.transform = '';
const rect = this.el.getBoundingClientRect();
this.el.style.transform = transform;
return {
x: rect.x + (this.scroller === window ? window.scrollX : this.scroller.scrollLeft),
y: rect.y + (this.scroller === window ? window.scrollY : this.scroller.scrollTop),
w: rect.width,
h: rect.height,
};
}
getScreenRect() {
return {
x: this.rect.x - (this.scroller === window ? window.scrollX : this.scroller.scrollLeft),
y: this.rect.y - (this.scroller === window ? window.scrollY : this.scroller.scrollTop),
w: this.rect.w,
h: this.rect.h,
};
}
updateTransforms() {
const pos = this.getScreenRect();
const { x: mx, y: my } = this.constructor.mouse;
const dist = Math.hypot(pos.x - mx, pos.y - my);
if (dist < this.dist) {
this.position.x = lerp(this.position.x, mx - pos.w / 2, this.smooth);
this.position.y = lerp(this.position.y, my - pos.h / 2, this.smooth);
}
else {
this.position.x = lerp(this.position.x, pos.x, this.smooth);
this.position.y = lerp(this.position.y, pos.y, this.smooth);
}
const tx = this.position.x - pos.x;
const ty = this.position.y - pos.y;
this.el.style.transform = `translate(${tx}px, ${ty}px)`;
}
static addGlobalEvents() {
window.addEventListener('resize', (e) => {
this.instances.forEach(item => {
item.rect = item.calcPageRect();
item.position = { item.rect };
});
});
window.addEventListener('mousemove', (e) => {
this.mouse.x = e.clientX;
this.mouse.y = e.clientY;
});
}
static beginRafLoop() {
const loop = (now) => {
this.instances.forEach(item => item.updateTransforms());
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
}
}
document.querySelectorAll('.btn')
.forEach(el => new MagneticEl(el));
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.