Персональный сайт Дмитрия Журавлева

Связь: dmitriyzhuravlev@yandex.ru

Простенькое "приложение" на React
React я не люблю. :)

Мне нравится писать на ванильном JS, но раз Реакт нынче в моде, то куда деваться, приходится знать этот фреймворк.

Мое простенькое "приложение" на реакте:
/js_files/react/owm/

Добавил там в дизайн пару свистелок при наведении мышки, некоторым такое нравится. Мне нет. :)

Исходники.
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './style.css';

let citiesDataArr = [
	{
		engName: 'Rostov-on-Don',
		rusName: 'Ростов-на-Дону',
		imgFile: 'rnd.jpg',
		idOWM: '501175',
	},
	{
		engName: 'Barcelona',
		rusName: 'Барселона',
		imgFile: 'barcelona.jpg',
		idOWM: '3128760',
	},
	{
		engName: 'Istanbul',
		rusName: 'Стамбул',
		imgFile: 'istanbul.jpg',
		idOWM: '745044',
	},
	{
		engName: 'New York',
		rusName: 'Нью-Йорк',
		imgFile: 'newyork.jpg',
		idOWM: '5128581',
	},
	{
		engName: 'Paris',
		rusName: 'Париж',
		imgFile: 'paris.jpg',
		idOWM: '2968815',
	},
	{
		engName: 'Prague',
		rusName: 'Прага',
		imgFile: 'prague.jpg',
		idOWM: '3067696',
	},
	{
		engName: 'Rome',
		rusName: 'Рим',
		imgFile: 'rome.jpg',
		idOWM: '4306693',
	},
	{
		engName: 'Saint Petersburg',
		rusName: 'Санкт-Петербург',
		imgFile: 'spb.jpg',
		idOWM: '498817',
	},
];

function handleClick(e) {
	let allCitiesBlock = document.querySelector('.allCities');
	let cityBlock;
		
	if ( e.target.closest('.moveLeft') ) {
		allCitiesBlock.scrollBy({ left: - allCitiesBlock.offsetWidth * 0.5, behavior: 'smooth'});
	}

	if ( e.target.closest('.moveRight') ) {
		allCitiesBlock.scrollBy({ left: allCitiesBlock.offsetWidth * 0.5, behavior: 'smooth'});
	}

	if ( (cityBlock = e.target.closest('.oneCityBlock')) ) {
		let index = cityBlock.dataset.index;
		// console.log('Нужно открыть город с индексом ' + index);
		changeCity(index);
		
		// Скачиваю, если прошло с последнего запроса более 5 минут
		if (citiesDataArr[index].lastTimeUpdateOWMcurrent && (Date.now() - citiesDataArr[index].lastTimeUpdateOWMcurrent) < 1000 * 60 * 5) {
			// console.log('Уже скачено для ' + citiesDataArr[index].rusName);
			updateWeatherResultState({hasOWMobj: true, city: index});
		}
		else {
			getDataFromOWM(index);
		}
	}
}

let citiesComponentsList = [];

for (let i = 0; i < citiesDataArr.length; i++) {
	citiesComponentsList.push(
		<OneCity rusName={citiesDataArr[i].rusName} imgFile={citiesDataArr[i].imgFile} key={citiesDataArr[i].engName} dataIndex={i} />
	);
}

function OneCity({rusName, imgFile, dataIndex}) {
	return (
	<div className="oneCityBlock" data-index={dataIndex}>
		<img src={'/images/'+imgFile} alt={rusName} className="cityPic" />
		<div className="cityNameWrapper">
			<div className="cityName">{rusName}</div>
		</div>
	</div>
	)
}

let changeCity;

function OWMApp() {
	const [cityIndex, changeCityFunc] = React.useState(null);
	changeCity = changeCityFunc; // выношу функцию на глобальный уровень, потому что handleClick объявлен на глобальное уровне, а не внутри этого функционального компонента
	
	return (
		<>
			<div className="OWMApp" onClick={handleClick}>
				<h1>Прогноз погоды</h1>
			
				<div className="wrapperCitiesAndArrows">
					<div className="moveLeft">
						<div className="arrow">&#9668;</div>
					</div>
					<div className="allCities">
						{citiesComponentsList}
					</div>
					<div className="moveRight">
						<div className="arrow">&#9658;</div>
					</div>
				</div>
				
				<WeatherResult city={cityIndex} />
			</div>
			<br/>
	   </>
	);
}

function getDataFromOWM(city) {
	// Функция получает с сайта OWM JSON-данные о погоде в городе и записывает эти данные в массив citiesDataArr
	let idOWM = citiesDataArr[city].idOWM; // id населённого пункта
	let keyAPI = 'db54a96ff5f67580b37c722666fcd57b'; // это мой ключ API для OWM
	let urlForFetch = `https://api.openweathermap.org/data/2.5/weather?id=${idOWM}&appid=${keyAPI}`;
	
	fetch(urlForFetch)
	.then((response) => response.json())
	.then((data) => {
			// console.log('После fetch получен объект:', data);
			if  (data.cod !== 200) {
					updateWeatherResultState({hasOWMobj: false, city: city, problem: 'Ошибка: в JSON-ответе от OWM написано, что есть какая-то проблема (свойство cod не  равно 200).'});
			}
			else {
				citiesDataArr[city].dataOWMcurrent = data;
				citiesDataArr[city].lastTimeUpdateOWMcurrent = Date.now();
				updateWeatherResultState({hasOWMobj: true, city: city});
			}
	})
	.catch((e) => {
		console.log('Ошибка во время fetch!', e);
		updateWeatherResultState({hasOWMobj: false, city: city, problem: 'Ошибка во время fetch!'});
	});
}

let updateWeatherResultState;

function WeatherResult(props) {
	const index = props.city;
	
	const [state, setState] = React.useState( {hasOWMobj: false, city: index} );
	updateWeatherResultState = setState;
	
	// Выбирается фоновый рисунок для блока weatherResultBackground
	let style;
	if (index === null) {
		style = { backgroundImage: 'none' };
	}
	else {
		style = { backgroundImage: 'url("images/' + citiesDataArr[index].imgFile + '")' };
	}
	
	let title;
	if (index === null) {
		title = 'Выберите город';
	}
	else {
		title = 'Погода в городе ' + citiesDataArr[index].rusName;
	}

	let content;
	if (index === null) {
		content = null;
	}
	else {
		if (state.hasOWMobj === true) {
			if (citiesDataArr[index].dataOWMcurrent) {
				let data = citiesDataArr[index].dataOWMcurrent;
				content = <>
					Температура сейчас: { Math.round(data?.main?.temp - 273.15) } °C <br/>
					Ощущается как: { Math.round(data?.main?.feels_like - 273.15) } °C <br/>
					<br/>
					Давление: { Math.round((data?.main?.pressure) / 1.333) } мм рт. ст. ({ data?.main?.pressure } гПа) <br/>
					Влажность: { data?.main?.humidity }%<br/>
					<br/>
					Скорость ветра: { data?.wind?.speed } <span style={{whiteSpace: 'nowrap'}}>м/с</span><br/>
					Облачность: { data?.clouds?.all }%<br/>
					<br/>
					Широта: {data?.coord?.lat} <br/>
					Долгота: {data?.coord?.lon} <br/>
					<br/>
					<small>Данные о погоде получены с помощью OpenWeatherMap API</small><br/>
				</>
			}
			else {
				content = 'Ждите...';
			}
			
		}
		else {
			if (state.problem) { content = state.problem; }
			else { content = <LoadingCircle />; }
		}
	}
	
	
	return (
		<div className="weatherResultWrapper">
			<div className="weatherResultBackground" style={style}>
			</div>
			<div className="weatherResult">
				<h2 className="cityTitle">{title}</h2>
				<div>
					{content}
				</div>
			</div>
		</div>
	);
}

function LoadingCircle() {
	return (
		<div className="loading">
			<div className="spinningCircle"></div>
			<div className="loadingText">Загрузка</div>
		</div>
	);
}

ReactDOM.render(
  <OWMApp />,
  document.getElementById('root')
);

style.css
:root {
	font-size: 20px;
	font-family: sans-serif;
	--sizeCity: 300px;
	--cityNameWrapperHeight: 40px;
	--cityNameFontSize: 26px;
	--cityPicDownMargin: 10px;
}

body {
	margin: 0;
}

.OWMApp {
	max-width: 1320px;
	margin: 0 auto;
}

h1 {
		font-size: 2rem;
		text-align: center;
		font-weight: normal;
		margin: 0 0 5px 0;
}

.wrapperCitiesAndArrows {
	border: 0px solid green;
	display: flex;
}

.moveRight, .moveLeft {
	border: 0px solid skyblue;
	color: #626262;
	cursor: default;
	user-select: none;
	display: flex;
	align-content: center;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
}

.arrow {
	border: 0px solid teal;
	font-size: 60px;
	transform: scaleX(0.5) translateY( calc(0px - var(--cityNameWrapperHeight)*0.5 - var(--cityPicDownMargin)*0.5 ));
	color: #5b8358;
}

.moveRight:hover .arrow, .moveLeft:hover .arrow {
	animation: scaleArrow 0.3s infinite alternate;
}

.allCities {
	display: flex;
	flex-wrap: nowrap;
	border: 0px solid black;
	overflow-x: scroll;
	scrollbar-width: none;
	user-select: none;
}

.allCities::-webkit-scrollbar {
	display: none;
}

.oneCityBlock {
	background-color: transparent;
	width: var(--sizeCity);
	text-align: center;
	border: 0px solid gray;
	cursor: pointer;
}

.cityPic {
	width: var(--sizeCity);
	height: var(--sizeCity);
	clip-path: circle(40%);
	transition: clip-path 0.1s;
	display: block;
	margin: 0 auto var(--cityPicDownMargin) auto;
}

.oneCityBlock:hover .cityPic {
	clip-path: circle(50%);
}

.cityNameWrapper {
	border: 0px solid red;
	padding: 0px 4px 0px 4px;
	height: var(--cityNameWrapperHeight);
}

.cityName {
	--verPadding: 5px;
	--horPadding: 10px;
	font-size: var(--cityNameFontSize);
	text-align: center;
	display: inline-block;
	max-width: calc(100% - 2 * var(--horPadding));
	text-overflow: ellipsis;
	height: calc(var(--cityNameWrapperHeight) - var(--verPadding) * 2);
	padding: var(--verPadding) var(--horPadding);
	border-radius: 10px;
	border: 0px solid black;
	margin: 0 auto;
	background-color: #5b8358;
	color: #fafafa;
	text-shadow: 1px 1px 2px black;
	transform: none;
	transition: background-color 0.1s, color 0.1s, transform 0.2s;
	overflow: hidden;
}

.oneCityBlock:hover .cityName {
	background-color: #2d8328;
	color: #ffffff;
	transform: translateY(calc(0px - (var(--sizeCity))*0.5 - var(--cityPicDownMargin) - var(--cityNameWrapperHeight)*0.5 ) );
}

@keyframes scaleArrow {        
	from { transform: scale(0.5, 1.0) translateY( calc(0px - var(--cityNameWrapperHeight)*0.5 )); }
	to { transform: scale(0.75, 1.5) translateY( calc(0px - var(--cityNameWrapperHeight)*0.5 )); }
}

.weatherResultWrapper {
		max-width: 1800px;
		border: 0px solid red;
		position: relative;
		margin: 10px auto 0 auto;;
}

.weatherResultBackground {
	width: 100%;
	height: 100%;
	background-size: cover;
	filter: blur(5px) opacity(0.4);
	position: absolute;
}

.weatherResult {
	width: 100%;
	box-sizing: border-box;
	padding: 10px;
	color: #242424;
	text-shadow: 1px 1px 2px #f0f0f0;
	font-size: 1.6rem;
	position: relative;
	overflow-wrap: break-word; 
	border: 0px solid green;
}

h2.cityTitle {
	font-size: 1.8rem;
	text-align: center;
	margin: 0px 0px 10px 0px;
	font-weight: normal;
}

.loading {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
}

.loadingText {
	font-size: 1.6rem;
	color: rgba(91, 131, 88, 1.0);
}

.spinningCircle {
	width: 50px;
	height: 50px;
	border-radius: 50%;
	border: 5px solid rgba(91, 131, 88, 1.0);
	border-left-color: rgba(91, 131, 88, 0.2);
	animation: rotate-s-loader 0.7s linear infinite;
}

@keyframes rotate-s-loader {
	from { transform: rotate(0); }
	to { transform: rotate(360deg); }
}

@media screen and (max-width: 680px)
{
	:root {
		--sizeCity: 150px;
		--cityNameFontSize: 14px;
		--cityNameWrapperHeight: 26px;
	}
}
Раздел: JavaScript

Комментарии
(из-за чертовых спамеров урлы в коментах теперь писать нельзя)

Имя:

 
Комментарий:

 

Антиспам. Сколько будет четырнадцать добавить десяточку?
Напишите только число:




Комментариев нет