mobile adapt and seasons UPD
All checks were successful
Build and Deploy / build (push) Successful in 6s
Build and Deploy / deploy (push) Successful in 14s

This commit is contained in:
Nikita 2025-12-27 12:43:41 +01:00
commit 668edc8123

View file

@ -1,37 +1,70 @@
/** @license
DHTML Snowstorm! JavaScript-based snow for web pages
Исправленная версия с фрактальными снежинками
Версия 2.2 с адаптацией под мобильные и сезонным ограничением
-----------------------------------------------------------
Версия 2.1 с работающим снегопадом
Автоматически работает только с 1 декабря по 31 января
*/
var fractalSnowstorm = function(window, document) {
// Конфигурация
// Проверяем сезон - снег только с 1 декабря по 31 января
function isSnowSeason() {
const now = new Date();
const month = now.getMonth() + 1; // 1-12
const day = now.getDate();
// 1 декабря - 31 января
if (month === 12 || month === 1) {
return true;
}
// Если февраль, но сегодня 1-е февраля, еще можно оставить снег
if (month === 2 && day === 1) {
return true;
}
return false;
}
// Если не сезон для снега, не запускаемся
if (!isSnowSeason()) {
console.log('Снегопад активен только с 1 декабря по 31 января. Сейчас не сезон.');
return {
start: function() { console.log('Снегопад доступен только зимой!'); },
stop: function() {},
isSeason: function() { return false; }
};
}
// Конфигурация с адаптацией для мобильных
const isMobile = /iPhone|iPad|iPod|Android|BlackBerry|Windows Phone/i.test(navigator.userAgent);
const mobileMultiplier = isMobile ? 0.5 : 1; // В 2 раза меньше на мобильных
const config = {
autoStart: true,
excludeMobile: true,
flakesMax: 80,
flakesMaxActive: 60,
animationInterval: 33, // ~30 FPS
excludeMobile: false, // Теперь работаем и на мобильных
flakesMax: isMobile ? 40 : 80, // Меньше снежинок на мобильных
flakesMaxActive: isMobile ? 30 : 60,
animationInterval: 33,
useGPU: true,
snowColor: "#ffffff",
useTwinkleEffect: true,
useMeltEffect: true,
snowStick: true,
followMouse: false, // Упростим для начала
snowStick: !isMobile, // На мобильных не прилипает (для производительности)
followMouse: false,
freezeOnBlur: true,
flakeWidth: 24,
flakeHeight: 24,
vMaxX: 2,
vMaxY: 3, // Увеличим скорость падения
flakeWidth: Math.round(24 * mobileMultiplier),
flakeHeight: Math.round(24 * mobileMultiplier),
vMaxX: 2 * mobileMultiplier,
vMaxY: 3 * mobileMultiplier,
zIndex: 999999,
targetElement: null,
flakeBottom: null,
usePositionFixed: true,
windVariation: 0.3,
rotationSpeed: 0.02,
fractalTypes: 6,
complexity: 2
windVariation: 0.3 * mobileMultiplier,
rotationSpeed: 0.02 * mobileMultiplier,
fractalTypes: isMobile ? 4 : 6, // Проще фракталы на мобильных
complexity: isMobile ? 1 : 2, // Меньшая сложность на мобильных
isMobile: isMobile
};
// Глобальные переменные
@ -64,12 +97,12 @@ var fractalSnowstorm = function(window, document) {
constructor(type, x, y) {
this.type = type || Math.floor(Math.random() * config.fractalTypes);
this.x = x || Math.random() * screenWidth;
this.y = y || -Math.random() * 500; // Начинаем выше экрана
this.y = y || -Math.random() * 500;
this.vx = 0;
this.vy = 0;
this.rotation = Math.random() * Math.PI * 2;
this.rotationSpeed = (Math.random() - 0.5) * config.rotationSpeed;
this.size = 8 + Math.random() * 16;
this.size = (8 + Math.random() * 16) * mobileMultiplier;
this.branchCount = 6 + this.type * 2;
this.complexity = Math.max(1, Math.min(5, config.complexity));
this.opacity = 0.8 + Math.random() * 0.2;
@ -101,9 +134,15 @@ var fractalSnowstorm = function(window, document) {
top: 0;
width: ${config.flakeWidth}px;
height: ${config.flakeHeight}px;
${config.isMobile ? 'image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges;' : ''}
`;
this.ctx = this.canvas.getContext('2d');
this.ctx = this.canvas.getContext('2d', { alpha: true });
// Для мобильных: более простая обработка
if (config.isMobile) {
this.ctx.imageSmoothingEnabled = false;
}
this.element = document.createElement('div');
this.element.style.cssText = `
@ -116,6 +155,7 @@ var fractalSnowstorm = function(window, document) {
height: ${config.flakeHeight}px;
transform: translate(${this.x}px, ${this.y}px);
opacity: ${this.opacity};
${config.isMobile ? 'will-change: transform, opacity;' : ''}
`;
this.element.appendChild(this.canvas);
@ -139,6 +179,12 @@ var fractalSnowstorm = function(window, document) {
// Очищаем canvas
ctx.clearRect(0, 0, config.flakeWidth, config.flakeHeight);
// Для мобильных: более простые снежинки
if (config.isMobile && this.type > 2) {
this.drawSimpleFlake(ctx, centerX, centerY, currentRadius);
return;
}
// Сохраняем состояние
ctx.save();
ctx.translate(centerX, centerY);
@ -149,7 +195,77 @@ var fractalSnowstorm = function(window, document) {
const alpha = this.opacity * (this.melting ? (1 - this.meltProgress) : 1);
const color = hexToRgba(config.snowColor, alpha);
// Рисуем фрактальную снежинку
// Для мобильных: более простая отрисовка
if (config.isMobile) {
this.drawMobileFlake(ctx, currentRadius, color);
} else {
this.drawDesktopFlake(ctx, currentRadius, color);
}
ctx.restore();
}
drawSimpleFlake(ctx, centerX, centerY, radius) {
// Простая снежинка для мобильных
ctx.beginPath();
ctx.arc(centerX, centerY, radius * 0.8, 0, Math.PI * 2);
const alpha = this.opacity * (this.melting ? (1 - this.meltProgress) : 1);
const color = hexToRgba(config.snowColor, alpha);
ctx.fillStyle = color;
ctx.fill();
// Добавляем простые лучи
for (let i = 0; i < 6; i++) {
const angle = (i * Math.PI) / 3;
const x1 = centerX + Math.cos(angle) * radius * 0.5;
const y1 = centerY + Math.sin(angle) * radius * 0.5;
const x2 = centerX + Math.cos(angle) * radius * 1.5;
const y2 = centerY + Math.sin(angle) * radius * 1.5;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineWidth = 1;
ctx.strokeStyle = color;
ctx.stroke();
}
}
drawMobileFlake(ctx, radius, color) {
// Упрощенная версия для мобильных
const branches = 6;
for (let i = 0; i < branches; i++) {
const angle = (i * 2 * Math.PI) / branches;
// Основная ветвь
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(Math.cos(angle) * radius, Math.sin(angle) * radius);
ctx.lineWidth = 1;
ctx.strokeStyle = color;
ctx.stroke();
// Противоположная ветвь
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(Math.cos(angle + Math.PI) * radius, Math.sin(angle + Math.PI) * radius);
ctx.lineWidth = 1;
ctx.strokeStyle = color;
ctx.stroke();
}
// Центральный круг
ctx.beginPath();
ctx.arc(0, 0, radius * 0.15, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
}
drawDesktopFlake(ctx, radius, color) {
// Полная версия для десктопа
const branches = this.branchCount;
const maxDepth = this.complexity;
@ -181,27 +297,23 @@ var fractalSnowstorm = function(window, document) {
const angle = (i * 2 * Math.PI) / branches;
// Основная ветвь
drawBranch(0, 0, currentRadius, angle, 0, 1.5);
drawBranch(0, 0, radius, angle, 0, 1.5);
// Противоположная ветвь
drawBranch(0, 0, currentRadius, angle + Math.PI, 0, 1.5);
drawBranch(0, 0, radius, angle + Math.PI, 0, 1.5);
}
// Центральный круг
ctx.beginPath();
ctx.arc(0, 0, currentRadius * 0.15, 0, Math.PI * 2);
ctx.arc(0, 0, radius * 0.15, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
ctx.restore();
}
setVelocities() {
// Базовые скорости
this.vx = windX + (Math.random() - 0.5) * config.vMaxX;
this.vy = windY + Math.random() * config.vMaxY * 0.5 + 1.5; // Гарантируем падение вниз
this.vy = windY + Math.random() * config.vMaxY * 0.5 + 1.5;
// Добавляем случайные колебания
this.vx += (Math.random() - 0.5) * config.windVariation;
}
@ -212,8 +324,8 @@ var fractalSnowstorm = function(window, document) {
this.x += this.vx;
this.y += this.vy;
// Обновляем вращение
this.rotation += this.rotationSpeed;
// Обновляем вращение (медленнее на мобильных)
this.rotation += this.rotationSpeed * (config.isMobile ? 0.5 : 1);
// Обновляем мерцание
if (this.twinkle) {
@ -239,10 +351,11 @@ var fractalSnowstorm = function(window, document) {
}
}
// Случайное таяние в воздухе
// Случайное таяние в воздухе (реже на мобильных)
const meltChance = config.isMobile ? 0.0002 : 0.0005;
if (config.useMeltEffect &&
!this.melting &&
Math.random() < 0.0005 &&
Math.random() < meltChance &&
Date.now() - this.created > 5000) {
this.melting = true;
}
@ -256,7 +369,7 @@ var fractalSnowstorm = function(window, document) {
}
}
// Обновляем отображение
// Обновляем отображение (реже перерисовываем на мобильных)
this.updateDisplay();
return true;
}
@ -267,8 +380,11 @@ var fractalSnowstorm = function(window, document) {
this.element.style.transform = `translate(${this.x}px, ${this.y - scrollTop}px) rotate(${this.rotation}rad)`;
this.element.style.opacity = this.opacity * (this.melting ? (1 - this.meltProgress) : 1);
// Перерисовываем фрактал если нужно
if (this.melting || this.twinkle) {
// Перерисовываем фрактал только если нужно (реже на мобильных)
const shouldRedraw = this.melting ||
(this.twinkle && Math.random() < (config.isMobile ? 0.1 : 0.3));
if (shouldRedraw) {
this.drawFractal();
}
}
@ -279,10 +395,20 @@ var fractalSnowstorm = function(window, document) {
this.vy = 0;
this.rotationSpeed = 0;
// Плавное исчезновение
// На мобильных сразу убираем прилипшие снежинки
if (config.isMobile) {
setTimeout(() => {
if (this.element && this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
}
}, 2000);
return;
}
// На десктопе плавное исчезновение
setTimeout(() => {
if (this.element) {
let opacity = this.element.style.opacity || 1;
let opacity = parseFloat(this.element.style.opacity) || 1;
const fadeInterval = setInterval(() => {
opacity -= 0.02;
this.element.style.opacity = opacity;
@ -307,7 +433,11 @@ var fractalSnowstorm = function(window, document) {
this.active = true;
this.created = Date.now();
this.updateDisplay();
this.drawFractal();
// Перерисовываем только если не на мобильных (для производительности)
if (!config.isMobile) {
this.drawFractal();
}
}
destroy() {
@ -345,11 +475,10 @@ var fractalSnowstorm = function(window, document) {
function randomizeWind() {
windX = (Math.random() - 0.5) * config.vMaxX;
// Гарантируем небольшое движение по горизонтали
if (Math.abs(windX) < 0.3) {
windX = windX < 0 ? -0.3 : 0.3;
}
windY = 0; // Вертикальный ветер не нужен, снег падает сам
windY = 0;
}
function createSnowflake() {
@ -370,15 +499,14 @@ var fractalSnowstorm = function(window, document) {
function animate(currentTime) {
if (disabled) return;
// Вычисляем дельту времени для плавной анимации
const deltaTime = lastTime ? (currentTime - lastTime) / 1000 : 0.016;
lastTime = currentTime;
updateScreenSize();
updateScroll();
// Обновляем ветер (медленные изменения)
if (Math.random() < 0.01) {
// Медленнее меняем ветер на мобильных
if (Math.random() < (config.isMobile ? 0.005 : 0.01)) {
windX += (Math.random() - 0.5) * 0.2;
windX = Math.max(-config.vMaxX, Math.min(config.vMaxX, windX));
}
@ -395,15 +523,16 @@ var fractalSnowstorm = function(window, document) {
}
}
// Добавляем новые снежинки если нужно
// Добавляем новые снежинки если нужно (реже на мобильных)
const addChance = config.isMobile ? 0.2 : 0.3;
if (activeCount < config.flakesMaxActive &&
snowflakes.length < config.flakesMax &&
Math.random() < 0.3) {
Math.random() < addChance) {
createSnowflake();
}
// Удаляем неактивные снежинки (редко)
if (Math.random() < 0.01) {
// Удаляем неактивные снежинки (реже на мобильных)
if (Math.random() < (config.isMobile ? 0.005 : 0.01)) {
snowflakes = snowflakes.filter(flake =>
flake.element && flake.element.parentNode &&
(flake.active || parseFloat(flake.element.style.opacity) > 0)
@ -423,7 +552,6 @@ var fractalSnowstorm = function(window, document) {
function handleResize() {
updateScreenSize();
// При ресайзе немного меняем ветер
windX *= 0.9;
}
@ -445,29 +573,29 @@ var fractalSnowstorm = function(window, document) {
const publicAPI = {
start: function() {
if (active) {
console.log('Snowstorm уже запущен');
console.log('Снегопад уже запущен');
return;
}
console.log('Запуск снегопада...');
if (!isSnowSeason()) {
console.log('Снегопад доступен только с 1 декабря по 31 января');
return;
}
console.log('Запуск зимнего снегопада...', config.isMobile ? '(мобильная версия)' : '(десктоп версия)');
// Определяем целевой элемент
targetElement = config.targetElement || document.body;
// Проверяем, что целевой элемент существует
if (!targetElement) {
console.error('Не могу найти целевой элемент');
return;
}
// Инициализация
updateScreenSize();
randomizeWind();
// Создаем начальные снежинки
createInitialSnowflakes();
// Настраиваем обработчики событий
if (config.followMouse) {
window.addEventListener('mousemove', handleMouseMove);
}
@ -475,13 +603,12 @@ var fractalSnowstorm = function(window, document) {
window.addEventListener('scroll', handleScroll);
document.addEventListener('visibilitychange', handleVisibilityChange);
// Запускаем анимацию
active = true;
disabled = false;
lastTime = 0;
timer = requestAnimationFrame(animate);
console.log(`Снегопад запущен. Снежинок: ${snowflakes.length}, Экран: ${screenWidth}x${screenHeight}`);
console.log(`Снегопад запущен. Снежинок: ${snowflakes.length}, Размер: ${config.flakeWidth}px`);
},
stop: function() {
@ -489,28 +616,23 @@ var fractalSnowstorm = function(window, document) {
console.log('Остановка снегопада...');
// Останавливаем анимацию
cancelAnimationFrame(timer);
timer = null;
// Удаляем обработчики
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('resize', handleResize);
window.removeEventListener('scroll', handleScroll);
document.removeEventListener('visibilitychange', handleVisibilityChange);
// Удаляем все снежинки
snowflakes.forEach(flake => flake.destroy());
snowflakes = [];
active = false;
console.log('Снегопад остановлен');
},
pause: function() {
if (disabled) return;
disabled = true;
console.log('Снегопад приостановлен');
},
resume: function() {
@ -520,7 +642,6 @@ var fractalSnowstorm = function(window, document) {
if (active) {
timer = requestAnimationFrame(animate);
}
console.log('Снегопад возобновлен');
},
toggle: function() {
@ -534,7 +655,6 @@ var fractalSnowstorm = function(window, document) {
updateConfig: function(newConfig) {
Object.assign(config, newConfig);
if (active) {
// Пересоздаем снежинки при изменении конфига
this.stop();
setTimeout(() => this.start(), 100);
}
@ -549,22 +669,30 @@ var fractalSnowstorm = function(window, document) {
totalFlakes: snowflakes.length,
activeFlakes: snowflakes.filter(f => f.active).length,
screenSize: { width: screenWidth, height: screenHeight },
wind: { x: windX, y: windY }
wind: { x: windX, y: windY },
isMobile: config.isMobile,
isSeason: isSnowSeason()
};
},
isSeason: function() {
return isSnowSeason();
}
};
// Автозапуск
if (config.autoStart) {
// Автозапуск только в сезон
if (config.autoStart && isSnowSeason()) {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM загружен, запускаю снегопад...');
setTimeout(() => publicAPI.start(), 500);
console.log('DOM загружен, запускаю зимний снегопад...');
setTimeout(() => publicAPI.start(), 1000);
});
} else {
console.log('DOM уже загружен, запускаю снегопад...');
setTimeout(() => publicAPI.start(), 500);
console.log('Запускаю зимний снегопад...');
setTimeout(() => publicAPI.start(), 1000);
}
} else if (config.autoStart) {
console.log('Сейчас не сезон для снегопада (только с 1 декабря по 31 января)');
}
return publicAPI;
@ -573,14 +701,23 @@ var fractalSnowstorm = function(window, document) {
// Создаем глобальный объект для доступа
window.fractalSnowstorm = fractalSnowstorm;
// Простая версия для быстрого запуска (если сложная не работает)
window.simpleSnowfall = function() {
console.log('Запуск простого снегопада...');
// Альтернативная простая версия (тоже с сезонным ограничением)
window.createSimpleSnow = function() {
// Проверяем сезон
const now = new Date();
const month = now.getMonth() + 1;
if (!(month === 12 || month === 1 || (month === 2 && now.getDate() === 1))) {
console.log('Простой снегопад доступен только зимой');
return { stop: function() {} };
}
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
const sizeMultiplier = isMobile ? 0.4 : 1;
const config = {
flakes: 100,
speed: 3,
size: 20,
flakes: isMobile ? 50 : 150,
speed: 2 * sizeMultiplier,
size: 15 * sizeMultiplier,
color: '#ffffff',
zIndex: 999999
};
@ -595,21 +732,25 @@ window.simpleSnowfall = function() {
height: 100%;
pointer-events: none;
z-index: ${config.zIndex};
overflow: hidden;
`;
document.body.appendChild(container);
// Создаем простые снежинки
for (let i = 0; i < config.flakes; i++) {
const flake = document.createElement('div');
const flakeSize = config.size * (0.5 + Math.random() * 0.5);
const opacity = 0.3 + Math.random() * 0.5;
flake.style.cssText = `
position: absolute;
width: ${config.size}px;
height: ${config.size}px;
width: ${flakeSize}px;
height: ${flakeSize}px;
background: ${config.color};
border-radius: 50%;
opacity: ${0.3 + Math.random() * 0.7};
filter: blur(${Math.random() * 3}px);
top: -${config.size}px;
opacity: ${opacity};
filter: blur(${Math.random() * 2}px);
top: -${flakeSize}px;
left: ${Math.random() * 100}%;
`;
@ -620,11 +761,16 @@ window.simpleSnowfall = function() {
x: Math.random() * window.innerWidth,
y: Math.random() * -window.innerHeight,
speed: config.speed * (0.5 + Math.random()),
sway: (Math.random() - 0.5) * 2
sway: (Math.random() - 0.5) * 1.5 * sizeMultiplier,
size: flakeSize,
rotation: Math.random() * Math.PI * 2,
rotationSpeed: (Math.random() - 0.5) * 0.02
});
}
// Анимация
let animationId = null;
function animate() {
const width = window.innerWidth;
const height = window.innerHeight;
@ -632,31 +778,49 @@ window.simpleSnowfall = function() {
flakes.forEach(flake => {
flake.y += flake.speed;
flake.x += flake.sway * Math.sin(flake.y * 0.01);
flake.rotation += flake.rotationSpeed;
// Если снежинка ушла за нижний край, возвращаем ее наверх
// Если снежинка ушла за нижний край
if (flake.y > height) {
flake.y = -config.size;
flake.y = -flake.size;
flake.x = Math.random() * width;
}
// Если снежинка ушла за боковые края, возвращаем с другой стороны
if (flake.x > width) flake.x = 0;
if (flake.x < 0) flake.x = width;
// Если снежинка ушла за боковые края
if (flake.x > width + flake.size) flake.x = -flake.size;
if (flake.x < -flake.size) flake.x = width + flake.size;
flake.element.style.transform = `translate(${flake.x}px, ${flake.y}px)`;
flake.element.style.transform = `
translate(${flake.x}px, ${flake.y}px)
rotate(${flake.rotation}rad)
`;
// Легкое мерцание
if (Math.random() < 0.01) {
flake.element.style.opacity = 0.3 + Math.random() * 0.4;
}
});
requestAnimationFrame(animate);
animationId = requestAnimationFrame(animate);
}
animate();
return {
stop: function() {
container.remove();
if (animationId) {
cancelAnimationFrame(animationId);
}
if (container.parentNode) {
container.parentNode.removeChild(container);
}
}
};
};
// Если фрактальный снег не работает, можно вызвать простой вариант:
// window.simpleSnowfall();
// Автоматический запуск простой версии если фрактальная не доступна
if (window.fractalSnowstorm && window.fractalSnowstorm.isSeason && window.fractalSnowstorm.isSeason()) {
console.log('Зимний сезон - снегопад будет запущен автоматически');
} else {
console.log('Сейчас не зимний сезон для снегопада');
}