Well, one day I had a challenge from my muslim friend to code a map that's gonna show an arrow from his current geolocation to Qibla or any geopoint.
That wasn't the best solution, cause a compass will solve it better way and make people's life easier. So, I've started to find any package/lib to put the compass into his webpage.
Found these solutions Compass.js or this one, but none of them work at all well. Cause last commits were 6-7 years ago.
Let's make own real compass for mobile browsers!
We will need several html elements.
<div class="compass">
<div class="arrow"></div>
<div class="compass-circle"></div>
<div class="my-point"></div>
</div>
<button class="start-btn">Start compass</button>
Let's add css for that
.compass {
position: relative;
width: 320px;
height: 320px;
border-radius: 50%;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
margin: auto;
}
.compass > .arrow {
position: absolute;
width: 0;
height: 0;
top: -20px;
left: 50%;
transform: translateX(-50%);
border-style: solid;
border-width: 30px 20px 0 20px;
border-color: red transparent transparent transparent;
z-index: 1;
}
.compass > .compass-circle,
.compass > .my-point {
position: absolute;
width: 80%;
height: 80%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition: transform 0.1s ease-out;
background: url(https://cdn.onlinewebfonts.com/svg/img_467023.png) center
no-repeat;
background-size: contain;
}
.compass > .my-point {
opacity: 0;
width: 20%;
height: 20%;
background: rgb(8, 223, 69);
border-radius: 50%;
transition: opacity 0.5s ease-out;
}
JavaScript time!
Define our html elements first and add event for button that starts it.
iOS needs to have manipulation by user to start DeviceOrientationEvent
, but for Android it works without it.
const compassCircle = document.querySelector(".compass-circle");
const startBtn = document.querySelector(".start-btn");
const myPoint = document.querySelector(".my-point");
let compass;
const isIOS = !(
navigator.userAgent.match(/(iPod|iPhone|iPad)/) &&
navigator.userAgent.match(/AppleWebKit/)
);
function init() {
startBtn.addEventListener("click", startCompass);
}
function startCompass() {
if (isIOS) {
DeviceOrientationEvent.requestPermission()
.then((response) => {
if (response === "granted") {
window.addEventListener("deviceorientation", handler, true);
} else {
alert("has to be allowed!");
}
})
.catch(() => alert("not supported"));
} else {
window.addEventListener("deviceorientationabsolute", handler, true);
}
}
function handler(e) {
compass = e.webkitCompassHeading || Math.abs(e.alpha - 360);
compassCircle.style.transform = `translate(-50%, -50%) rotate(${-compass}deg)`;
}
init();
Done! Our compass is working for both iOS and Android.
Upgrade our compass to reach the goal
On this step we need to find correct angle/degree to our point (Qibla).
We put the point coordinates and calculate degree from out current geolocation.
How it works?
- We're getting our current geolocation
- Define point coordinates (where we should turn to)
- Calculate degree from our position to defined point
- Display point when we're in correct position
Define pointDegree
and our functions for this.
let pointDegree;
function locationHandler(position) {
const { latitude, longitude } = position.coords;
pointDegree = calcDegreeToPoint(latitude, longitude);
if (pointDegree < 0) {
pointDegree = pointDegree + 360;
}
}
function calcDegreeToPoint(latitude, longitude) {
// Qibla geolocation
const point = {
lat: 21.422487,
lng: 39.826206,
};
const phiK = (point.lat * Math.PI) / 180.0;
const lambdaK = (point.lng * Math.PI) / 180.0;
const phi = (latitude * Math.PI) / 180.0;
const lambda = (longitude * Math.PI) / 180.0;
const psi =
(180.0 / Math.PI) *
Math.atan2(
Math.sin(lambdaK - lambda),
Math.cos(phi) * Math.tan(phiK) -
Math.sin(phi) * Math.cos(lambdaK - lambda)
);
return Math.round(psi);
}
We put our location handler into init
function to listen Geolocation API. Add some code to handler
that's gonna update our point state.
function init() {
startBtn.addEventListener("click", startCompass);
navigator.geolocation.getCurrentPosition(locationHandler);
}
function handler(e) {
compass = e.webkitCompassHeading || Math.abs(e.alpha - 360);
compassCircle.style.transform = `translate(-50%, -50%) rotate(${-compass}deg)`;
// ±15 degree
if (
(pointDegree < Math.abs(compass) && pointDegree + 15 > Math.abs(compass)) ||
pointDegree > Math.abs(compass + 15) ||
pointDegree < Math.abs(compass)
) {
myPoint.style.opacity = 0;
} else if (pointDegree) {
myPoint.style.opacity = 1;
}
}
We're done! We have a real compass in our mobile browsers.
Demo link
Here's a source link
by @gigantz
Discussion (3)
Hey, thank you for the great tutorial! I've got it working flawlessly on iOS, but there appears to be an issue on Android - North is always in the direction the phone is pointing when the page loads, and if you're not looking north, all the directions are miscalculated. Have you faced this issue and were you able to overcome it?
I hope your Android device was using GPS with high accuracy mode. If not it will not work as expected. Try to get or debug heading value from geolocation or compassHeading. I didn’t faced this issue on Android
This is great. Thanks for sharing 👏👏