<div class="container">
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
body {
min-height: 100vh;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
background: #f5f5f5;
overflow: hidden;
}
.container {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
}
.box {
width: 100px;
height: 100px;
background: var(--color, #eeeeee);
}
.box:nth-child(1) {
--color: #ED1C24;
}
.box:nth-child(2) {
--color: #22B14C;
}
.box:nth-child(3) {
--color: #00A2E8;
}
.box:nth-child(4) {
--color: #FFC90E;
}
const boxes = document.querySelectorAll('.box');
const items = [];
const Mode = Object.freeze({
Idle: 1 << 0,
Move: 1 << 1,
Return: 1 << 2
});
const update = () => {
for (const item of items) {
if (item.mode !== Mode.Idle) {
item.position.x += item.velocity.x;
item.position.y += item.velocity.y;
item.entity.style.setProperty('transform', `translate(${item.position.x}px, ${item.position.y}px)`);
if (item.position.x !== 0 || item.position.y !== 0) {
if (item.mode === Mode.Move) {
item.velocity.x *= 0.975;
item.velocity.y *= 0.975;
if (Math.abs(item.velocity.x) < 0.1 && Math.abs(item.velocity.y) < 0.1) {
[item.mouse.current.x, item.mouse.current.y] = [null, null];
[item.mouse.previous.x, item.mouse.previous.y] = [null, null];
item.mode = Mode.Return;
}
} else {
[item.velocity.x, item.velocity.y] = [item.position.x / -10, item.position.y / -10];
if (Math.abs(item.velocity.x) < 0.1 && Math.abs(item.velocity.y) < 0.1) {
[item.position.x, item.position.y] = [0, 0];
[item.velocity.x, item.velocity.y] = [0, 0];
}
}
} else {
item.mode = Mode.Idle;
}
}
}
requestAnimationFrame(update);
};
const init = () => {
for (const box of boxes) {
const item = {
entity: box,
position: {
x: 0,
y: 0
},
velocity: {
x: 0,
y: 0
},
mouse: {
current: {
x: null,
y: null
},
previous: {
x: null,
y: null
}
},
mode: Mode.Idle
};
box.addEventListener('mousemove', event => {
[item.mouse.current.x, item.mouse.current.y] = [event.offsetX, event.offsetY];
if (item.mouse.previous.x !== null && item.mouse.previous.y !== null) {
item.velocity.x += (item.mouse.current.x - item.mouse.previous.x) / 25;
item.velocity.y += (item.mouse.current.y - item.mouse.previous.y) / 25;
}
[item.mouse.previous.x, item.mouse.previous.y] = [item.mouse.current.x, item.mouse.current.y];
item.mode = Mode.Move;
});
box.addEventListener('mouseleave', event => {
[item.mouse.current.x, item.mouse.current.y] = [null, null];
[item.mouse.previous.x, item.mouse.previous.y] = [null, null];
});
items.push(item);
}
requestAnimationFrame(update);
};
window.addEventListener('DOMContentLoaded', init);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.