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
Top comments (9)
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
Can someone help me understand this code, why are we calculating pointdegree and all, we can do only with e.webkitcompassheading (this will give the required movement from north position ) isn't it??
And also we are transforming compass with value of 'compass' so what purpose does other functions serve??
This is great. Thanks for sharing ππ
Thanks a lot for this, I do have some problems with it though:
for some reason, the compass keeps "jumping" around. On first load north is determined accurately, but with a few of those "jumps" it is lost.
One more ting: turning my mobile 180Β° when pointing north does not turn exactly to south, but quite bit more.
Any clue what might cause this behaviour?
Edit: I did recalibrate my device's compass, no change.
Hi, it worked perfectly for me. I would like to ask a question to see if you can help me.
How could I in this same project if I pass by GET both the longitude and latitude of the destination to update it every 5 seconds.
That is to say:
1- Instead of having the latitude and longitude defined in the variable, that the longitude and latitude were passed by get and update every 5 seconds the latitude and longitude in case it has moved.
Thanks in advance
Hi, thanks, I tried getting chatgpt to create something like this, unsuccessfully. This is exactly what I wanted it to do. Making an app for paragliding pilots, our Qibla is our local landing zone ;)
ws.paraguide.in
github.com/cyberorg/ParaguideWS-web
When the compass points north, it spins 360 degrees each time. Is there any way to stop that behavior?
Can any body help me out the compass is not rotating On IOS but it works fine on Android .
Even it gives the heading value but not rotating on ios.