Responsive Web Design
Niels Olof Bouvin
1
Responsive Web Design Niels Olof Bouvin 1 Overview - - PowerPoint PPT Presentation
Responsive Web Design Niels Olof Bouvin 1 Overview Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser
Niels Olof Bouvin
1
Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser
2
A Website should adapt to its user
providing a quickly updating user interface adapt its presentation to the user’s device
We will look at this from two sides
adding JavaScript to Web pages in order to avoid complete reloads extending our CSS layout, so that we can support both wide and narrow screens
responsiveness | rɪˈspɒnsɪvnəs |
noun [mass noun]
the quality of reacting quickly and positively
3
Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser
4
One of the most transformational developments in Web design and development was the realisation that you could break free of the pure client/server model Rather than having to reload the entire Web page from the server, parse, and render it (again) to refmect some change, it became possible to have a script on the page retrieve changes from the server, and update the Web page in situ
much smaller download, no need to re-render ⇒ much faster, fmuid and responsive
5
Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser
6
The Web browser is governed by events
the page has loaded, or a resource has loaded the user has clicked something, or dragged something, or dropped something the user has resized the browser window
If we want something to happen when an event
proper Web browser parlance register an event handler
7
Whenever the button is clicked, the registered function is called (with the event as argument), and it modifjes the button’s background colour
app └── index.js public/ ├── index.html ├── js │ └── main.js └── style └── main.css const btn = document.querySelector('#button') function randomInt (limit = 256) { return Math.floor(limit * Math.random()) } function bgcChange (e) { const rndCol = `rgb(${randomInt()}, ${randomInt()}, ${randomInt()})` console.log(e) e.target.style.backgroundColor = rndCol } btn.addEventListener('click', bgcChange) … <body> <button id="button">Press this Button!</button> </body> </html>
8
for all elements
click dblclick focus blur mouseover mouseout
for windows
keypress keydown keyup
9
Just as we can change the styling of existing elements
you will recall the Document Object Model, which forms a tree of element rooted in <html> here, we added new children to the <body> element
for (let i = 1; i <= 32; i++) { const myDiv = document.createElement('div') document.body.appendChild(myDiv) } const divs = document.querySelectorAll('div') for (let myDiv of divs) { myDiv.addEventListener('mouseover', bgcChange) } app └── index.js public/ ├── index.html ├── js │ └── main.js └── style └── main.css
10
It can be a great convenience to be able to validate form entry before it actually makes it to the server (where we of course also have to validate it)
never, ever trust the user’s input
But, there is already a default action for pressing the Submit button on a form, so what to do? Events can be prevented from happening, until such time we are ready to permit them to execute
11
I have added a couple of empty, but named, <div> which will be used for error messages
<form method="POST" action="/greetings" id="greetings-form"> <div> <label for="recipient">Recipient:</label> <input type="text" id="recipient" name="recipient"> <div id="recipient-feedback" class="feedback"></div> </div> <div> <label for="message">Message:</label> <textarea id="message" name="message"></textarea> <div id="message-feedback" class="feedback"></div> </div> <div> <button type="submit">Submit</button> </div> </form>
app └── index.js db ├── db.js └── greetings.sqlite public ├── js │ └── main.js └── style └── main.css views ├── form.hbs ├── greeting.hbs └── layouts └── main.hbs12
If a fjeld is empty, we put out an error message, and prevent the submit event from occurring
const form = document.querySelector('#greetings-form') const recipient = document.querySelector('#recipient') const message = document.querySelector('#message') const recipientFeedback = document.querySelector('#recipient-feedback') const messageFeedback = document.querySelector('#message-feedback') form.addEventListener('submit', (e) => { if (recipient.value === '') { e.preventDefault() recipientFeedback.textContent = 'Please add a recipient' } else { recipientFeedback.textContent = '' } if (message.value === '') { e.preventDefault() messageFeedback.textContent = 'Please add a message' } else { messageFeedback.textContent = '' } })
app └── index.js db ├── db.js └── greetings.sqlite public ├── js │ └── main.js └── style └── main.css views ├── form.hbs ├── greeting.hbs └── layouts └── main.hbs13
14
For all elements on a Web page, we can add event listeners But what happens if we add the same kind of event listener (e.g., ‘click’) on elements that are contained within each other? Who gets the event?
the parent element (the ‘outer’ element), or the child element (‘the ‘inner’ element)?
15
Clicking the video is supposed to play it Clicking the surrounding area is supposed to hide it
16
The video element is contained within the <div> which has the class ‘hidden’ from the start
(incidentally, this shows you how to add video to your pages, and how to support multiple formats)
app └── index.js public ├── index.html ├── js │ └── main.js ├── style │ └── main.css └── video ├── rabbit320.mp4 └── rabbit320.webm
<body> <button>Display video</button> <div class="hidden"> <video> <source src="video/rabbit320.webm" type="video/webm"> <source src="video/rabbit320.mp4" type="video/mp4"> <p>Your browser doesn't support HTML5 video. Here is a <a href="video/rabbit320.mp4">link to the video</a> instead.</p> </video> </div> </body>
17
app └── index.js public ├── index.html ├── js │ └── main.js ├── style │ └── main.css └── video ├── rabbit320.mp4 └── rabbit320.webm
div { position: absolute; top: 50%; transform: translate(-50%,-50%); width: 480px; height: 380px; border-radius: 10px; background-color: #eee; background-image: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.1)); } .hidden { left: -50%; /* off screen */ } .showing { left: 50%; /* on screen */ } div video { display: block; width: 400px; margin: 40px auto; }
18
Seems straight forward enough:
click on the button: show the videoBox click on the videoBox: hide the videoBox click on the video: play the video so why does it not work?
app └── index.js public ├── index.html ├── js │ └── main.js ├── style │ └── main.css └── video ├── rabbit320.mp4 └── rabbit320.webm
const btn = document.querySelector('button') const videoBox = document.querySelector('div') const video = document.querySelector('video') btn.addEventListener('click', (e) => { videoBox.setAttribute('class', 'showing') }) videoBox.addEventListener('click', (e) => { videoBox.setAttribute('class', 'hidden') }) video.addEventListener('click', (e) => { video.play() })
19
Oddly enough, two things:
Event capturing: the browser starts from the outer-most ancestor (<html>), checks if it is registered for the event in the capturing phase, runs it if so, and then continues to the next element and so on, until it hits the inner-most element where the event occurred Event bubbling: the browser checks if the element which had the event occur is registered for that event in the bubbling phase, runs it if so, and then continues
20
In modern browsers, the default is event bubbling When the user clicks on the video, it starts playing, but the event continues upwards until the hits <html>
thus, the event listener on the <div> is also triggered, causing the <div> to be hidden
Happily, it is possible to stop events from propagating
e.stopPropagation()
21
Sometimes it can be convenient that events bubble up, and sometimes you will want to curtail that
app └── index.js public ├── index.html ├── js │ └── main.js ├── style │ └── main.css └── video ├── rabbit320.mp4 └── rabbit320.webm
const btn = document.querySelector('button') const videoBox = document.querySelector('div') const video = document.querySelector('video') btn.addEventListener('click', (e) => { videoBox.setAttribute('class', 'showing') }) videoBox.addEventListener('click', (e) => { videoBox.setAttribute('class', 'hidden') }) video.addEventListener('click', (e) => { e.stopPropagation() video.play() })
22
Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser
23
Web browsers can do a lot of things these days
manipulate the DOM, obviously draw on the page manipulate sound and video do 3D graphics access sensors found on the machine the Web browser is running on talk to servers
24
Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser
25
The central object in the Web browser, document,
event listeners, read state of elements, modify, and add content to the page The Number Guessing Game is a small example of what is possible Follow the MDN page on the game, but note the changes I have made to the game in the following pages
26
A game on a Web page It generates a random number as a target It accepts the user's guess through a text fjeld It checks the user's guess against the target It updates the Web page by adding elements This is repeated until the game is won or lost The game can then be restarted and the page reset
27
<!DOCTYPE html> <html lang=“en”> <head> <meta charset="utf-8"> <title>Number guessing game</title> <link rel="stylesheet" href="game.css"> </head> <body> <h1>Number guessing game</h1> <p>We have selected a random number between 1 and 100. See if you can guess it in 10 turns or fewer. We'll tell you if your guess was too high or too low.</p> <div class="form"> <label for="guessField">Enter a guess: </label> <input type="text" id="guessField"> <input type="submit" value="Submit guess" id="guessSubmit"> </div> <div id="resultParas"> <p id="guesses"></p> <p id="lastResult"></p> <p id="lowOrHi"></p> </div> <script src="game.js"></script> </body> </html>
28
html { font-family: sans-serif; } body { width: 50%; max-width: 800px; min-width: 480px; margin: 0 auto; } #lastResult { color: white; padding: 3px; }
29
let randomNumber = Math.ceil(Math.random() * 100) const guesses = document.querySelector('#guesses') const lastResult = document.querySelector('#lastResult') const lowOrHi = document.querySelector('#lowOrHi') const guessSubmit = document.querySelector('#guessSubmit') const guessField = document.querySelector('#guessField') let guessCount = 1 let resetButton function checkGuess () { let userGuess = Number(guessField.value) if (guessCount === 1) { guesses.textContent = 'Previous guesses: ' } guesses.textContent += userGuess + ' ' if (userGuess === randomNumber) { lastResult.textContent = 'Congratulations! You got it right!' lastResult.style.backgroundColor = 'green' lowOrHi.textContent = '' setGameOver() } else if (guessCount === 10) { lastResult.textContent = '!!!GAME OVER!!!' setGameOver() } else { lastResult.textContent = 'Wrong!' lastResult.style.backgroundColor = 'red' if (userGuess < randomNumber) { lowOrHi.textContent = 'Last guess was too low!' } else if (userGuess > randomNumber) { lowOrHi.textContent = 'Last guess was too high!' } } guessCount++ guessField.value = '' guessField.focus() } guessSubmit.addEventListener('click', checkGuess) function setGameOver () { guessField.disabled = true guessSubmit.disabled = true resetButton = document.createElement('button') resetButton.textContent = 'Start new game' document.body.appendChild(resetButton) resetButton.addEventListener('click', resetGame) } function resetGame () { guessCount = 1 const resetParas = document.querySelectorAll('#resultParas p') for (let rP of resetParas) { rP.textContent = '' } resetButton.parentNode.removeChild(resetButton) guessField.disabled = false guessSubmit.disabled = false guessField.value = '' guessField.focus() lastResult.style.backgroundColor = 'white' randomNumber = Math.ceil(Math.random() * 100) }
30
The game demonstrates several things
accessing the Web page through the Document Object Model fjnding, adding, modifying, and removing elements from the DOM adding event listeners to elements in the DOM, and reacting when triggered
This forms the basis for much of what goes on in interactive Web pages
minus getting data from the server, but we will get to that next
31
Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser
32
The XMLHttpRequest class makes it possible to access Web servers from JavaScript in the Web browser This is crucial functionality, as it allows to retrieve information from Web servers as well as initiating HTTP requests that we cannot do through the location bar
such as invoking the HTTP DELETE method
33
I have added a new route to the Express server, and I have added a ‘Delete’ button to the greetings’ view
… app.delete('/greetings/:id', (request, response, next) => { const id = request.params.id Greetings.delete(id, (err) => { if (err) return next(err) }) }) …
app └── index.js db ├── db.js └── greetings.sqlite public ├── js │ └── main.js └── style └── main.css views ├── form.hbs ├── greeting.hbs └── layouts └── main.hbs{{#each greetings}} <h2>Hello {{recipient}}</h2> <p>{{message}} <button data-id="{{id}}" class="delete">Delete</button></p> {{/each}} <div><a href="/form">Add a greeting</a></div>
34
You may add data-whatever attributes to any element It is a very handy way to insert data for later use into a Web page
35
I create a XMLHttpRequest object with the DELETE method and a URL of the form /greetings/:id
after it has been sent, I force a reload of the page to show the remaining greetings
app └── index.js db ├── db.js └── greetings.sqlite public ├── js │ └── main.js └── style └── main.css views ├── form.hbs ├── greeting.hbs └── layouts └── main.hbs… const greetings = document.querySelectorAll('.delete') function deleteThisGreeting (e) { const requestURL = `${document.URL}/${e.target.attributes['data-id'].value}` const request = new XMLHttpRequest() request.open('DELETE', requestURL) request.send() window.location.replace(document.URL) } if (greetings) { for (let myGreet of greetings) { myGreet.addEventListener('click', deleteThisGreeting) } }
36
37
We have seen that it is easy, using createElement() and appendChild(), to create and add elements to the DOM Using removeChild(), we can remove an element, provided that we know its parent:
parentElement.removeChild(unwantedChild)
So, if we can arrange it so that we do know the parent, things are pretty straightforward
38
I have wrapped all greetings in an IDed <div>, as well as wrapping every greeting in a <div> with an ID of the form ‘greeting-id’ , so it will be easy to fjnd
app └── index.js db ├── db.js └── greetings.sqlite public ├── js │ └── main.js └── style └── main.css views ├── form.hbs ├── greeting.hbs └── layouts └── main.hbs
<div id="greetings"> {{#each greetings}} <div id="greeting-{{id}}"> <h2>Hello {{recipient}}</h2> <p>{{message}} <button class="delete" data-id="{{id}}">Delete</button></p> </div> {{/each}} </div> <div><a href="/form">Add a greeting</a></div>
39
const deleteButtons = document.querySelectorAll('.delete') const greetings = document.querySelector('#greetings') function deleteThisGreetingOnServer (e) { const id = e.target.attributes['data-id'].value const requestURL = `${document.URL}/${id}` const request = new XMLHttpRequest() request.open('DELETE', requestURL) request.send() deleteThisGreetingOnPage(id) } function deleteThisGreetingOnPage (id) { const selector = `#greeting-${id}` const theGreeting = document.querySelector(selector) greetings.removeChild(theGreeting) } if (deleteButtons) { for (let myButton of deleteButtons) { myButton.addEventListener('click', deleteThisGreetingOnServer) } }
40
41
We can dynamically add and delete from the DOM as we see fjt This enables fmexible and responsive Web pages
42
Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser
43
Layout has always been difficult on the Web
for many years, there was not any good support for it
Then CSS came along, and some things improved a lot But, most layout was based on a fmawed assumption: “We know the proportions & size of the users’ displays” Which was true in the PC era, but as smartphones progressed it became increasingly problematic
44
Many layouts involve elements that should adapt to the available space, possibly sharing it in an equitable way with other elements The fmexbox is well suited for
vertically aligning an element within its parent making all children of a container take an equal part of the available space making a multi-column layout where the columns are of equal height
45
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Flexbox Basics</title> <link rel="stylesheet" type="text/css" media="screen" href="style/main.css" /> </head> <body> <header> <h1>Sample flexbox example</h1> </header> <section> <article> <h2>First article</h2> <p>…</p> </article> <article> <h2>Second article</h2> <p>…</p> </article> <article> <h2>Third article</h2> <p>…</p> <p>…</p> </article> </section> </body> </html>
app └── index.js public ├── index.html └── style └── main.css
46
html { font-family: sans-serif; } body { margin: 0; } header { background: purple; height: 100px; } h1 { text-align: center; color: white; line-height: 100px; margin: 0; } article { padding: 10px; margin: 10px; background: aqua; }
app └── index.js public ├── index.html └── style └── main.css
47
html { font-family: sans-serif; } body { margin: 0; } header { background: purple; height: 100px; } h1 { text-align: center; color: white; line-height: 100px; margin: 0; } article { padding: 10px; margin: 10px; background: aqua; } section { display: flex; }
app └── index.js public ├── index.html └── style └── main.css
48
html { font-family: sans-serif; } body { margin: 0; } header { background: purple; height: 100px; } h1 { text-align: center; color: white; line-height: 100px; margin: 0; } article { padding: 10px; margin: 10px; background: aqua; } section { display: flex; flex-direction: column; }
app └── index.js public ├── index.html └── style └── main.css
49
html { font-family: sans-serif; } body { margin: 0; } header { background: purple; height: 100px; } h1 { text-align: center; color: white; line-height: 100px; margin: 0; } article { padding: 10px; margin: 10px; background: aqua; } section { display: flex; flex-direction: row; }
app └── index.js public ├── index.html └── style └── main.css
50
html { font-family: sans-serif; } body { margin: 0; } header { background: purple; height: 100px; } h1 { text-align: center; color: white; line-height: 100px; margin: 0; } article { padding: 10px; margin: 10px; background: aqua; } section { display: flex; } article { flex: 1; } article:nth-of-type(3) { flex: 2; }
app └── index.js public ├── index.html └── style └── main.css
51
A (part of a) page is divided into a grid by numbered lines, and content can then be distributed between the cells within those lines
❶ ❶ ❷ ❸ ❹ ❷ ❸ ❹
52
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Grid Basics</title> <link rel="stylesheet" type="text/css" media="screen" href="style/ main.css" /> </head> <body> <div class="wrapper"> <div class="box">One</div> <div class="box">Two</div> <div class="box">Three</div> <div class="box">Four</div> <div class="box">Five</div> <div class="box">Six</div> </div> </body> </html> app └── index.js public ├── index.html └── style └── main.css
53
This is just as you would expect: the divs naturally expand to fjll the width of the screen
body { background-color: whitesmoke; } div { border: solid 1px saddlebrown; padding: 1em; border-radius: 5px; } .box { background-color: indianred; } .wrapper { } app └── index.js public ├── index.html └── style └── main.css
54
We are now using grid layout, but it is implicitly or automatically created, leaving no discernible visual difference
body { background-color: whitesmoke; } div { border: solid 1px saddlebrown; padding: 1em; border-radius: 5px; } .box { background-color: indianred; } .wrapper { display: grid; } app └── index.js public ├── index.html └── style └── main.css
55
Let’s add some columns…
the layout implicitly/automatically repeats for the remaining divs
.wrapper { display: grid; grid-template-columns: 100px 200px 300px; } app └── index.js public ├── index.html └── style └── main.css
56
Let’s add some different columns…
the layout automatically repeats for the remaining divs
.wrapper { display: grid; grid-template-columns: 200px 300px; } app └── index.js public ├── index.html └── style └── main.css
57
Let’s add both columns and rows
‘fr’ defjnes a fraction of the available space within the enclosing container
.wrapper { display: grid; grid-template-columns: 100px 300px 100px; grid-template-rows: 1fr 2fr } app └── index.js public ├── index.html └── style └── main.css
58
<div class="wrapper"> <div class="header">Header</div> <div class="sidebar">Sidebar</div> <div class="main">Main</div> </div> app └── index.js public ├── index.html └── style └── main.css
.wrapper { border: solid 2px saddlebrown; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); } .header { grid-column-start: 1; grid-column-end: 3; grid-row-start: 1; grid-row-end: 2; background-color: #85BDFD; } .main { grid-column-start: 1; grid-column-end: 3; grid-row-start: 2; grid-row-end: 4; background-color: #66B312; } .sidebar { grid-column-start: 3; grid-column-end: 4; grid-row-start: 2; grid-row-end: 4; background-color: D25EA8; }
❶❶ ❷ ❸ ❹ ❷ ❸ ❹
59
<div class="wrapper"> <div class="header">Header</div> <div class="sidebar">Sidebar</div> <div class="main">Main</div> </div> app └── index.js public ├── index.html └── style └── main.css
.wrapper { border: solid 2px saddlebrown; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr) } .header { grid-column: 1 / 3; grid-row: 1 / 2; background-color: #85BDFD; } .main { grid-column: 1 / 3; grid-row: 2 / 4; background-color: #66B312; } .sidebar { grid-column: 3 / 4; grid-row: 2 / 4; background-color: #D25EA8; }
❶❶ ❷ ❸ ❹ ❷ ❸ ❹
60
<div class="wrapper"> <div class="header">Header</div> <div class="sidebar">Sidebar</div> <div class="main">Main</div> </div> app └── index.js public ├── index.html └── style └── main.css
.wrapper { border: solid 2px saddlebrown; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr) } .header { grid-column: 1 / 3; grid-row: 1 / 2; background-color: #85BDFD; } .main { grid-column: 1 / 3; grid-row: 2 / 4; background-color: #66B312; } .sidebar { grid-column: 3 / 4; grid-row: 2 / 4; background-color: #D25EA8; }
61
While both fmexbox and grid help with general layout and can create some really versatile designs, we are not quite responsive yet We need to take the available display (or ‘viewport’) into account This can be done directly in CSS using the @media() selector
62
app └── index.js public ├── index.html └── style └── main.css
<!DOCTYPE html> <html> <head> <title>Hello!</title> <meta charset="utf-8"> <link rel="stylesheet" href="style/main.css"> </head> <body class="container"> <header>Header</header> <nav>Navigation</nav> <main> <h1>Main</h1> <p>…</p> </main> <aside>Related links</aside> <footer>Footer</footer> </body> </html>
❶❶ ❷ ❸ ❹ ❷ ❸ ❹
63
app └── index.js public ├── index.html └── style └── main.css body { font-family: sans-serif; margin: 0 } .container { display: flex; flex-direction: column; min-height: 100vh; } @media (min-width: 768px) { .container { display: grid; grid-template-columns: 200px 1fr 200px; grid-template-rows: auto 1fr auto; } } header { grid-column: 1 / 4; padding: 30px; text-align: center; background-color: #369; color: white; } main { flex: 1; padding: 20px; } nav { background-color: #f90; padding: 20px; } aside { padding: 20px; background-color: #936; } footer { grid-column: 1 / 4; padding: 30px; text-align: center; background-color: #690; color: white; }
64
65
66
A major breakthrough in the past few years is that it is now possible to specify the layout of a Web page purely in CSS without the use of JavaScript With JavaScript, we add, remove, or modify content With CSS, we style it and adapt to the user’s display
67
Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser
68
Considering the complexities and enormous amount work behind, it is pretty straightforward to display a map in your Web browser I will be using Open Street Maps in my examples, as they are open source
69
Note how we include external JS and CSS resources here: this is quite common when using 3rd party APIs
app └── index.js public/ ├── index.html ├── js │ └── main.js └── style └── main.css <!doctype html> <html lang="en"> <head> <link rel="stylesheet" href="https://cdn.rawgit.com/openlayers/
<link rel="stylesheet" href="style/main.css" type="text/css"> <script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/ master/en/v5.3.0/build/ol.js"></script> <script defer src="js/main.js" type="text/javascript"></script> <title>OpenLayers example</title> </head> <body> <h2>My Map</h2> <div id="map" class="map"></div> </body> </html> .map { height: 400px; width: 100%; }
70
The fjrst line defjnes my home’s position, and then instantiates a new OpenLayer’s Map object set to retrieve map tiles from OpenStreetMap, and a view centred on my humble abode
app └── index.js public/ ├── index.html ├── js │ └── main.js └── style └── main.css const position = [10.155, 56.154] const map = new ol.Map({ target: 'map', layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ], view: new ol.View({ center: ol.proj.fromLonLat(position), zoom: 12 }) })
71
Web browsers support a geolocation API, enabling us to access the user’s actual position (measured using GPS or some other means)
navigator.geolocation.getCurrentPosition()
However, this is sensitive information, and Web browsers will not permit the use of this API on unsecured Web sites So, if we are using HTTP, we are out of luck
72
The document is the root object that represents the document shown on the page The navigator object represents the Web browser and its capabilities You will probably mainly access it to get the user’s position
73
Developing Web sites using HTTPS is quite a bit more labour intensive, as it is necessary to generate server certifjcates, that authenticates the Web server to the user, and enables encryption of the transmitted data We may later go into how you can use Let’s Encrypt’s (free) services to build secure Web sites, but for the purposes of this lecture, we will be using a Node module called ‘pem’
however, it does require a bit of clicking in the Web browser, as they are, justifjably, paranoid about self-signed certifjcates it is ok for testing and development, but not for production
74
app └── index.js public/ ├── index.html ├── js │ └── main.js └── style └── main.css 'use strict' const https = require('https') const express = require('express') const pem = require('pem') const path = require('path') const app = express() const port = 4430 pem.createCertificate({ days: 1, selfSigned: true }, function (err, keys) { if (err) throw err app.use(express.static(path.join(__dirname, ‘../public'))) https.createServer({ key: keys.serviceKey, cert: keys.certificate }, app).listen(4430, () => { console.log(`Listening on https://localhost:${port}/`) }) })
75
app └── index.js public/ ├── index.html ├── js │ └── main.js └── style └── main.css
if ('geolocation' in navigator) { navigator.geolocation.getCurrentPosition(function (position) { const myPosition = [position.coords.longitude, position.coords.latitude] const map = new ol.Map({ target: 'map', layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ], view: new ol.View({ center: ol.proj.fromLonLat(myPosition), zoom: 12 }) }) }) } else { const para = document.createElement('p') para.textContent = 'Geolocation is not available' document.body.appendChild(para) }
76
77