loading...
Cover image for Real Compass on mobile browsers with Javascript

Real Compass on mobile browsers with Javascript

gigantz profile image Orkhan Jafarov ・3 min read

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>

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?

  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);
}

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

Posted on by:

Discussion

markdown guide