Offline, Fullscreenable HTML5 Compass with SVG [Part 1 - Drawing Things]

Link to Demo - Note that you need a device that has a GPS, so you can get orientation data. Here's the source.

Alternatively, if you just want to play with it without a GPS, use the developer console (F12 in Firefox, Chrome, or IE) and set the global variable "direction" to a value in the range [0,360) like so:

direction = 45;  
  • Note: This post assumes that you know something about Javascript, HTML, and CSS.

Plan

I wanted to learn more about how to run a web application offline- in particular, on mobile devices where connectivity is never guaranteed, and where user experience diminishes quickly when a stable network connection is assumed.

I hadn't really explored the idea of full-screen HTML5, which piqued my interest especially with respect to the debate between progressive web apps and native apps.

I also wanted a chance to work with SVG and SVG animations. SVG has the potential to be much faster than its raster graphics counterparts on the web (jpg/png). Additionally, SVG can work in concert with raster textures by manipulating them as vector surfaces, or masking them with vectors.

Finally, I realized I had never directly worked with device orientation in HTML5, and I wanted to do a little bit more than my previous demo, which was a webpage that regurgitated the orientation variables as text.

Overview

First thing I suggest anyone do before they dive into a new technology is RTFM:

DeviceOrientation Specification - Since we're making a compass, we need to know which way is North.

Scalable Vector Graphics - Knowing a little bit about how SVG works will help us make a vector compass.

AppCache - Right now, offline support is transitioning between the Application Cache and ServiceWorker API. However, browser support for Application Cache is much more robust than browser support for Service Workers- so what I'll probably do is update this demo later to use Service Workers and have Application Cache serve as a fallback.

Drawing Things

I should mention that my first draft of the compass came from Inkscape, but that there are a couple of difficulties that I encountered with that approach:

  1. I'm used to a level of precision that I can only really get by editing code by hand. Inkscape has changed a great deal since I last used it, and my difficulty in getting the compass to look the way I wanted it to just proved to me that I need a lot more practice with the tool.

  2. Saving an Inkscape document to an SVG and importing it is problematic. Long story short, if you try to import an SVG using an <img> tag, it becomes much more difficult to access the contents of the SVG from the document. JWatt's getSVGDocument demo is a great resource for this. If you use the <embed> or <object tag, you can technically reach the contents of the SVG in a manner resembling the following:

// embed
var mySvgElement = document.getElementById('mySvgElement');  
var svgDom = mySvgElement.getSVGDocument();  
// object
var svgDom = mySvgElement.contentDocument || mySvgElement.getSVGDocument();  

You also have the option to create inline ECMAScript (which, at the risk of my inbox filling with pedants, is a fancy name for Javascript). Unfortunately, that's a nightmare to debug, as any script failure is simply eaten by the SVG renderer in most contexts.

What ended up happening is that I closed Inkscape and resigned myself to writing inline SVG; I may explore a little later how to best integrate SVG content into the DOM, but I figured it was good practice to start writing things by hand. Plus, I can draw inline with this post.

The Needle.

The needle of a compass is pretty familiar to most people: a long rhombus that's about ten times long as it is wide:

<svg>  
   <polygon id="needleBody" points="50,98 60,50 50,2 40,50" style="fill: black;" />
</svg>  

The <polygon> element accepts a points property that defines sequential points on a Cartesian plane. That means that writing a <polygon> element is a little bit like writing connect-the-dots drawings backwards, while flashing back to High School Geometry.

I started with a 100x100 domain with 2 units of "padding" on each side: It's a little dangerous in SVG to draw up against the edge of your drawing, because you risk something like your stroke radius flying off the edge, as I'll go into detail about later.

It's also important that our needle have a tip that points north, plus a little tack to hold it in the center. Just to remind everyone, we'll label north N as well:

<svg>  
  <polygon id="needleBody" points="50,98 60,50 50,2 40,50" style="fill: black;"/>
  <polygon id="needleTip" points="50,2 55,25 45,25" style="fill: red;" />
  <text x="50" y="20" font-family="Arial" font-size=".5em" text-anchor="middle">
   N
  </text>
  <circle id="needleCenter" cx="50" cy="50" r="3" style="fill: white;" />
</svg>  

Remember here that (x,0) is the top of our drawing: SVG is like a vertically flipped High School Geometry system in which the Y-axis is backwards and positive-Y points down, but positive-X is still to the right.

N

Now we want to express that this needle is a single fixed object. We do that by "grouping" the object using the <g> element. After we do this, I can isolate the needle from a "compass" group representing the face of my compass. Right now, I'm going to keep it simple and make my compass face a white circle with a black outline.

Finally, because we want the SVG to scale to fit its container, we set the viewBox property. viewBox tells the renderer where our origin is (usually 0,0) and what our width and height should be. Because I initially decided this was 100x100, I make the viewBox="0, 0, 100, 100" corresponding to centerX, centerY, width, height respectively.

<svg viewBox="0, 0, 100, 100">  
  <g id="compass">
   <circle id="compassBody" cx="50" cy="50" r="48" style="fill: white; stroke: black;" />
  </g>
  <g id="needle">
    <polygon id="needleBody" points="50,98 60,50 50,2 40,50" style="fill: black;"/>
    <polygon id="needleTip" points="50,2 55,25 45,25" style="fill: red;" />
    <text x="50" y="20" font-family="Arial" font-size=".5em" text-anchor="middle">
     N
    </text>
    <circle id="needleCenter" cx="50" cy="50" r="3" style="fill: white;" />
  </g>
</svg>  
N

At this point, we have the bare minimum to create a recognizable compass... but it doesn't do anything! In the next post, I'll discuss how to make it do some work for you.

How to Cite Republished Information

Do not use this information as legal advice.
If you need advice, call or email a licensed attorney.
No attorney-client relationship is created by your use of this site, including any communication with the author in comments.