Building a Running Pace Calculator With AMP

Sometimes you need to know how fast you need to run to achieve a personal best time. Previously the way I did this was to search “running pace calculator” and follow and use one of the top results. However, I was doing this almost always on mobile and none of those results are very mobile friendly. There might be good native apps for this, but I’m a fan of the web and don’t want to download an extra app if I can avoid it.

Motivation

Normally you wouldn’t think of AMP as a dynamic web framework, but it recently gained the ability to build dynamic web pages with amp-bind and also now supports some PWA features like Service Worker. I’m a fan of AMP and I thought this would be an awesome project to learn more about those features.

Result

Before going into implementation details, here’s the finished product, live on pacing.run:

Implementation

The setup for this project was super simple. You can view all the code via the source on pacing.run. For hosting I chose Firebase, using the same automatic deploy-on-green method I describe here.

Here’s a quick run through how I used amp-bind in this project. amp-bind works off a global state using AMP.setState(). That means each input can set its own value in the store, like this:

<input type="number"
  name="time-hours"
  min="0"
  max="1000"
  placeholder="0"
  [value]="timeHours"
  on="input-throttled:AMP.setState({ timeHours: event.value })">

The distance shortcut buttons are implemented similarly, setting distance:

<div class="button-set">
  <button on="tap:AMP.setState({distance: 3.1})">5K</button>
  <button on="tap:AMP.setState({distance: 6.2})">10K</button>
  <button on="tap:AMP.setState({distance: 13.1})">Half Marathon</button>
  <button on="tap:AMP.setState({distance: 26.2})">Marathon</button>
</div>

The pace calculation happens when you push the “Calculate” buttons. It gets a little verbose, but it works.

<button on="tap:AMP.setState({
    paceHours: toHours(paceInSeconds(timeHours, timeMinutes, timeSeconds, distance)),
    paceMinutes: toMinutes(paceInSeconds(timeHours, timeMinutes, timeSeconds, distance)),
    paceSeconds: toSeconds(paceInSeconds(timeHours, timeMinutes, timeSeconds, distance)),
  })">
  Calculate Pace
</button>

The functions used above, like timeInSeconds are achieved using amp-bind-macro for re-using calculations. For example:

<amp-bind-macro id="timeInSeconds"
  arguments="paceHours, paceMinutes, paceSeconds, distance"
  expression="(paceHours*3600 + paceMinutes*60 + paceSeconds*1) * distance" />

I also enabled offline loading with amp-install-serviceworker as described here.

Thoughts on AMP as a dynamic web framework

I was surprised how versatile AMP can be when utilizing amp-bind. Anyone with experience using a JavaScript web framework can quickly grok how amp-bind works. I like that it supports a React/Redux-esque global state store and doesn’t require a huge learning curve.

Finishing thoughts

The site is live at pacing.run! I don’t have any future plans for it, but if you have any ideas or feedback I’d love to hear it. Thanks for reading.

Contents (top)