Новогодний снегопад с поздравлением для любого сайта, скрипт
Украсить ваш сайт снежинками и поздравлениями с новым годом:
Код вставляем перерд закрывающим
Пздравления и снег:
<script>
/*
* ============================================================
* ❄️ НОВОГОДНИЙ СНЕГОПАД С ПОЗДРАВЛЕНИЕМ (оптимизированный) ❄️
* ============================================================
* Автор: Ведерников Сергей
* ИИ: Дэп — https://1promtai.ru/
* ============================================================
* Оптимизация: снежинки рисуются 1 раз в offscreen-канвас,
* затем штампуются через drawImage(). Без shadowBlur.
* ============================================================
*/
(function() {
'use strict';
const CONFIG = {
back: {
count: 35, // было 50 — меньше нагрузка
minSize: 3,
maxSize: 6,
minSpeed: 0.2,
maxSpeed: 0.7,
swayAmplitude: 1.0,
rotationSpeed: 0.004,
opacityMin: 0.15,
opacityMax: 0.35,
colors: ['30,100,220', '50,120,230', '70,140,240']
},
front: {
count: 18, // было 25 — меньше нагрузка
minSize: 5,
maxSize: 10,
minSpeed: 0.6,
maxSpeed: 1.6,
swayAmplitude: 1.5,
rotationSpeed: 0.012,
opacityMin: 0.6,
opacityMax: 1.0,
colors: ['20,80,200', '30,100,220', '40,110,235', '60,130,245']
},
greetingInterval: 60000,
greetingDuration: 6000,
greetings: [
'🎄 С Новым годом! 🎄',
'❄️ Счастья и удачи в новом году! ❄️',
'🎁 Пусть всё загаданное сбудется! 🎁',
'✨ С Новым годом, дорогие друзья! ✨',
'⛄ Мира, тепла и волшебства! ⛄',
'🥂 За новый счастливый год! 🥂'
]
};
// ===== CANVAS =====
const canvas = document.createElement('canvas');
canvas.style.cssText =
'position:fixed;top:0;left:0;width:100%;height:100%;' +
'pointer-events:none;z-index:9998;';
canvas.setAttribute('aria-hidden', 'true');
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d', { alpha: true });
let W = window.innerWidth;
let H = window.innerHeight;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
const dpr = Math.min(window.devicePixelRatio || 1, 2); // ограничили 2 — на 3x дорого
canvas.width = W * dpr;
canvas.height = H * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
resize();
// resize — с дебаунсом, чтобы не пересчитывать при каждом пикселе
let resizeTimer;
window.addEventListener('resize', function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(resize, 200);
});
// ===== ПРЕДВАРИТЕЛЬНАЯ ОТРИСОВКА СНЕЖИНОК (кэш) =====
// Для каждой комбинации (размер + цвет) создаём offscreen-canvas
// один раз при старте, потом только drawImage.
const SPRITE_PADDING = 4; // запас под сглаживание
const spriteCache = {}; // ключ: color|size → { canvas, half }
function makeSnowflakeSprite(size, color) {
const key = color + '|' + size.toFixed(1);
if (spriteCache[key]) return spriteCache[key];
const half = Math.ceil(size + SPRITE_PADDING);
const full = half * 2;
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const spr = document.createElement('canvas');
spr.width = full * dpr;
spr.height = full * dpr;
const sctx = spr.getContext('2d');
sctx.setTransform(dpr, 0, 0, dpr, 0, 0);
sctx.translate(half, half);
const lineWidth = Math.max(0.6, size * 0.09);
const stroke = 'rgba(' + color + ',1)';
sctx.strokeStyle = stroke;
sctx.fillStyle = stroke;
sctx.lineWidth = lineWidth;
sctx.lineCap = 'round';
// 6 лучей с веточками
for (let i = 0; i < 6; i++) {
const angle = (Math.PI / 3) * i;
const dx = Math.cos(angle);
const dy = Math.sin(angle);
sctx.beginPath();
sctx.moveTo(0, 0);
sctx.lineTo(dx * size, dy * size);
sctx.stroke();
const branchPositions = [0.4, 0.7];
const branchLength = size * 0.35;
const branchAngle = Math.PI / 5;
for (let b = 0; b < branchPositions.length; b++) {
const pos = branchPositions[b];
const bx = dx * size * pos;
const by = dy * size * pos;
const bLen = branchLength * (b === 0 ? 1 : 0.7);
sctx.beginPath();
sctx.moveTo(bx, by);
sctx.lineTo(
bx + Math.cos(angle - branchAngle) * bLen,
by + Math.sin(angle - branchAngle) * bLen
);
sctx.stroke();
sctx.beginPath();
sctx.moveTo(bx, by);
sctx.lineTo(
bx + Math.cos(angle + branchAngle) * bLen,
by + Math.sin(angle + branchAngle) * bLen
);
sctx.stroke();
}
}
// центральная точка
sctx.beginPath();
sctx.arc(0, 0, lineWidth * 0.9, 0, Math.PI * 2);
sctx.fill();
const sprite = { canvas: spr, half: half };
spriteCache[key] = sprite;
return sprite;
}
// Округляем размеры до 0.5 — кэш не разрастается
function snapSize(s) {
return Math.round(s * 2) / 2;
}
// ===== СНЕЖИНКИ =====
function createFlake(layer, randomY) {
const rawSize = layer.minSize + Math.random() * (layer.maxSize - layer.minSize);
const size = snapSize(rawSize);
const color = layer.colors[(Math.random() * layer.colors.length) | 0];
const sprite = makeSnowflakeSprite(size, color);
return {
x: Math.random() * W,
y: randomY ? Math.random() * H : -20,
size: size,
sprite: sprite,
speed: layer.minSpeed + Math.random() * (layer.maxSpeed - layer.minSpeed),
sway: Math.random() * Math.PI * 2,
swaySpeed: 0.01 + Math.random() * 0.02,
rotation: Math.random() * Math.PI * 2,
rotationSpeed: (Math.random() - 0.5) * layer.rotationSpeed * 2,
opacity: layer.opacityMin + Math.random() * (layer.opacityMax - layer.opacityMin),
layer: layer
};
}
function createLayer(cfg) {
const arr = new Array(cfg.count);
for (let i = 0; i < cfg.count; i++) arr[i] = createFlake(cfg, true);
return arr;
}
const backFlakes = createLayer(CONFIG.back);
const frontFlakes = createLayer(CONFIG.front);
// ===== АНИМАЦИЯ =====
function updateFlakes(flakes, dt) {
for (let i = 0; i < flakes.length; i++) {
const f = flakes[i];
f.y += f.speed * dt;
f.sway += f.swaySpeed * dt;
f.rotation += f.rotationSpeed * dt;
const x = f.x + Math.sin(f.sway) * f.layer.swayAmplitude;
const spr = f.sprite;
if (f.opacity < 0.99) ctx.globalAlpha = f.opacity;
else ctx.globalAlpha = 1;
// drawImage с вращением
ctx.save();
ctx.translate(x, f.y);
if (f.rotation !== 0) ctx.rotate(f.rotation);
ctx.drawImage(spr.canvas, -spr.half, -spr.half, spr.half * 2, spr.half * 2);
ctx.restore();
if (f.y - f.size > H) {
flakes[i] = createFlake(f.layer, false);
}
}
ctx.globalAlpha = 1;
}
let lastTime = performance.now();
function animate(now) {
const dt = Math.min((now - lastTime) / 16.67, 3);
lastTime = now;
ctx.clearRect(0, 0, W, H);
updateFlakes(backFlakes, dt);
updateFlakes(frontFlakes, dt);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
// ===== ПЛАШКА ПОЗДРАВЛЕНИЯ =====
const greetingEl = document.createElement('div');
greetingEl.style.cssText = [
'position:fixed',
'top:50%',
'left:50%',
'transform:translate(-50%,-50%) scale(0.6)',
'background:linear-gradient(135deg,#0a1f4a,#143a80,#1e5bc8)',
'color:#fff',
'padding:28px 44px',
'border-radius:20px',
'font-family:-apple-system,"Segoe UI",Roboto,Arial,sans-serif',
'font-size:clamp(20px,4vw,42px)',
'font-weight:700',
'text-align:center',
'box-shadow:0 0 40px rgba(50,130,255,0.7),0 0 80px rgba(30,100,220,0.4)',
'border:1px solid rgba(120,180,255,0.5)',
'text-shadow:0 0 12px rgba(120,180,255,0.9)',
'pointer-events:none',
'opacity:0',
'transition:opacity 0.6s ease,transform 0.6s cubic-bezier(0.18,0.89,0.32,1.28)',
'z-index:9999',
'max-width:90vw',
'line-height:1.3'
].join(';');
greetingEl.setAttribute('aria-hidden', 'true');
document.body.appendChild(greetingEl);
let lastGreetingIndex = -1;
function showGreeting() {
let idx;
do {
idx = (Math.random() * CONFIG.greetings.length) | 0;
} while (idx === lastGreetingIndex && CONFIG.greetings.length > 1);
lastGreetingIndex = idx;
greetingEl.textContent = CONFIG.greetings[idx];
greetingEl.style.opacity = '1';
greetingEl.style.transform = 'translate(-50%,-50%) scale(1)';
setTimeout(function() {
greetingEl.style.opacity = '0';
greetingEl.style.transform = 'translate(-50%,-50%) scale(0.6)';
}, CONFIG.greetingDuration);
}
setTimeout(showGreeting, 2000);
setInterval(showGreeting, CONFIG.greetingInterval);
})();
</script>
