Мне нравится писать на ванильном 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">◄</div>
</div>
<div className="allCities">
{citiesComponentsList}
</div>
<div className="moveRight">
<div className="arrow">►</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;
}
}
(из-за чертовых спамеров урлы в коментах теперь писать нельзя)
Комментариев нет