DEV Community

Cover image for Real Compass on mobile browsers with Javascript
Orkhan Jafarov
Orkhan Jafarov

Posted on

Real Compass on mobile browsers with Javascript

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!

Result we're gonna have Alt Text

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>
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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/)
);
Enter fullscreen mode Exit fullscreen mode
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();
Enter fullscreen mode Exit fullscreen mode

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?

  1. We're getting our current geolocation
  2. Define point coordinates (where we should turn to)
  3. Calculate degree from our position to defined point
  4. 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);
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

We're done! We have a real compass in our mobile browsers.

Demo link

Here's a source link

by @gigantz

Discussion (5)

Collapse
emiltoteb profile image
Emil Totev

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?

Collapse
orkhanjafarovr profile image
Orkhan Jafarov Author

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

Collapse
muzairs15 profile image
MUzairS15

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??

Collapse
jespoting profile image
jespoting

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

Collapse
shaikh profile image
Javed Shaikh

This is great. Thanks for sharing 👏👏