С наступающим 2027годом
Скрипт создаёт новогоднюю анимацию в верхней части сайта. Он рисует широкую полосу, которая накрывает страницу сверху и не мешает с ней работать — по ней нельзя кликнуть, и она не перекрывает кнопки или ссылки.
Эта полоса выглядит как ночное небо: сверху оно тёмное и плотное, а ближе к низу становится всё прозрачнее, так что сквозь него спокойно просматривается содержимое сайта. По небу мерцают звёзды — одни горят ярче, другие тише, и это создаёт ощущение живого ночного неба. Между звёздами медленно падает снег: снежинки опускаются вниз, слегка покачиваясь из стороны в сторону, а когда долетают до края, снова появляются наверху, чтобы падать заново.
По этому небу летит упряжка Деда Мороза. Впереди бегут олени — их ноги двигаются, как будто они действительно скачут по воздуху. Позади оленей едет сам Дед Мороз в санях. Сани украшены мигающей гирляндой, которая переливается разными цветами. От рук Деда Мороза к оленям тянутся поводья, и они тоже слегка колышутся в такт движению. Вся упряжка плавно покачивается вверх-вниз, как будто летит по волнам.
Внизу, поверх всей этой картины, постепенно проявляется праздничная надпись с поздравлением — сначала она едва заметна, потом становится всё ярче, пока не загорится полностью золотисто-белым сиянием. Вся анимация длится недолго, а потом исчезает сама: полоса убирается со страницы, и сайт остаётся таким, каким был до неё.
Внутри скрипт устроен так. Сначала он создаёт холст — специальную область, на которой можно рисовать. Этот холст прикрепляется к самому верху страницы и растягивается во всю ширину. Он настроен так, чтобы не перехватывать нажатия мыши и не мешать посетителю пользоваться сайтом. Дальше скрипт каждый кадр заново рисует всю картину: сначала фон, потом звёзды, потом снег, потом упряжку, потом надпись.
Делается это очень часто — много раз в секунду, — чтобы движение выглядело плавным. Упряжка движется слева направо, постепенно пересекая весь экран: сначала появляется у левого края, потом пролетает через середину и уходит за правый край. Когда проходит заданное время, скрипт убирает холст со страницы, и от анимации не остаётся следа. Всё сделано так, чтобы работало легко: объектов на экране немного, фигуры простые, а на телефонах и планшетах картинка автоматически становится меньше, чтобы ничего не тормозило.
Скрипт также защищён от повторного запуска — если его случайно подключить дважды, второй раз он уже не сработает и не будет рисовать две упряжки одновременно.
(function() {
'use strict';
if (window.__santaRunning) return;
window.__santaRunning = true;
function init() {
var canvas = document.createElement('canvas');
var W = window.innerWidth;
var H = 200;
var isMobile = W < 700;
var S = isMobile ? 0.5 : 0.7;
canvas.width = W;
canvas.height = H;
canvas.style.cssText = [
'position:fixed',
'top:0',
'left:0',
'width:100%',
'height:200px',
'pointer-events:none',
'z-index:9998',
'display:block'
].join(';');
canvas.setAttribute('aria-hidden', 'true');
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
var dpr = Math.min(1.5, window.devicePixelRatio || 1);
canvas.width = W * dpr;
canvas.height = H * dpr;
ctx.scale(dpr, dpr);
var start = performance.now();
var TOTAL = 12000;
// ---------- Звёзды ----------
var stars = [];
var starCount = isMobile ? 20 : 35;
for (var i = 0; i < starCount; i++) {
stars.push({
x: Math.random() * W,
y: Math.random() * H * 0.6,
r: Math.random() * 1.1 + 0.3,
phase: Math.random() * Math.PI * 2
});
}
// ---------- Снежинки ----------
var snowflakes = [];
var snowCount = isMobile ? 30 : 50;
for (var j = 0; j < snowCount; j++) {
snowflakes.push({
x: Math.random() * W,
y: Math.random() * H,
r: Math.random() * 1.6 + 0.6,
vy: 0.5 + Math.random() * 0.9,
vx: (Math.random() - 0.5) * 0.4,
phase: Math.random() * Math.PI * 2
});
}
function drawSky() {
var sky = ctx.createLinearGradient(0, 0, 0, H);
sky.addColorStop(0.0, 'rgba(5, 11, 31, 0.85)');
sky.addColorStop(0.5, 'rgba(13, 26, 61, 0.55)');
sky.addColorStop(1.0, 'rgba(28, 47, 94, 0.15)');
ctx.fillStyle = sky;
ctx.fillRect(0, 0, W, H);
}
function drawStars(now) {
ctx.fillStyle = '#fff';
for (var i = 0; i < stars.length; i++) {
var s = stars[i];
var depth = 1 - s.y / (H * 0.6);
ctx.globalAlpha = (0.25 + 0.6 * Math.abs(Math.sin(now / 1000 + s.phase))) * (0.4 + 0.6 * depth);
ctx.beginPath();
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
}
function drawSnow(now) {
ctx.fillStyle = '#fff';
for (var i = 0; i < snowflakes.length; i++) { var f = snowflakes[i]; f.y += f.vy; f.x += f.vx + Math.sin(now / 900 + f.phase) * 0.3; if (f.y > H + 4) { f.y = -4; f.x = Math.random() * W; }
if (f.x < -4) f.x = W + 4; if (f.x > W + 4) f.x = -4;
ctx.globalAlpha = 0.5 + 0.4 * (1 - f.y / H);
ctx.beginPath();
ctx.arc(f.x, f.y, f.r, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
}
// ---------- Олень (смотрит вправо, бежит вправо) ----------
function drawDeer(x, y, scale, legPhase) {
ctx.save();
ctx.translate(x, y);
ctx.scale(scale, scale);
var legSwing = Math.sin(legPhase) * 6;
// Ноги
ctx.strokeStyle = '#5a3a1e';
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(-14, 6); ctx.lineTo(-18 + legSwing, 30);
ctx.moveTo(-6, 8); ctx.lineTo(-4 - legSwing, 30);
ctx.moveTo(12, 8); ctx.lineTo(16 + legSwing, 30);
ctx.moveTo(20, 6); ctx.lineTo(24 - legSwing, 30);
ctx.stroke();
// Тело
ctx.beginPath();
ctx.fillStyle = '#8b5a2b';
ctx.ellipse(0, 0, 28, 13, 0, 0, Math.PI * 2);
ctx.fill();
// Шея
ctx.beginPath();
ctx.moveTo(20, -6);
ctx.quadraticCurveTo(34, -14, 38, -22);
ctx.lineTo(46, -18);
ctx.quadraticCurveTo(38, -6, 26, 4);
ctx.closePath();
ctx.fill();
// Голова
ctx.beginPath();
ctx.fillStyle = '#9c6633';
ctx.ellipse(46, -24, 11, 8, -0.35, 0, Math.PI * 2);
ctx.fill();
// Морда
ctx.beginPath();
ctx.fillStyle = '#7a4f26';
ctx.ellipse(55, -26, 6, 4.5, -0.25, 0, Math.PI * 2);
ctx.fill();
// Нос
ctx.beginPath();
ctx.fillStyle = '#3a2412';
ctx.arc(59, -27, 2, 0, Math.PI * 2);
ctx.fill();
// Глаз
ctx.beginPath();
ctx.fillStyle = '#1b1008';
ctx.arc(49, -26, 1.6, 0, Math.PI * 2);
ctx.fill();
// Рога
ctx.strokeStyle = '#d9c9a3';
ctx.lineWidth = 2.2;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(43, -31);
ctx.bezierCurveTo(38, -46, 30, -50, 24, -44);
ctx.moveTo(47, -31);
ctx.bezierCurveTo(52, -46, 60, -50, 66, -44);
ctx.moveTo(30, -47); ctx.lineTo(26, -54);
ctx.moveTo(60, -47); ctx.lineTo(64, -54);
ctx.stroke();
// Уздечка
ctx.strokeStyle = '#c9a44c';
ctx.lineWidth = 1.4;
ctx.beginPath();
ctx.moveTo(52, -28);
ctx.quadraticCurveTo(50, -20, 44, -16);
ctx.stroke();
// Хомут
ctx.strokeStyle = '#a67c2e';
ctx.lineWidth = 2.6;
ctx.beginPath();
ctx.moveTo(24, -12);
ctx.quadraticCurveTo(30, -18, 34, -10);
ctx.stroke();
ctx.restore();
}
// ---------- Сани ----------
function drawSleigh(x, y, scale, now) {
ctx.save();
ctx.translate(x, y);
ctx.scale(scale, scale);
ctx.strokeStyle = '#c0c8d0';
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(-6, 22);
ctx.quadraticCurveTo(-18, 22, -22, 10);
ctx.moveTo(-2, 22);
ctx.lineTo(58, 22);
ctx.quadraticCurveTo(70, 22, 74, 10);
ctx.stroke();
ctx.beginPath();
ctx.fillStyle = '#b22222';
ctx.moveTo(0, 6);
ctx.quadraticCurveTo(28, -8, 58, 6);
ctx.lineTo(52, 20);
ctx.quadraticCurveTo(28, 26, 4, 20);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.fillStyle = '#8b1a1a';
ctx.moveTo(52, 6);
ctx.quadraticCurveTo(66, -14, 60, -26);
ctx.quadraticCurveTo(50, -20, 46, -2);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.fillStyle = '#8b5a2b';
ctx.ellipse(34, -4, 12, 10, 0, 0, Math.PI * 2);
ctx.fill();
// Гирлянда
for (var i = 0; i < 8; i++) {
var p = i / 7;
var lx = 2 + p * 54;
var ly = 18 + Math.sin(p * Math.PI) * -4;
var hue = (i * 45 + now / 15) % 360;
var alpha = 0.5 + 0.5 * Math.abs(Math.sin(now / 260 + i));
ctx.beginPath();
ctx.fillStyle = 'hsla(' + hue + ', 100%, 65%, ' + alpha.toFixed(2) + ')';
ctx.arc(lx, ly, 1.7, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
// ---------- Дед Мороз ----------
function drawSanta(x, y, scale, wave) {
ctx.save();
ctx.translate(x, y);
ctx.scale(scale, scale);
ctx.beginPath();
ctx.fillStyle = '#d62828';
ctx.moveTo(-14, -8);
ctx.quadraticCurveTo(2, -18, 18, -8);
ctx.lineTo(14, 26);
ctx.quadraticCurveTo(2, 32, -10, 26);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.fillStyle = '#fff';
ctx.moveTo(-10, 24);
ctx.quadraticCurveTo(2, 32, 14, 24);
ctx.quadraticCurveTo(4, 30, -10, 24);
ctx.fill();
var swing = Math.sin(wave) * 2;
ctx.beginPath();
ctx.fillStyle = '#1d3557';
ctx.arc(-6, swing, 4.5, 0, Math.PI * 2);
ctx.arc(22, -4 - swing, 4.5, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = '#fff';
ctx.moveTo(-10, -18);
ctx.quadraticCurveTo(2, 10, 14, -18);
ctx.quadraticCurveTo(2, -8, -10, -18);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = '#f7c9a3';
ctx.ellipse(2, -26, 10, 9, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#1b1008';
ctx.beginPath();
ctx.arc(-2, -28, 1.4, 0, Math.PI * 2);
ctx.arc(6, -28, 1.4, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = '#e07a5f';
ctx.arc(2, -24, 2, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = '#d62828';
ctx.moveTo(-8, -34);
ctx.quadraticCurveTo(2, -50, 12, -34);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.fillStyle = '#fff';
ctx.ellipse(2, -34, 11, 3.5, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(2, -48, 4, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
// ---------- Поводья: от Деда Мороза (слева) к оленям (справа) ----------
function drawReins(santaX, santaY, d1x, d1y, d2x, d2y, wave) {
var swing = Math.sin(wave) * 2;
ctx.strokeStyle = '#c9a44c';
ctx.lineWidth = 1.5;
// От правой руки Деда Мороза к первому оленю
ctx.beginPath();
ctx.moveTo(santaX + 22, santaY - 4 - swing);
ctx.quadraticCurveTo(
(santaX + d1x) / 2,
santaY + 14 + Math.sin(wave / 2) * 2,
d1x - 26, d1y - 12
);
ctx.stroke();
// От первого оленя ко второму
ctx.beginPath();
ctx.moveTo(d1x + 24, d1y - 10);
ctx.quadraticCurveTo(
(d1x + d2x) / 2,
d1y + 10 + Math.sin(wave / 2 + 0.6) * 2,
d2x - 26, d2y - 12
);
ctx.stroke();
}
// ---------- Надпись ----------
function drawText(progress) {
var alpha = Math.min(1, Math.max(0, (progress - 0.05) / 0.15));
if (alpha <= 0) return; var fontSize = Math.max(18, Math.min(34, W / 22)); var cx = W / 2; var cy = H * 0.82; ctx.save(); ctx.globalAlpha = alpha; ctx.font = 'bold ' + fontSize + 'px "Segoe UI", Arial, sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.lineWidth = 5; ctx.strokeStyle = 'rgba(0,0,0,0.65)'; ctx.strokeText('С Новым 2027 годом!', cx, cy); var grad = ctx.createLinearGradient(cx - 200, 0, cx + 200, 0); grad.addColorStop(0, '#fff2b0'); grad.addColorStop(0.5, '#ffffff'); grad.addColorStop(1, '#ffd166'); ctx.fillStyle = grad; ctx.fillText('С Новым 2027 годом!', cx, cy); ctx.restore(); } function animate(now) { var t = now - start; if (t > TOTAL) {
if (canvas.parentNode) canvas.parentNode.removeChild(canvas);
window.__santaRunning = false;
return;
}
var progress = t / TOTAL;
ctx.clearRect(0, 0, W, H);
drawSky();
drawStars(now);
var gs = 0.55 * S;
// Едем слева направо. Начинаем за левым краем и уезжаем за правый.
var travel = -500 * S + (W + 1000 * S) * progress;
var flyY = H * 0.40 + Math.sin(t / 700) * (10 * S);
// Сани и Дед Мороз — ПОЗАДИ (слева)
var santaX = travel;
var santaY = flyY;
var sleighX = santaX - 10 * S;
var sleighY = santaY + 14 * S;
// Олени — ВПЕРЕДИ (справа), везут сани
var deer1X = santaX + 110 * S;
var deer1Y = santaY + 18 * S + Math.sin(t / 250) * 2;
var deer2X = deer1X + 100 * S;
var deer2Y = santaY + 22 * S + Math.sin(t / 250 + 1.2) * 2;
var legPhase = t / 80;
// Порядок отрисовки: дальние → ближние
// 1) Дальний олень (второй, самый правый, впереди всех)
drawDeer(deer2X, deer2Y, gs * 0.95, legPhase + 1.1);
// 2) Ближний олень (первый)
drawDeer(deer1X, deer1Y, gs, legPhase);
// 3) Поводья от Деда Мороза к оленям
drawReins(santaX, santaY, deer1X, deer1Y, deer2X, deer2Y, t / 120);
// 4) Сани
drawSleigh(sleighX, sleighY, gs, now);
// 5) Дед Мороз поверх саней — он сидит в них
drawSanta(santaX, santaY, gs, t / 120);
drawText(progress);
drawSnow(now);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
var resizeTimer;
window.addEventListener('resize', function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
if (canvas.parentNode) canvas.parentNode.removeChild(canvas);
window.__santaRunning = false;
init();
}, 300);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
Добавить комментарий