Many websites and applications depend on the HTML5 Geolocation API, which provides the location information of the user, in the form of latitude and longitude. It’s often used alongside a map, providing features such as a local store finder. In this guide, we’ll start with a simple implementation, then extend it to support advanced functionality with a great user experience.
Finding a user’s position in Javascript
The HTML5 geolocation API has only one object – the navigator.geolocation object and exposes 3 methods getCurrentPosition(), watchPosition(), and clearWatch().
Let’s start with a simple implementation that will display the user’s coordinates on a webpage.
<p>Locating you...</p>
<script>
const p = document.querySelector('p');
function onError() {
p.textContent = 'Unable to locate you'
}
function onSuccess(position) {
p.textContent = `Latitude: ${position.coords.latitude} °, Longitude: ${position.coords.longitude} °`;
}
if (!navigator.geolocation) {
// This browser doesn't support Geolocation, show an error
onError();
} else {
// Get the current position of the user!
navigator.geolocation.getCurrentPosition(onSuccess, onError);
}
</script>
To make this more visually appealing, let’s update our script to plot the location on a map, using Google Maps.
<div id="map" style="height: 400px; width: 700px"></div>
<script>
const map = document.querySelector("#map");
function initMap() {
const googleMap = new google.maps.Map(map, {
center: { lat: 0, lng: 0 },
zoom: 1
});
function onError() {
map.textContent = "Unable to locate you";
}
function onSuccess(geo) {
const position = {
lat: geo.coords.latitude,
lng: geo.coords.longitude
};
// Reposition the map to the detected location
googleMap.setCenter(position);
googleMap.setZoom(12);
// Add a marker
new google.maps.Marker({ position, map: googleMap });
}
if (!navigator.geolocation) {
onError();
} else {
navigator.geolocation.getCurrentPosition(onSuccess, onError);
}
}
</script>
<script
async
defer
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"
></script>
Now, we have a map which shows the user where they’re located.
Watching for updates
A users location can easily change whilst they’re on the page, for example they could be travelling. With our current implementation, the user would need to refresh to update, but the Geolocation API provides a watchPosition function for exactly this situation. Simply change the function to
navigator.geolocation.watchPosition(onSuccess, onError);
Checking Browser Support
Check whether HTML5 Geolocation is supported;
if (navigator.geolocation) {
console.log('Geolocation is supported!');
}
else {
console.log('Geolocation is not supported for this Browser/OS.');
}
Reverse geocoding – more than just coordinates
There are many use-cases when it’s useful to have the user’s address, rather than just their geoposition. There are a few options, such as the Google Geocoding API or Geocode.xyz. For this example, we’ve chosen Geocode.xyz. We’re going to move away from the map example and use reverse geocoding to auto-fill some address details. This is commonly used by navigation services, such as Citymapper.
<pre>Locating you...</pre>
<script>
const address = document.querySelector("pre");
function onError() {
address.textContent = "Unable to locate you";
}
async function onSuccess(position) {
const geocode = await fetch(`https://geocode.xyz/${position.coords.latitude},${position.coords.longitude}?json=1`);
const geoResponse = await geocode.json();
address.textContent = `${geoResponse.stnumber} ${geoResponse.staddress}
${geoResponse.city}
${geoResponse.postal}
${geoResponse.country}`;
}
if (!navigator.geolocation) {
onError();
} else {
navigator.geolocation.getCurrentPosition(onSuccess, onError);
}
</script>
Reverse geocoding can be exceptionally accurate – it’s often able to get the exact street address. Due to this, it’d be very useful for ecommerce stores or any website which requires the location of the user for shipping.
Failure cases
Many users will not allow websites to access their current location on page load – they’ve been given no indication as to why you’re requesting it. Therefore, it’s best to delay requesting geolocation until it’s required, or when a user explicitly asks for that functionality by clicking a “Use my current location” button.
Even with these improvements, some users will always reject usage of their location. Additionally, some older browsers don’t support the Geolocation API at all, so what can we do to improve their experience? ipdata.co provides an API to lookup a user by their IP address, so we can fall back to a slightly less accurate location for these users.
<p>Locating you...</p>
<script>
const p = document.querySelector("p");
async function onError() {
// Fall back to ipdata.co response
const ipdataResponse = await fetch("https://api.ipdata.co?api-key=test");
const ipdata = await ipdataResponse.json();
// Call our onSuccess handler with the location
onSuccess({
coords: {
latitude: ipdata.latitude,
longitude: ipdata.longitude
}
});
}
function onSuccess(position) {
p.textContent = `Latitude: ${position.coords.latitude}, Longitude: ${position.coords.longitude}`;
}
if (!navigator.geolocation) {
// This browser doesn't support Geolocation, show an error
onError();
} else {
// Get the current position of the user!
navigator.geolocation.getCurrentPosition(onSuccess, onError);
}
</script>
This solution works very well, and gives all of our users get a good experience, regardless of whether they allow location.
The HTML5 Geolocation API returns 3 error codes according to the Documentation;ValueAssociated constantDescription1PERMISSION_DENIEDThe acquisition of the geolocation information failed because the page didn't have the permission to do it.2POSITION_UNAVAILABLEThe acquisition of the geolocation failed because one or several internal sources of position returned an internal error.3TIMEOUTThe time allowed to acquire the geolocation, defined by PositionOptions.timeout information that was reached before the information was obtained.
Progressive Enhancement
We can take this solution one step further. Instead of waiting for the user to accept or deny location access, we can start with their ipdata location and enhance it with their exact geoposition afterwards. With this, we can build a map which shows a rough location and allow users the option to improve it.
<div id="map" style="height: 400px; width: 700px"></div>
<button id="enhance">Improve location accuracy</button>
<script>
const map = document.querySelector("#map");
async function initMap() {
const googleMap = new google.maps.Map(map, {
center: { lat: 0, lng: 0 },
zoom: 1
});
function onSuccess(geo) {
const position = {
lat: geo.coords.latitude,
lng: geo.coords.longitude
};
// Reposition the map to the detected location
googleMap.setCenter(position);
googleMap.setZoom(12);
// Add a marker
new google.maps.Marker({ position, map: googleMap });
}
// Fetch ipdata location and update the map
const ipdataResponse = await fetch("https://api.ipdata.co?api-key=test");
const ipdata = await ipdataResponse.json();
onSuccess({
coords: {
latitude: ipdata.latitude,
longitude: ipdata.longitude
}
});
// Listen for clicks on the "Enhance" button and use Geolocation API when clicked
document.querySelector("#enhance").addEventListener("click", () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(onSuccess);
}
});
}
</script>
<script
async
defer
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCKGRx32IU_tJgtGea2En9kwLsAOtCmE2I&callback=initMap"
></script>
Conclusions
The Geolocation API is extremely powerful and the possibilities for exciting features are limitless, but it’s also very intrusive to users as they need to explicitly allow access. The first question, whenever thinking about using geolocation, should be “Do I really need to?”. If you’re able to use a non-intrusive method, such as using ipdata, then users will often have a better experience. If you need to use a more exact location, then giving users more control of their privacy is also preferable – communicate why you need access and let them opt-in to using the Geolocation API.