Мне нравится писать на ванильном 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; } }
(из-за чертовых спамеров урлы в коментах теперь писать нельзя)
Комментариев нет