In this tutorial, I will teach you how to create a weather app using HTML5, CSS3, and JavaScript. The complete source code of this JavaScript Weather App is given below.
You can download the full source code (including images) of this JavaScript Weather app at the end of this article.
JavaScript Weather App Source Code
index.html
HTML
x
59
59
1
2
<html lang="en" dir="ltr">
3
<head>
4
<meta charset="utf-8">
5
<title>Weather App in JavaScript</title>
6
<link rel="stylesheet" href="style.css">
7
<meta name="viewport" content="width=device-width, initial-scale=1.0">
8
<!-- Linking BoxIcon for Icon -->
9
<link href='https://unpkg.com/boxicons@2.0.9/css/boxicons.min.css' rel='stylesheet'>
10
</head>
11
<body>
12
<div class="wrapper">
13
<header><i class='bx bx-left-arrow-alt'></i>Weather App</header>
14
<section class="input-part">
15
<p class="info-txt"></p>
16
<div class="content">
17
<input type="text" spellcheck="false" placeholder="Enter city name" required>
18
<div class="separator"></div>
19
<button>Get Device Location</button>
20
</div>
21
</section>
22
<section class="weather-part">
23
<img src="" alt="Weather Icon">
24
<div class="temp">
25
<span class="numb">_</span>
26
<span class="deg">°</span>C
27
</div>
28
<div class="weather">_ _</div>
29
<div class="location">
30
<i class='bx bx-map'></i>
31
<span>_, _</span>
32
</div>
33
<div class="bottom-details">
34
<div class="column feels">
35
<i class='bx bxs-thermometer'></i>
36
<div class="details">
37
<div class="temp">
38
<span class="numb-2">_</span>
39
<span class="deg">°</span>C
40
</div>
41
<p>Feels like</p>
42
</div>
43
</div>
44
<div class="column humidity">
45
<i class='bx bxs-droplet-half'></i>
46
<div class="details">
47
<span>_</span>
48
<p>Humidity</p>
49
</div>
50
</div>
51
</div>
52
</section>
53
</div>
54
55
<script src="script.js"></script>
56
57
</body>
58
</html>
59
script.js
JavaScript
1
97
97
1
const wrapper = document.querySelector(".wrapper"),
2
inputPart = document.querySelector(".input-part"),
3
infoTxt = inputPart.querySelector(".info-txt"),
4
inputField = inputPart.querySelector("input"),
5
locationBtn = inputPart.querySelector("button"),
6
weatherPart = wrapper.querySelector(".weather-part"),
7
wIcon = weatherPart.querySelector("img"),
8
arrowBack = wrapper.querySelector("header i");
9
10
let api;
11
12
inputField.addEventListener("keyup", e =>{
13
// if user pressed enter btn and input value is not empty
14
if(e.key == "Enter" && inputField.value != ""){
15
requestApi(inputField.value);
16
}
17
});
18
19
locationBtn.addEventListener("click", () =>{
20
if(navigator.geolocation){ // if browser support geolocation api
21
navigator.geolocation.getCurrentPosition(onSuccess, onError);
22
}else{
23
alert("Your browser not support geolocation api");
24
}
25
});
26
27
function requestApi(city){
28
api = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=your_api_key`;
29
fetchData();
30
}
31
32
function onSuccess(position){
33
const {latitude, longitude} = position.coords; // getting lat and lon of the user device from coords obj
34
api = `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&units=metric&appid=your_api_key`;
35
fetchData();
36
}
37
38
function onError(error){
39
// if any error occur while getting user location then we'll show it in infoText
40
infoTxt.innerText = error.message;
41
infoTxt.classList.add("error");
42
}
43
44
function fetchData(){
45
infoTxt.innerText = "Getting weather details...";
46
infoTxt.classList.add("pending");
47
// getting api response and returning it with parsing into js obj and in another
48
// then function calling weatherDetails function with passing api result as an argument
49
fetch(api).then(res => res.json()).then(result => weatherDetails(result)).catch(() =>{
50
infoTxt.innerText = "Something went wrong";
51
infoTxt.classList.replace("pending", "error");
52
});
53
}
54
55
function weatherDetails(info){
56
if(info.cod == "404"){ // if user entered city name isn't valid
57
infoTxt.classList.replace("pending", "error");
58
infoTxt.innerText = `${inputField.value} isn't a valid city name`;
59
}else{
60
//getting required properties value from the whole weather information
61
const city = info.name;
62
const country = info.sys.country;
63
const {description, id} = info.weather[0];
64
const {temp, feels_like, humidity} = info.main;
65
66
// using custom weather icon according to the id which api gives to us
67
if(id == 800){
68
wIcon.src = "icons/clear.svg";
69
}else if(id >= 200 && id <= 232){
70
wIcon.src = "icons/storm.svg";
71
}else if(id >= 600 && id <= 622){
72
wIcon.src = "icons/snow.svg";
73
}else if(id >= 701 && id <= 781){
74
wIcon.src = "icons/haze.svg";
75
}else if(id >= 801 && id <= 804){
76
wIcon.src = "icons/cloud.svg";
77
}else if((id >= 500 && id <= 531) || (id >= 300 && id <= 321)){
78
wIcon.src = "icons/rain.svg";
79
}
80
81
//passing a particular weather info to a particular element
82
weatherPart.querySelector(".temp .numb").innerText = Math.floor(temp);
83
weatherPart.querySelector(".weather").innerText = description;
84
weatherPart.querySelector(".location span").innerText = `${city}, ${country}`;
85
weatherPart.querySelector(".temp .numb-2").innerText = Math.floor(feels_like);
86
weatherPart.querySelector(".humidity span").innerText = `${humidity}%`;
87
infoTxt.classList.remove("pending", "error");
88
infoTxt.innerText = "";
89
inputField.value = "";
90
wrapper.classList.add("active");
91
}
92
}
93
94
arrowBack.addEventListener("click", ()=>{
95
wrapper.classList.remove("active");
96
});
97
style.css
CSS
1
198
198
1
/* Import Google Font - Poppins */
2
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap');
3
*{
4
margin: 0;
5
padding: 0;
6
box-sizing: border-box;
7
font-family: 'Poppins', sans-serif;
8
}
9
body{
10
display: flex;
11
align-items: center;
12
justify-content: center;
13
min-height: 100vh;
14
background: #43AFFC;
15
}
16
::selection{
17
color: #fff;
18
background: #43AFFC;
19
}
20
.wrapper{
21
width: 400px;
22
background: #fff;
23
border-radius: 7px;
24
box-shadow: 7px 7px 20px rgba(0, 0, 0, 0.05);
25
}
26
.wrapper header{
27
display: flex;
28
font-size: 21px;
29
font-weight: 500;
30
color: #43AFFC;
31
padding: 16px 15px;
32
align-items: center;
33
border-bottom: 1px solid #ccc;
34
}
35
header i{
36
font-size: 0em;
37
cursor: pointer;
38
margin-right: 8px;
39
}
40
.wrapper.active header i{
41
margin-left: 5px;
42
font-size: 30px;
43
}
44
.wrapper .input-part{
45
margin: 20px 25px 30px;
46
}
47
.wrapper.active .input-part{
48
display: none;
49
}
50
.input-part .info-txt{
51
display: none;
52
font-size: 17px;
53
text-align: center;
54
padding: 12px 10px;
55
border-radius: 7px;
56
margin-bottom: 15px;
57
}
58
.input-part .info-txt.error{
59
color: #721c24;
60
display: block;
61
background: #f8d7da;
62
border: 1px solid #f5c6cb;
63
}
64
.input-part .info-txt.pending{
65
color: #0c5460;
66
display: block;
67
background: #d1ecf1;
68
border: 1px solid #bee5eb;
69
}
70
.input-part :where(input, button){
71
width: 100%;
72
height: 55px;
73
border: none;
74
outline: none;
75
font-size: 18px;
76
border-radius: 7px;
77
}
78
.input-part input{
79
text-align: center;
80
padding: 0 15px;
81
border: 1px solid #ccc;
82
}
83
.input-part input:is(:focus, :valid){
84
border: 2px solid #43AFFC;
85
}
86
.input-part input::placeholder{
87
color: #bfbfbf;
88
}
89
.input-part .separator{
90
height: 1px;
91
width: 100%;
92
margin: 25px 0;
93
background: #ccc;
94
position: relative;
95
display: flex;
96
align-items: center;
97
justify-content: center;
98
}
99
.separator::before{
100
content: "or";
101
color: #b3b3b3;
102
font-size: 19px;
103
padding: 0 15px;
104
background: #fff;
105
}
106
.input-part button{
107
color: #fff;
108
cursor: pointer;
109
background: #43AFFC;
110
transition: 0.3s ease;
111
}
112
.input-part button:hover{
113
background: #1d9ffc;
114
}
115
116
.wrapper .weather-part{
117
display: none;
118
margin: 30px 0 0;
119
align-items: center;
120
justify-content: center;
121
flex-direction: column;
122
}
123
.wrapper.active .weather-part{
124
display: flex;
125
}
126
.weather-part img{
127
max-width: 125px;
128
}
129
.weather-part .temp{
130
display: flex;
131
font-weight: 500;
132
font-size: 72px;
133
}
134
.weather-part .temp .numb{
135
font-weight: 600;
136
}
137
.weather-part .temp .deg{
138
font-size: 40px;
139
display: block;
140
margin: 10px 5px 0 0;
141
}
142
.weather-part .weather{
143
font-size: 21px;
144
text-align: center;
145
margin: -5px 20px 15px;
146
}
147
.weather-part .location{
148
display: flex;
149
font-size: 19px;
150
padding: 0 20px;
151
text-align: center;
152
margin-bottom: 30px;
153
align-items: flex-start;
154
}
155
.location i{
156
font-size: 22px;
157
margin: 4px 5px 0 0;
158
}
159
.weather-part .bottom-details{
160
display: flex;
161
width: 100%;
162
justify-content: space-between;
163
border-top: 1px solid #ccc;
164
}
165
.bottom-details .column{
166
display: flex;
167
width: 100%;
168
padding: 15px 0;
169
align-items: center;
170
justify-content: center;
171
}
172
.column i{
173
color: #5DBBFF;
174
font-size: 40px;
175
}
176
.column.humidity{
177
border-left: 1px solid #ccc;
178
}
179
.column .details{
180
margin-left: 3px;
181
}
182
.details .temp, .humidity span{
183
font-size: 18px;
184
font-weight: 500;
185
margin-top: -3px;
186
}
187
.details .temp .deg{
188
margin: 0;
189
font-size: 17px;
190
padding: 0 2px 0 1px;
191
}
192
.column .details p{
193
font-size: 14px;
194
margin-top: -6px;
195
}
196
.humidity i{
197
font-size: 37px;
198
}