DEV Community

Cover image for Weather App with HTML, SASS, and JavaScript
Maxim Maeder
Maxim Maeder

Posted on • Originally published at maximmaeder.com

Weather App with HTML, SASS, and JavaScript

Original Post

Learn how to make a Simple App showing Temperatures for the next seven days. We learn about SVG and API fetching.

Before we get into it, I want to stress that I am no JavaScript Pro; therefore, it may be that my practices are not the best or even the worst. Feel free to contact me in any way and tell me better ways to do things!

Idea

Today we will make a little Website that shows us the hourly temperatures in the next seven days for a certain choosable city. We will make the graph ourselves using the power of SVG. We get the data from the Open Meteo API (Licence). Before we get into coding, we look at how SVG will help us and what kind of data the API returns.

Live Demo

SVG

Scalable Vector Graphics are fantastic. They look good in all sizes and can easily be made programmatically because they are HTML tags. Below you see a simple SVG tag that will show a polygon. We will use this tag for our graph and line and text. In the program, we add the points.

<svg height="210"  width="500">  
    <polygon points="200,10 250,190 160,210"/>  
</svg>
Enter fullscreen mode Exit fullscreen mode

API

We will request the hourly temperatures in the next seven days from the weather temperatures. We request an URL that looks something like this. The coordinates will be inserted.

https://api.open-meteo.com/v1/forecast?latitude=30.0&longitude=30.0&hourly=temperature_2m
Enter fullscreen mode Exit fullscreen mode

Such a request will return a JSON string with the information we need.

{
    "latitude": 30.0,
    "longitude": 30.0,
    "generationtime_ms": 1.3890266418457031,
    "utc_offset_seconds": 0,
    "elevation": 137.0,
    "hourly_units": {
        "time": "iso8601",
        "temperature_2m": "°C"
    },
    "hourly": {
        "time": [
            "2022-08-03T00:00",
            "2022-08-03T01:00",
            "2022-08-03T02:00",
            ...
        ],
        "temperature_2m": [
            24.7,
            24.3,
            23.9,
            ...
        ]
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's get into coding!

HTML

Let us start with the HTML of our website. As always, we use the default scaffolding that can be expanded in VS-code with the emmet abbreviation !.

In the head, we import our stylesheet.

<link  rel="stylesheet"  href="style.css">
Enter fullscreen mode Exit fullscreen mode

In our body, we have two div elements and the script tag for the js of the website. The first div holds the header and the select element for the choosable cities, and the other contains the SVG and the link to the API.

<div class="header-container">
     <div class="header">
         ...
     </div>
 </div>

 <div class="container graph-container">
     <svg height="400" id="graph-svg">
        ...
     </svg>

     <div>
         <a href="https://open-meteo.com" target="_blank">Weather Data Provided By Open-Meteo</a>
     </div>
 </div>

 <script src="app.js"></script>
Enter fullscreen mode Exit fullscreen mode

Inside the header, we set an h1 and a select element. Each option represents a location; therefore, we give each of them a custom data-lon and data-lat attribute, which are the coordinates of the given area. We later access these attributes in the JS code.

    <h1>Weather App</h1>

    <div>
        <span>Choose Location: </span>
        <select name="" class="countrySelect">
            <option value="Berlin" selected data-lat="52.5235" data-lon="13.4115">Berlin</option>
            <option value="Paris" data-lat="48.8567" data-lon="2.3510">Paris</option>
            <option value="London" data-lat="51.5002" data-lon="-0.1262">London</option>
            <option value="Madrid" data-lat="40.4167" data-lon="-3.7033">Madrid</option>
            ...
        </select>
     </div>
Enter fullscreen mode Exit fullscreen mode

Inside the svg element, we first make a gradient texture that can be later used in visible SVG elements. Then we add two SVG groups with the element g. The first one holds all the labels and the second one the graph itself. We will insert the other SVG that's through the JS code.

<defs>
  <linearGradient id="gradient" x1="0%" y1="0%" x2="0%" y2="100%">
      <stop offset="0%" style="stop-color:#5591c2  ;stop-opacity:1" />
      <stop offset="50%" style="stop-color:#5591c2  ;stop-opacity:0.5" />
      <stop offset="100%" style="stop-color:rgba(44, 44, 44, 0);stop-opacity:0" />
  </linearGradient>
</defs>

<g class="lables"></g>
<g class="data"></g>
Enter fullscreen mode Exit fullscreen mode

Now we add the functionalities with JavaScript!

JavaScript

Let's get to the JS of the Program. We must remember that we must redraw the graph every time the window resizes or when the user changes the location.

Setup

Let's setup up some variables. The first one currentData acts just as a cache, so we don't have to get the data each time the window resizes. The following three constants are used for the styling of the graph and the labels. We need to add a little padding to the left inside the SVG element because our temperature scale will be there. We also exaggerate the temperatures, so they have a more apparent difference. And we also define how far apart each step on the temperature scale is.

let currentData = {}

const paddingLeftGraph = 60
const height_exxagaration = 7
const LabelStepsY = 5
Enter fullscreen mode Exit fullscreen mode

After that, we save some of the needed elements in variables. For this we use querySelector and getElementById. continuing, we get the dimensions of the SVG element minus the padding; this is important so the graph will span the full width of the container. Last but not least, we define a step multiplier. This value will be used in tandem with the width.

The API will always return 168 Datapoints, but it may be that the SVG is 500 Pixels wide, and we want each of the Datapoints evenly spread out in these 500 Pixels. These last three variables will always be overwritten when the window size changes.

const dataContainer = document.querySelector('.data')
const lableContainer = document.querySelector('.lables')
const citySelect = document.querySelector('.countrySelect')
const svgElement = document.getElementById('graph-svg')

let container_width = svgElement.getBoundingClientRect().width - paddingLeftGraph
let container_height = svgElement.getBoundingClientRect().height
let step_multiplier = container_width / 168
Enter fullscreen mode Exit fullscreen mode

Functions

Let us go over the function used on our website.

The first one is pretty simple. It will just fetch a provided URL and return the parsed json.

async function request(url) {
    const response = await fetch(url);
    const data = await response.json();
    return data;
}
Enter fullscreen mode Exit fullscreen mode

The next one is also simple as it will just set the new width and step multiplier and redraw the data and labels as soon as the window size changes.

function setSize() {
    let size = svgElement.getBoundingClientRect()

    container_width = size.width - paddingLeftGraph
    step_multiplier = container_width / 168

    DrawData()
    drawLables()
}
Enter fullscreen mode Exit fullscreen mode

The getWeekDay function will return the name of the weekday from a given date.

function getWeekDay(date) {
    let days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
    return days[date.getDay()];
}
Enter fullscreen mode Exit fullscreen mode

The changeLocation function will first get the option element where the value is the same as the select itself. This returned element has the data-lon and data-lat attributes which we will get from it using getAttribute(). After that we build the request string where insert these coordinates and we call the request() function. As soon as the request is finished we set the currentData variable to the returned data and we redraw the graph with DrawData()

function changeLocation() {    

    let currentCityEl = citySelect.querySelector(`[value="${citySelect.value}"]`)

    let lat = currentCityEl.getAttribute('data-lat')
    let lon = currentCityEl.getAttribute('data-lon')

    let requestString = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&hourly=temperature_2m`

    request(requestString).then((data) => {
        currentData = data
        DrawData(currentData)
    })

}
Enter fullscreen mode Exit fullscreen mode

Now let us get to the DrawData() function. We start by making a string in which we build a polygon element. The thing about SVG polygons is that they always have to be closed, that is why we insert the positions of the start and end. We also add a placeholder INSERT_POINTS which will later be replaced with the points of the temperatures. When making the start and end point we take the padding into account.

Then we define three variables one will hold the concatenated string of all the points and the other hold the hourly temperatures we got from the request. The last one will be the x index position.

Then we loop over the hourly temperatures and we add the temperature and index to the points. We have to keep in mind the step multiplier and that the coordinates in a polygon start at the top left of the SVG element. Last but not least we insert the points string we just made into the string we made in the first place and we set it to be the inner HTML of the dataContainer which is a group in our SVG.

function DrawData() {
    let poly = `<polygon fill="url(#gradient)" points="${paddingLeftGraph},${container_height} INSERT_POINTS ${container_width + paddingLeftGraph},${container_height}"></polygon>`
    let positions = ''

    const temps = currentData.hourly.temperature_2m

    let index = 0
    temps.forEach(temperature => {
        positions = positions + ` ${index * step_multiplier + paddingLeftGraph},${container_height - temperature * height_exxagaration}`

        index++
    })

    poly = poly.replace('INSERT_POINTS', positions)

    dataContainer.innerHTML = poly
}
Enter fullscreen mode Exit fullscreen mode

Let's all so go over the drawLables function. We do it pretty similar here, we build a string of text and line elements and we set them to be the inner HTML of the lableContainer. We make a loop where we count by our LabelStepsY variable. We do this for the X and Y axis. For the horizontal axis, we insert the weekdays but we set today and tomorrow.

function drawLables() {

    let verticalLinesString = ''
    for (let index = 0; index < 50; index += LabelStepsY) {
        let height = container_height - index * height_exxagaration
        verticalLinesString = verticalLinesString + `<line x1="0" y1="${height}" x2="1000" y2="${height}" class="line" /><text x="0" y="${height}">${index} °C</text>`
    }

    let dayIndex = 0
    for (let index = 0; index < 169; index += 24) {

        let daytext
        if (dayIndex == 0) {
            daytext = 'Today'
        }
        else if (dayIndex == 1) {
            daytext = 'Tomorrow'
        }
        else {
            var date = new Date();
            date.setDate(date.getDate() + dayIndex);

            daytext = getWeekDay(date)
        }

        let width = index * step_multiplier + paddingLeftGraph
        verticalLinesString = verticalLinesString + `<line x1="${width}" y1="50" x2="${width}" y2="1000" class="line"/><text x="${width}" y="${container_height - 5}">${daytext}</text>`

        dayIndex++
    }

    lableContainer.innerHTML = verticalLinesString
}
Enter fullscreen mode Exit fullscreen mode

Connecting Events

After making the functions we connect the change event from the selection to the changeLocation function and the resize event from the document to setSize. Last but not least we call changeLocation and drawLables so the graph is drawn for the firs time after the page loads.

citySelect.addEventListener('change', changeLocation)
window.addEventListener('resize', setSize, false);

changeLocation()
drawLables()
Enter fullscreen mode Exit fullscreen mode

CSS / SASS

Our Web App will now work but it won't look good, so let's add some styling to it!

In the sass file, we first add four variables that hold the color palette and we define a mixin which is a reusable component that we can later include anywhere with @include container.

$color: #3e6b8f    
$bg: rgb(65, 65, 65)
$lines: rgb(90, 90, 90)
$text: #f3f3f3

@mixin container()
    max-width: 900px
    margin: 0 auto
    padding: 1em
Enter fullscreen mode Exit fullscreen mode

After that, we style the body by setting the font and color. We also make a class container that solely consists of the mixin.

body
    box-sizing: border-box
    font-family: 'Segoe UI', sans-serif
    margin: 0
    background-color: $bg
    color: $text


.container
    @include container
Enter fullscreen mode Exit fullscreen mode

Then we make the header, where we also use the mixin

.header-container

    background-color: $color
    border-bottom: 6px solid darken($color, 5)

    .header
        @include container
        padding: 1em
        display: flex
        justify-content: space-between
        align-items: center

    h1
        margin: 0
Enter fullscreen mode Exit fullscreen mode

continuing we style the SVG to be full width and we set the outline color of the lines with the stroke property and the color of the text with fill. Last but not least we style the link and we use the lighten function that will as its name suggests lighten the color by the amount given as a second argument.

.graph-container
    margin-top: 1em

    svg
        width: 100%

        .line
            stroke: $lines

        text
            fill: $text

a
    color: lighten($color, 20)
    text-decoration: none
    padding: 0.5em
    display: block
    text-align: center
Enter fullscreen mode Exit fullscreen mode

Showcase

Below you see the Website in action.

GIF

Weather App (maximmaeder.com)
GitHub

Conclusion

Excellent! You have successfully created a Weather using Web Technologies! See how you can add more features to this program, such as rain prediction or custom locations.

Keep in mind that I am also just a beginner, so it could be that my way of solving these problems is not the best or that I use functions or features that aren't advised. Always ask questions and try to solve problems your way!

Attribution

Open Meteo
Licenceand try to solve problems your way!

Top comments (0)