159 lines
4.1 KiB
JavaScript
159 lines
4.1 KiB
JavaScript
class Animation {
|
||
cols = 16;
|
||
rows = 9;
|
||
|
||
circleSize = 30;
|
||
scaleMin = 0.15;
|
||
scaleMax = 2.5;
|
||
range = 175;
|
||
|
||
spacingHorizontal = 60;
|
||
spacingVertical = 80;
|
||
fill = getComputedStyle(document.documentElement).getPropertyValue('--circles');
|
||
|
||
svgMargin = 40;
|
||
svgHeight = 0;
|
||
svgWidth = 0;
|
||
|
||
circles = [];
|
||
screen = {
|
||
width: window.innerWidth,
|
||
height: window.innerHeight
|
||
};
|
||
mouse = {
|
||
x: window.innerWidth / 2,
|
||
y: window.innerHeight / 2
|
||
};
|
||
mouseStored = Object.assign({}, this.mouse);
|
||
|
||
constructor(selector) {
|
||
this.svg = document.querySelector(selector);
|
||
this.g = this.svg.querySelector("g");
|
||
|
||
this.svgWidth = this.cols * this.circleSize + 2 * this.svgMargin + (this.cols - 1) * this.spacingHorizontal;
|
||
this.svgHeight = this.rows + 2 * this.svgMargin + (this.rows - 1) * this.spacingVertical;
|
||
|
||
this.svg.setAttribute("viewBox", `0 0 ${this.svgWidth} ${this.svgHeight}`);
|
||
|
||
this.addEventListeners();
|
||
// Draw all the circles
|
||
this.draw();
|
||
// And animate them if the user is fine with it
|
||
window.matchMedia('(prefers-reduced-motion: no-preference)').matches ? this.animate() : null;
|
||
}
|
||
|
||
addEventListeners() {
|
||
let self = this;
|
||
// Don't redraw everything, only recalculate the centers of all arrows
|
||
window.addEventListener("resize", function() {
|
||
self.screen.width = window.innerWidth;
|
||
self.screen.height = window.innerHeight;
|
||
self.setCircleCenters();
|
||
});
|
||
}
|
||
|
||
draw() {
|
||
for(var i = 0; i < this.rows; i++) {
|
||
const offset = i % 2;
|
||
for(var j = 0; j < this.cols + 2 * offset; j++) {
|
||
// We're drawing the initial lines horizontally
|
||
let c = new Circle(
|
||
this.svgMargin + j*this.circleSize + j*this.spacingHorizontal - (offset * (this.spacingHorizontal + this.circleSize)/2),
|
||
this.svgMargin + i*this.spacingVertical,
|
||
this.circleSize,
|
||
this.fill
|
||
);
|
||
|
||
// Set a transform origin and add the HTML element to the SVG
|
||
const cElement = c.getElement();
|
||
gsap.set(cElement, {transformOrigin: "50% 50%"});
|
||
this.g.appendChild(cElement);
|
||
|
||
this.circles.push(c);
|
||
}
|
||
}
|
||
|
||
this.setCircleCenters();
|
||
}
|
||
|
||
clamp(num, min, max) {
|
||
return Math.min(Math.max(num, min), max);
|
||
}
|
||
|
||
setMouseCoords(event) {
|
||
this.mouse.x = event.clientX;
|
||
this.mouse.y = event.clientY;
|
||
}
|
||
|
||
setCircleCenters() {
|
||
this.circles.forEach(circle => {
|
||
// Get the center of the line
|
||
// Instead of mapping svg coords to the screen position, get the position on the actual screen using boundingRect
|
||
const boundingRect = circle.getElement().getBoundingClientRect();
|
||
circle.center = {
|
||
x: boundingRect.x + boundingRect.width / 2,
|
||
y: boundingRect.y + boundingRect.height / 2
|
||
};
|
||
})
|
||
}
|
||
|
||
animate() {
|
||
// Listen for the mouse movements
|
||
window.addEventListener("mousemove", this.setMouseCoords.bind(this));
|
||
// And use the ticker to update the rotation accordingly
|
||
gsap.ticker.add(this.setCircleScale.bind(this));
|
||
}
|
||
|
||
setCircleScale() {
|
||
// Don't do anything if the cursor's position is the same as in the previous tick
|
||
if (this.mouseStored.x === this.mouse.x && this.mouseStored.y === this.mouse.y) return;
|
||
|
||
this.circles.forEach(circle => {
|
||
// d = √[(x2 − x1)2 + (y2 − y1)2]
|
||
const distance = Math.sqrt((Math.pow((circle.center.x - this.mouse.x), 2) + Math.pow((circle.center.y - this.mouse.y), 2)));
|
||
const scale = this.range / (distance + this.circleSize);
|
||
|
||
gsap.to(
|
||
circle.getElement(),
|
||
{
|
||
scale: this.clamp(scale, this.scaleMin, this.scaleMax)
|
||
}
|
||
);
|
||
});
|
||
|
||
// Store the mouse position for the next tick
|
||
this.mouseStored.x = this.mouse.x;
|
||
this.mouseStored.y = this.mouse.y;
|
||
}
|
||
}
|
||
|
||
class Circle {
|
||
cx = 0;
|
||
cy = 0;
|
||
r = 0;
|
||
fill = "white";
|
||
element = null;
|
||
|
||
constructor(cx, cy, r, fill) {
|
||
this.cx = cx;
|
||
this.cy = cy;
|
||
this.r = r;
|
||
this.fill = fill;
|
||
this.element = document.createElementNS("http://www.w3.org/2000/svg", 'circle');
|
||
|
||
this.setElement();
|
||
}
|
||
|
||
getElement() {
|
||
return this.element;
|
||
}
|
||
|
||
setElement(cx, cy, r, fill) {
|
||
this.element.setAttribute("cx", cx ? cx : this.cx);
|
||
this.element.setAttribute("cy", cy ? cy : this.cy);
|
||
this.element.setAttribute("r", r ? r : this.r);
|
||
this.element.setAttribute("fill", fill ? fill : this.fill);
|
||
}
|
||
}
|
||
|
||
const animation = new Animation("#animation"); |