Weather/script.js
Fovway 096dac61aa new file: README.md
modified:   index.html
	modified:   package-lock.json
	modified:   package.json
	modified:   script.js
	modified:   server.js
	deleted:    style.css
2025-10-07 22:02:35 +07:00

544 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// API ключ от WeatherAPI
const API_KEY = '485eff906f7d473b913104046250710';
const citySelect = document.getElementById('city');
const getWeatherBtn = document.getElementById('get-weather');
const timelapseContainer = document.getElementById('timelapse-container');
const currentWeatherDiv = document.getElementById('current-weather');
const currentTempEl = document.getElementById('current-temp');
const currentDescEl = document.getElementById('current-desc');
const currentHumidityEl = document.getElementById('current-humidity');
const currentWindEl = document.getElementById('current-wind');
// Глобальная переменная для Swiper
let weatherSwiper;
// Глобальные переменные для графиков
let temperatureChart = null;
let precipitationChart = null;
// Иконки погоды
const weatherIcons = {
'Sunny': 'sun',
'Clear': 'sun',
'Partly cloudy': 'cloud-sun',
'Cloudy': 'cloud',
'Overcast': 'cloud',
'Mist': 'cloud-fog',
'Fog': 'cloud-fog',
'Light rain': 'cloud-rain',
'Moderate rain': 'cloud-rain',
'Heavy rain': 'cloud-rain',
'Light snow': 'cloud-snow',
'Moderate snow': 'cloud-snow',
'Heavy snow': 'cloud-snow',
'Thunderstorm': 'cloud-lightning',
'Patchy rain possible': 'cloud-drizzle',
'Patchy snow possible': 'cloud-snow',
'Patchy sleet possible': 'cloud-snow',
'Patchy freezing drizzle possible': 'cloud-drizzle',
'Blowing snow': 'wind',
'Blizzard': 'wind',
'Freezing fog': 'cloud-fog',
'Patchy light drizzle': 'cloud-drizzle',
'Light drizzle': 'cloud-drizzle',
'Freezing drizzle': 'cloud-drizzle',
'Heavy freezing drizzle': 'cloud-drizzle',
'Patchy light rain': 'cloud-rain',
'Light rain shower': 'cloud-rain',
'Moderate or heavy rain shower': 'cloud-rain',
'Torrential rain shower': 'cloud-rain',
'Patchy light snow': 'cloud-snow',
'Light snow showers': 'cloud-snow',
'Moderate or heavy snow showers': 'cloud-snow',
'Patchy light rain with thunder': 'cloud-lightning',
'Moderate or heavy rain with thunder': 'cloud-lightning',
'Patchy light snow with thunder': 'cloud-lightning',
'Moderate or heavy snow with thunder': 'cloud-lightning'
};
// Функция для отображения текущей даты
function displayCurrentDate() {
const currentDateEl = document.getElementById('current-date');
const now = new Date();
const daysOfWeek = ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота'];
const months = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'];
const dayOfWeek = daysOfWeek[now.getDay()];
const day = now.getDate();
const month = months[now.getMonth()];
currentDateEl.textContent = `${dayOfWeek}, ${day} ${month}`;
}
// Функция для обновления заголовка страницы
function updatePageTitle() {
const selectedCity = citySelect.options[citySelect.selectedIndex].text;
document.title = `Погода в ${selectedCity}`;
}
// Автоматическая загрузка погоды при загрузке страницы
document.addEventListener('DOMContentLoaded', () => {
// Отображаем текущую дату
displayCurrentDate();
// Загружаем сохраненный город из localStorage
const savedCity = localStorage.getItem('selectedCity');
if (savedCity) {
citySelect.value = savedCity;
}
// Обновляем заголовок страницы
updatePageTitle();
// Ждем загрузки Swiper
if (typeof Swiper !== 'undefined') {
getWeatherBtn.click();
} else {
// Если Swiper еще не загрузился, ждем его
const checkSwiper = setInterval(() => {
if (typeof Swiper !== 'undefined') {
clearInterval(checkSwiper);
getWeatherBtn.click();
}
}, 100);
}
});
getWeatherBtn.addEventListener('click', async () => {
const city = citySelect.value;
// Сохраняем выбранный город в localStorage
localStorage.setItem('selectedCity', city);
const url = `https://api.weatherapi.com/v1/forecast.json?key=${API_KEY}&q=${city}&days=7&hourly=true&lang=ru`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Ошибка сети');
}
const data = await response.json();
// Отображаем текущую погоду
displayCurrentWeather(data);
// Пересоздаем иконки после обновления DOM
setTimeout(() => {
lucide.createIcons();
}, 100);
// Таймлапс на сегодня
displayTimelapse(data.forecast.forecastday[0].hour, data.location.localtime);
// Погода на неделю
displayWeeklyWeather(data.forecast.forecastday);
// Графики температуры и осадков
createTemperatureChart(data.forecast.forecastday[0].hour);
createPrecipitationChart(data.forecast.forecastday[0].hour);
// Обновляем заголовок страницы с выбранным городом
updatePageTitle();
} catch (error) {
console.error('Ошибка загрузки данных:', error);
alert('Не удалось загрузить данные о погоде. Проверьте API ключ.');
}
});
function displayCurrentWeather(data) {
const current = data.current;
currentTempEl.textContent = `${Math.round(current.temp_c)}°C`;
currentDescEl.textContent = current.condition.text;
currentHumidityEl.textContent = `${current.humidity}%`;
currentWindEl.textContent = `${current.wind_kph} км/ч`;
// Показываем блок текущей погоды
currentWeatherDiv.classList.remove('hidden');
currentWeatherDiv.classList.add('animate-fade-in');
}
function displayTimelapse(hourlyData, localtime) {
timelapseContainer.innerHTML = '';
const currentHour = new Date(localtime).getHours();
let currentSlideIndex = -1;
hourlyData.forEach((item, index) => {
const slide = document.createElement('div');
const hour = new Date(item.time).getHours();
const isCurrent = hour === currentHour;
if (isCurrent) currentSlideIndex = index;
slide.className = `swiper-slide timelapse-item bg-gradient-to-br from-blue-50 to-indigo-50 p-3 rounded-xl shadow-md border ${isCurrent ? 'border-blue-500 bg-gradient-to-br from-blue-100 to-indigo-100 ring-2 ring-blue-300' : 'border-gray-100'}`;
slide.style.width = '140px';
const iconName = weatherIcons[item.condition.text] || 'cloud';
slide.innerHTML = `
<div class="text-center">
<p class="text-xs font-medium ${isCurrent ? 'text-blue-700 font-semibold' : 'text-gray-600'} mb-1">${hour}:00 ${isCurrent ? '(сейчас)' : ''}</p>
<div class="flex justify-center mb-2">
<i data-lucide="${iconName}" class="w-6 h-6 ${isCurrent ? 'text-blue-600' : 'text-blue-500'}"></i>
</div>
<p class="text-lg font-bold ${isCurrent ? 'text-blue-700' : 'text-blue-600'} mb-1">${Math.round(item.temp_c)}°C</p>
<p class="text-xs ${isCurrent ? 'text-blue-600' : 'text-gray-600'} mb-1">${item.condition.text}</p>
<p class="text-xs text-gray-500">💧 ${item.precip_mm} мм</p>
<p class="text-xs text-gray-500">☔ ${item.chance_of_rain}%</p>
</div>
`;
timelapseContainer.appendChild(slide);
});
// Пересоздаем иконки
setTimeout(() => {
lucide.createIcons();
}, 100);
// Инициализируем Swiper если он еще не создан
setTimeout(() => {
if (!weatherSwiper) {
weatherSwiper = new Swiper('.swiper', {
slidesPerView: window.innerWidth < 768 ? 3 : 6,
spaceBetween: 12,
grabCursor: true,
freeMode: true,
mousewheel: {
enabled: true,
sensitivity: 1,
},
slidesOffsetBefore: 0,
slidesOffsetAfter: 0,
centeredSlides: false,
watchSlidesProgress: true
});
} else {
weatherSwiper.update();
weatherSwiper.updateSlides();
}
// Фокус на текущем времени города
if (currentSlideIndex !== -1) {
setTimeout(() => {
weatherSwiper.slideTo(currentSlideIndex, 800);
}, 200);
}
}, 50);
}
function displayWeeklyWeather(forecastData) {
const weeklyContainer = document.getElementById('weekly-container');
weeklyContainer.innerHTML = '';
const daysOfWeek = ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'];
forecastData.forEach((day, index) => {
const date = new Date(day.date);
const dayOfWeek = daysOfWeek[date.getDay()];
const dayNumber = date.getDate();
const month = date.toLocaleDateString('ru-RU', { month: 'short' });
const iconName = weatherIcons[day.day.condition.text] || 'cloud';
const isToday = index === 0;
const card = document.createElement('div');
card.className = `text-center p-4 rounded-xl border transition-all duration-200 hover:shadow-lg ${
isToday
? 'bg-blue-50 border-blue-300 ring-2 ring-blue-200'
: 'bg-gray-50 border-gray-200 hover:border-blue-300'
}`;
card.innerHTML = `
<div class="mb-2">
<p class="text-sm font-medium text-gray-600 ${isToday ? 'text-blue-700' : ''}">
${isToday ? 'Сегодня' : dayOfWeek}
</p>
<p class="text-xs text-gray-500">${dayNumber} ${month}</p>
</div>
<div class="flex justify-center mb-3">
<i data-lucide="${iconName}" class="w-8 h-8 ${isToday ? 'text-blue-600' : 'text-gray-600'}"></i>
</div>
<div class="mb-2">
<p class="text-lg font-bold ${isToday ? 'text-blue-700' : 'text-gray-800'}">
${Math.round(day.day.maxtemp_c)}°
</p>
<p class="text-sm text-gray-500">
${Math.round(day.day.mintemp_c)}°
</p>
</div>
<p class="text-xs ${isToday ? 'text-blue-600' : 'text-gray-600'}">
${day.day.condition.text}
</p>
`;
weeklyContainer.appendChild(card);
});
// Пересоздаем иконки для недельного прогноза
setTimeout(() => {
lucide.createIcons();
}, 100);
}
// Функция создания графика температуры
function createTemperatureChart(hourlyData) {
const ctx = document.getElementById('temperatureChart').getContext('2d');
// Подготавливаем данные
const labels = hourlyData.map(item => {
const hour = new Date(item.time).getHours();
return `${hour}:00`;
});
const temperatures = hourlyData.map(item => Math.round(item.temp_c));
// Уничтожаем предыдущий график если он существует
if (temperatureChart) {
temperatureChart.destroy();
}
// Создаем новый график
temperatureChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Температура (°C)',
data: temperatures,
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.4,
pointBackgroundColor: '#3b82f6',
pointBorderColor: '#ffffff',
pointBorderWidth: 2,
pointRadius: 4,
pointHoverRadius: 6,
pointHoverBackgroundColor: '#2563eb',
pointHoverBorderColor: '#ffffff',
pointHoverBorderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleColor: '#ffffff',
bodyColor: '#ffffff',
cornerRadius: 8,
displayColors: false,
callbacks: {
title: function(context) {
return `Время: ${context[0].label}`;
},
label: function(context) {
return `Температура: ${context.parsed.y}°C`;
}
}
}
},
scales: {
x: {
grid: {
display: false
},
ticks: {
color: '#6b7280',
font: {
size: 12
}
}
},
y: {
grid: {
color: 'rgba(0, 0, 0, 0.1)',
lineWidth: 1
},
ticks: {
color: '#6b7280',
font: {
size: 12
},
callback: function(value) {
return value + '°C';
}
}
}
},
animation: {
duration: 2000,
easing: 'easeInOutQuart'
},
interaction: {
intersect: false,
mode: 'index'
}
}
});
}
// Функция создания графика осадков
function createPrecipitationChart(hourlyData) {
const ctx = document.getElementById('precipitationChart').getContext('2d');
// Подготавливаем данные
const labels = hourlyData.map(item => {
const hour = new Date(item.time).getHours();
return `${hour}:00`;
});
const precipitations = hourlyData.map(item => item.precip_mm);
const chances = hourlyData.map(item => item.chance_of_rain);
// Уничтожаем предыдущий график если он существует
if (precipitationChart) {
precipitationChart.destroy();
}
// Создаем новый график
precipitationChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Осадки (мм)',
data: precipitations,
backgroundColor: 'rgba(59, 130, 246, 0.6)',
borderColor: '#3b82f6',
borderWidth: 1,
borderRadius: 4,
borderSkipped: false,
barThickness: 'flex',
maxBarThickness: 30
}, {
label: 'Вероятность (%)',
data: chances,
type: 'line',
borderColor: '#ef4444',
backgroundColor: 'rgba(239, 68, 68, 0.1)',
borderWidth: 2,
fill: false,
tension: 0.4,
pointBackgroundColor: '#ef4444',
pointBorderColor: '#ffffff',
pointBorderWidth: 2,
pointRadius: 3,
pointHoverRadius: 5,
pointHoverBackgroundColor: '#dc2626',
pointHoverBorderColor: '#ffffff',
pointHoverBorderWidth: 2,
yAxisID: 'y1'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleColor: '#ffffff',
bodyColor: '#ffffff',
cornerRadius: 8,
displayColors: true,
callbacks: {
title: function(context) {
return `Время: ${context[0].label}`;
},
label: function(context) {
if (context.datasetIndex === 0) {
return `Осадки: ${context.parsed.y} мм`;
} else {
return `Вероятность: ${context.parsed.y}%`;
}
}
}
}
},
scales: {
x: {
grid: {
display: false
},
ticks: {
color: '#6b7280',
font: {
size: 12
}
}
},
y: {
type: 'linear',
display: true,
position: 'left',
grid: {
color: 'rgba(0, 0, 0, 0.1)',
lineWidth: 1
},
ticks: {
color: '#6b7280',
font: {
size: 12
},
callback: function(value) {
return value + ' мм';
}
},
title: {
display: true,
text: 'Осадки (мм)',
color: '#3b82f6',
font: {
size: 14,
weight: 'bold'
}
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
grid: {
drawOnChartArea: false
},
ticks: {
color: '#ef4444',
font: {
size: 12
},
callback: function(value) {
return value + '%';
}
},
title: {
display: true,
text: 'Вероятность (%)',
color: '#ef4444',
font: {
size: 14,
weight: 'bold'
}
}
}
},
animation: {
duration: 2000,
easing: 'easeInOutQuart',
delay: function(context) {
return context.dataIndex * 100;
}
},
interaction: {
intersect: false,
mode: 'index'
}
}
});
}