Может быть кому пригодится тут сделал сброс стилей по завершению анимации и разные размеры кругов в зависимости от размера экрана
const container = document.getElementById("animation-container");
const createElement = (parent_, tag) => {
const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
parent_.appendChild(element);
return element;
};
const svg = createElement(container, "svg");
const g = createElement(svg, "g");
g.setAttribute("transform", "");
const clipPath = createElement(g, "clipPath");
clipPath.setAttribute("id", "clipPath");
const path = createElement(clipPath, "path");
path.setAttribute("clip-rule", "evenodd");
const setPath = (w, h, r, holes) => {
const shapes = [`M 0 0 H ${w} V ${h} H -${w} z`];
for (const [x, y] of holes) {
const hole = `
M ${x - r} ${y}
a ${r} ${r} 0 1 0 ${2 * r} 0
a ${r} ${r} 0 1 0 ${-2 * r} 0
z
`;
shapes.push(hole);
}
path.setAttribute("d", shapes.join(" "));
};
const rect = createElement(g, "rect");
rect.setAttribute("width", "100%");
rect.setAttribute("height", "100%");
rect.setAttribute("clip-path", "url(#clipPath)");
rect.setAttribute("fill", "#c9fd5b");
const shuffle = (a) => {
for (let i = 1; i < a.length; ++i) {
const j = Math.floor(Math.random() * (i + 1)); // random in [0, i]
[a[i], a[j]] = [a[j], a[i]]; // swap a[i] <-> a[j]
}
};
const slide = (next) => {
const start = performance.now() / 1000;
const step = () => {
const t = performance.now() / 1000 - start;
const w = svg.clientWidth;
const h = Math.min(2000 * t, svg.clientHeight);
setPath(w, h, 20, []);
window.requestAnimationFrame(h < svg.clientHeight ? step : next);
};
window.requestAnimationFrame(step);
};
const holes = (next) => {
const start = performance.now() / 1000;
let r;
if (window.innerWidth < 600) {
r = 40;
} else if (window.innerWidth < 1200) {
r = 60;
} else if (window.innerWidth < 1920) {
r = 90;
} else {
r = 100;
}
const d = 2 * r;
const w = Math.floor(svg.clientWidth / d);
const h = Math.floor(svg.clientHeight / d);
const points = [];
for (let i = 0; i < h; ++i) {
for (let j = 0; j < w; ++j) {
points.push([r + j * d, r + i * d]);
}
}
shuffle(points);
points.splice(Math.floor((points.length * 3) / 4));
const step = () => {
const t = performance.now() / 1000 - start;
const n = Math.min(points.length, Math.floor(20 * t));
setPath(svg.clientWidth, svg.clientHeight, r, points.slice(0, n));
window.requestAnimationFrame(n < points.length ? step : next(r, points[0]));
};
window.requestAnimationFrame(step);
};
const dist = (p1, p2) => {
const dx = p1[0] - p2[0];
const dy = p1[1] - p2[1];
return Math.sqrt(dx * dx + dy * dy);
};
const zoom = (radius, center) => () => {
const start = performance.now() / 1000;
const w = svg.clientWidth;
const h = svg.clientHeight;
const corners = [[0, 0], [w, 0], [0, h], [w, h]];
const maxDist = Math.max(...corners.map((p) => dist(center, p)));
const maxScale = maxDist / radius;
const step = () => {
const t = performance.now() / 1000 - start;
const s = Math.exp(t * t);
const tx = (1 - s) * center[0];
const ty = (1 - s) * center[1];
g.style.transform = `translate(${tx}px, ${ty}px) scale(${s})`;
if (s < maxScale) {
window.requestAnimationFrame(step);
} else {
svg.style.position = "absolute";
// удаление стиля через 5 секунд после окончания анимации
setTimeout(() => {
g.removeAttribute("style");
}, 300);
setTimeout(() => {
g.removeAttribute("style");
path.setAttribute("d", ""); }, 300);
}
};
window.requestAnimationFrame(step);
};
let isAnimationRunning = false;
let secondClick = false;
const resetAnimation = () => {
clearAnimation();
resetTransform();
isAnimationRunning = false;
secondClick = false;
g.setAttribute("transform", "");
rect.setAttribute("fill", "#c9fd5b");
};
const clearAnimation = () => {
while (svg.lastChild) {
svg.removeChild(svg.lastChild);
}
resetTransform();
svg.style.position = "relative";
};
const toggleAnimation = () => {
if (!isAnimationRunning) {
svg.style.position = "fixed";
svg.style.left = 0;
svg.style.top = 0;
svg.style.width = "100%";
svg.style.height = "100%";
slide(() => holes(zoom))(null, [svg.clientWidth / 2, svg.clientHeight / 2]);
isAnimationRunning = true;
} else if (!secondClick) {
resetAnimation();
secondClick = true;
} else {
resetAnimation();
svg.style.position = "fixed";
svg.style.left = 0;
svg.style.top = 0;
svg.style.width = "100%";
svg.style.height = "100%";
slide(() => holes(zoom))(null, [svg.clientWidth / 2, svg.clientHeight / 2]);
secondClick = false;
}
};
svg.addEventListener("animationend", resetAnimation);
document.getElementById("switch").addEventListener("click", toggleAnimation);