webhack / ウェブ技術が好き

javascriptやcssやHTML5とかサーバーサイド等のウェブ技術全般を好きに書くブログ

夏だしダイエットしたいから痩せれるウェブアプリを作った

f:id:tkosuga:20160118190902j:plain 写真はうちのみーこ。

今年も夏がやってきました。毎年この季節になると思うことはなんでしょうか?

そう、ダイエットですよね。

海にプールにフェスにビアガーデンと夏特有のイベントがあるたびに痩せなきゃ!と気を引き締めるのですが3日後にはすっかり忘れているものです。

毎日のカロリーバランスを消費 > 摂取にすれば自然と痩せられると頭では分かっていても実現するのが難しい。

そんな自分のために、毎日のカロリーバランスを記録して摂取カロリーが消費カロリーを上回らないようにするウェブアプリを開発しました。

tkosuga.jp

使い方はかんたん、摂取カロリーを毎日入力するだけです。

はじめて使うと使い方説明のツアーが始まるので、説明を見て進めるだけで利用開始できます。

このカロリーを記録するダイエット方法をレコーディングダイエットと呼びます。数年前に流行りましたよね。

基礎代謝量と生活強度から日常生活で自然消費されるカロリーを計算するので、摂取カロリーが消費カロリーを上回らないよう生活に気を付けて暮らせば自然と痩せて行く皮算用です。

さらにこのウェブアプリではダイエットの成果をはちみつ・鮭・りんご・どんぐり・おにぎり換算で共有する機能もあるので、痩せてきたら友達に鮭x匹分も痩せたよ!と自慢しましょう。

f:id:tkosuga:20160726214740p:plain ※キャプチャ1「おにぎり40個分のダイエットに成功したよ!やったね!」

f:id:tkosuga:20160726214746p:plain ※キャプチャ2「鮭4匹相当のダイエットに成功だね!すごいね!」

データ保持について

データは全てブラウザ内に保存しています。またダイエットレポートを共有する場合を除き、データを外部に転送・保存など通信していません。複数ユーザーでPC/スマホを共有していてブラウザも共有している場合、共有している他ユーザーに見られてしまう可能性がありますのでご注意下さい。

※プライバシーに関する情報(基礎代謝計算のための体重・年齢・性別)はダイエットレポートの共有および移行データに含まれません。

個人的なダイエット成果と開発の動機

ぼくはレコーディングダイエットのくまスリムの開発を始めた=個人で使い始めた6月~7カ月末の2カ月で80kgから74kgの6kgのダイエットに成功しました。

ダイエット開始初日はスマホのメモアプリで摂取カロリーと消費カロリーを記録して、電卓アプリで合算してカロリーコントロールしていたのですが、2日目には早くも辛くなってきたので以下の3つ要件を満たしたアプリを自作する事にしました。

  1. スマホでお手軽に使える。
  2. ボタンを押すだけでカロリーコントロールできる。
  3. モチベーションを維持できる。

要件を満たしていく中で幾つかの課題を抽出できました。以下解決した課題とその解決方法です。

  1. アプリにするとインストール面倒、保守も面倒
    • 解決方法
      • ブラウザだけで使えるウェブアプリにする。
  2. 読み込みや反応が遅いとストレス
    • 解決方法
      • 外部リソースをとにかく減らす。
      • 動作をとにかく高速化する。
      • AdBlock/コンテンツブロッカー環境で動作するようにする。
  3. 痩せたら褒められたい
    • 解決方法
      • はちみつ・鮭などの謎単位で何だか凄い感を演出。
      • SNS共有機能を付ける(LINE/Facebook/Twitter)。
  4. ポジティブな数字しか見たくない
    • 解決方法
      • 目標値やノルマを表示しない。
      • 現在の体重とか見える所に表示しない。
  5. メンテナンスに苦労したくない
    • 解決方法
      • サーバーサイドを使わずHTMLで完結する形にする。
      • Node.js + gulpでビルドを自動化。
  6. お金かけたくない
    • 解決方法
  7. セキュリティ/プライバシーで苦労したくない
    • 解決方法
      • HTTPSの導入
      • ウェブストレージの利用。サーバーサイドで保存しない。
      • 生年月日にニックネーム入力等をしない。

技術的な情報

ここからツール紹介からがらっと変わって技術的な情報です。

StartSSL

本アプリのドメイン(tkosuga.jp)には無料で使えるStartSSLを利用しています。

StartSSL™ Certificates; Public Key Infrastructure

1アカウントで上限5つのサーバー証明書を無料で発行できます。企業情報や個人事業主の情報を入力する必要がありません。クラス1の証明書です。

f:id:tkosuga:20160726223445p:plain

※上記キャプチャはhttps://cryptoreport.websecurity.symantec.com/checker/の結果

せっかくなのでSSL脆弱性診断(SSL Server Test)を試して見ます。Fか。

f:id:tkosuga:20160726224058p:plain

※上記キャプチャはhttps://www.ssllabs.com/ssltest/の結果

SSLハンドシェイクの欄を見るとStartSSLは昔のブラウザに対応していないんですね。

f:id:tkosuga:20160726224307p:plain

※上記キャプチャはhttps://www.ssllabs.com/ssltest/の結果

さくらレンタルサーバーでのSSL転送

本アプリはさくらレンタルサーバーで動作しているのですが、SSLを有効にするとサーバーにはHTTPSが解除された状態で転送されます。リバースプロキシを経由してレンタルサーバーに辿りついているようです。

そのため.htaccess等で「httpならhttpsに転送する設定」をするとリダイレクトループが起こります。これを回避するにはさくらレンタルサーバー特有のカスタムHTTPヘッダに付与されているX-Sakura-Forwarded-Forを利用します。

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTP:X-Sakura-Forwarded-For} ^$
  RewriteRule ^(.*)$ https://tkosuga.jp%{REQUEST_URI} [R=301,L]
</IfModule>

SVG

本アプリはPNG/JPEG画像等(ラスター画像)を使わずにインラインSVGベクター画像)を利用しています。以下のアイコンは全てインラインSVGです。

  • タイトルロゴ(くまのアイコン)
  • カロリー換算(はちみつ・鮭・おにぎり等)
  • 機能アイコン(Font Awesomeアイコン)

SVGアニメーション

ロゴアイコンのアニメーションにhttp://maxwellito.github.io/vivus/を利用しています。

CodePenにロゴアイコンとVivusを使ったデモを設置しました。

codepen.io

Vivusを使うと下のコードだけでSVGアウトラインのアニメーションができます。100msecでポーリングしながら開始と終了を延々とループさせています。

var animationVector = 1;
var vivus = new Vivus('app-icon-svg', {}, function() {
  $('#app-icon-svg').removeClass('app-icon-svg');
});
setInterval(function() {
  if (vivus.getStatus() == 'start') {
    $('#app-icon-svg').addClass('app-icon-svg');
    vivus.reset().play(animationVector = 1);
  } else if (vivus.getStatus() == 'end') {
    $('#app-icon-svg').addClass('app-icon-svg');
    vivus.stop().play(animationVector = -1);
  }
}, 100);

Font AwesomeをSVGに置き換え

Font Awesomeのフォントアイコンを利用するには以下HTMLのように「fa fa-icon-name」とします。お馴染みの形式ですね。

<i class="fa fa-cogs"></i>

本アプリではFont Awesomeのフォントファイルを使わず全てSVGを利用するようにしたので以下のようにしてFont Awesomeのアイコンを表示しています。

<i class="fa-svg"><%- include("font-awesome-svg/cogs.svg") %></i>

CSSでは以下のように文字幅に合わせてSVGの横幅を指定し、横幅に合わせてSVGの高さ上限を指定しています。納得でしょ。

.fa-svg svg {
  color: $text-color;
  display: inline-block;
  max-height: 1.1rem;
  width: 1.1rem;
}

rem単位(root em)についてはMozillaの説明が分かり易いです。

developer.mozilla.org

remは2016年現在の主要ブラウザでサポートされているので利用して問題ないと思います。

http://caniuse.com/#feat=rem

Font-AwesomeアイコンのSVG化には以下を利用しています。

github.com

ウェブストレージ利用で注意した点

本アプリではダイエットデータの保存にWeb Storage APIを利用しています。

developer.mozilla.org

ウェブストレージは便利な反面、もしブラウザに脆弱性が見つかりその範囲にウェブストレージが含まれていればウェブストレージに保管されている重要な情報が漏えいする可能性があります。

そのためダイエットアプリでお約束になっている生年月日の入力・保存といった個人の特定に繋がる情報(他には姓名やニックネーム)を保存しないようにしています。

ウェブストレージ利用のサンプルコード

以下のコードはウェブストレージAPIを使ってハッシュマップをsave/loadするサンプルです。JSON#parseとJSON#stringifyでシリアライズを実現しています。

  function loadFromStorage() {
    return JSON.parse(storage.getItem("options"));
  }
  function saveToStorage(options) {
    storage.setItem("options", JSON.stringify(options));
  }
  var storage = localStorage;
  var options = loadFromStorage();
  saveToStorage(options);

共有するデータのシリアライズ+圧縮

データ共有の仕組みにはウェブストレージに保存しているデータをシリアライズしURLに付与して、他ブラウザで開いた時のそのURLからデータを復元する方法を取っています。

このURLに付与するシリアライズされた文字列を生成するのをブラウザ内のJavascriptで行うのに lz-string を利用しています。ものすごく便利なライブラリです。

github.com

以下lz-string を利用をしたサンプルコードです。文字列をURIセーフな形に圧縮して復元しています。

const lzstring = require('lz-string');
const string = "stringstringstring";
const compressedString = lzstring.compressToEncodedURIComponent(string);
lzstring.decompressFromEncodedURIComponent(compressedString);

短縮URL

データ共有のための短縮URLにはGoogle URL Shortener APIを利用しています。

developers.google.com

ここだけサーバーサイドでrubyCGIを利用しています。

require 'net/http'
require 'net/https'
require 'yaml'
require 'cgi'

def shorten(original_url)
  req = Net::HTTP::Post.new("/urlshortener/v1/url?key=#{API_KEY}", initheader = {'Content-Type' => 'application/json'})
  req.body = %|{"longUrl":"#{original_url}"}|
  https = Net::HTTP.new("www.googleapis.com", 443)
  https.use_ssl = true
  https.verify_mode = OpenSSL::SSL::VERIFY_NONE
  res = https.start{|http|
    http.request(req)
  }
  hash = YAML.load(res.body)
  hash['id']
end
shorten(URI.unescape(cgi.params['u'].first))

毎日のカロリー変動を見るラインチャート

MetricsGraphics.jsを利用しています。

metricsgraphicsjs.org

CodePenにデモを準備しました。見やすくするため線と文字を太く大きくしています。

codepen.io

四方の余白と高さを指定して2本の線で描画するデータを渡すとキレイなラインチャートが表示されます。

MG.data_graphic({
  data: [intakeKcals, usedKcals],
  width: $(".header").width(),
  height: 90,
  left: 30,
  right: 40,
  top: 10,
  bottom: 30,
  target: ".chart-daily-kcal",
  x_accessor: 'date',
  y_accessor: 'value',
  legend: ['摂取kcal', '消費kcal'],
  colors: ['#e80703', '#909090'],
  //show_secondary_x_label: false,
  //show_confidence_band: ['l', 'u'],
  //x_extended_ticks: true,
});

その日のカロリー摂取量を見るパイチャート

日付の横にある小さいチャートには Peity を利用しています。

http://benpickles.github.io/peity/

CodePenのデモを準備しました。

codepen.io

このチャートライブラリはとても分かり易くて便利で、使い方は以下の通りです。直感的でシンプルですよね。

<span class="pie">1/5</span>
$(function() {
  $("span.pie").peity("pie")
);

スマホのクリックを速くするfastclick

github.com

fastclickを利用してスマホでボタンを押したときの反応速度を向上させています。何をしているのかなと思ってこのライブラリの実装を見てみるとclickやtouchstartのEventListenerを入れ替えたりイベントの伝播に変更を加えたりしています。

あと残りの説明

長くなってきたのでアプリ内で利用していて説明していないライブラリを列挙します。

まとめ

お手軽に使えるダイエットアプリ作ったので使って見てね!

tkosuga.jp

ぼくはこのウェブアプリでダイエットに成功、さらにフロントエンドとNode.js/gulpと今風のサイト高速化にSVGの勉強もできたので万々歳です。

意見要望があれば気軽にブログのコメントやTwitter宛てにコメントをください。