喫煙管理アプリ「Smoking Note」のアップデートを行いました。
V1.2.5 アップデート内容
- 誤入力データの取消機能追加
- 表示文字やエフェクトなどのブラッシュアップ
設定画面の「いっぷく時間の取消」ボタンから直前の記録を削除できるようになりました。連続タップや誤入力をしてしまっても元に戻すことができます。
また、メイン画面で使用している文字や時間更新エフェクト、グラフ画面の表の色などを少し変更しました。
アプリ開発ブログ
アプリの起動がみょーに遅くて、どこで時間がかかっているのか調べたくなることってありますよね。私はしょっちゅうあります。そこで、JavaScriptで処理時間を計測する方法について調べました。メジャーな方法は次の2つのようです。
それぞれについて簡単に説明します。
console.time()メソッドによりタイマーを開始し、console.timeEnd()メソッドでタイマーを停止してその間の経過時間をミリ単位で取得します。引数はタイマーを選別する任意の名前になります。
console.time() — MDN
console.timeEnd() — MDN
使用例
console.time("timer_0");
alert("click!");
console.timeEnd("timer_0");
console.timeEnd()メソッドを実行すると経過時間がコンソールに出力されます。
ここで、戻り値がどうなっているのか気になって実験してみました。
実験
var a = console.time("timer_0");
alert("click!");
var b = console.timeEnd("timer_0");
console.log("a: " + a + ", b: " + b);
コンソール出力
戻り値はundefinedでした。取得した経過時間をUIに表示したりプログラムで使うことはできないみたいです。console()メソッドですから当然といえば当然ですかね。
Date()コンストラクタからインスタンス化した日付オブジェクト(new Date())のgetTime()メソッドは、1970年1月1日00:00:00からインスタンス化までの時間をミリ秒で返します。処理時間は、計測開始時と終了時にgetTime()メソッドでミリ秒を取得し、その差分をとることで得ることができます。
ちなみに、インスタンス化せずに直接Dateオブジェクトのnow()メソッドを使用してもgetTime()メソッドと同じ結果になります。ただしIE9未満で未サポートになっています。
使用例
var stime = new Date().getTime();
for(var i=0, a=0; i<100000; i++) a += i;
var etime = new Date().getTime();
console.log("time: " + (etime - stime));
処理時間を計測したいポイントが複数ある場合、各ポイントにgetTime()の実行と取得した値を格納するコードを埋め込み、最後にループ文などで差分を計算して表示する、というパターンになるかと思います。そこで時間計測用オブジェクトを作成してみました。
Script
var timeLog = (function() {
var timeLog = {};
var memory = [];
timeLog.rec = function(comment) {
memory.push({
comment: comment || "",
time: new Date().getTime()
});
};
timeLog.show = function(flg) {
var n = memory.length;
if (n<=1) {
console.log("Too few records");
return;
}
var stime = memory[0].time, etime, delta, log, logs="";
for (var i=1; i<n; i++) {
etime = memory[i].time;
delta = etime - stime;
stime = etime;
log = "[" + i + "] " + memory[i].comment + ": " + delta + "ms";
logs += log + "\n";
console.log(log);
}
if (flg) window.alert(logs);
};
timeLog.clear = function() {
memory.length = 0;
};
return timeLog;
})();
使用例
timeLog.rec("start");
for(var i=0, a=0; i<100000; i++) a += i;
timeLog.rec("一回目の処理");
for(var i=0, a=0; i<1000000; i++) a += i;
timeLog.rec("二回目の処理");
for(var i=0, a=0; i<10000000; i++) a += i;
timeLog.rec("最後の処理");
timeLog.show();
コンソール出力
一連の処理をまとめただけなのでその必要性が微妙なのですが、使用感はすっきりさわやかです。また、show()の引数にtrueなどを渡して呼び出すとアラートダイアログを表示します。release版のapkなどで確認する場合に使えると思います。
端末を回転させたとき、アプリを見やすくするために要素の配置やサイズを変えたり、要素を非表示にしたいことがあるかと思います。そこで、Android端末の回転処理について調べました。
端末を回転させて横置きにすると回転に対応しているアプリでは横幅に合わせて文字や画像が拡大されたりしますが、そもそもアプリを回転させるメリットがない場合は、回転させないようにしたいところです。
Monacaの場合はconfig.xmlに以下の記述をすることでアプリの向きを固定することができます。
// 縦置き <preference name="Orientation" value="portrait"> // 横置き <preference name="Orientation" value="landscape">
CSSのorientationメディアクエリで端末の回転を検出することができます。
メディアクエリ — MDN
メディアクエリの利用 — MDN
例)縦置きのときツールバーを表示し、横置きのときツールバーを非表示にする
HTML
<ons-toolbar id="tinytoolbar"> ... </ons-toolbar>
CSS
@media screen and (orientation: portrait) { // 縦置き
#tinytoolbar {
display: flex;
}
}
@media screen and (orientation: landscape) { // 横置き
#tinytoolbar {
display: none;
}
}
Note
ソフトキーボードが起動すると、その高さの分だけコンテンツが縮小します。このため縦置きの状態でもlandscapeのメディアクエリが適用されることがあります。個人的にこれはネックになりやすいのではと思います。
windowオブジェクトのorientationプロパティは、デバイスの本来の向きに対するビューポートの向きを表します。取得できる値は [-90, 0, 90, 180] とのことです。
原文(英語)— WHATWG
日本語翻訳版 — WHATWG
Can i useに掲載されていないのがちょっと謎です。window.screenは掲載されているのですが、現在AndroidやiOSでは対応していないようです。
端末を回転させるとorientationchangeイベントとresizeイベントが発火するようです。
Script
window.addEventListener("orientationchange", function(e) {
console.log("orientationchange");
});
window.addEventListener("resize", function(e) {
console.log("resize");
});
テスト用のapkを構築してAndroid端末で動かしてみることにしました。
調べたいこと
HTML
<body> <ul id="results" contenteditable="true"></ul> </body>
CSS
#results {
margin: 0;
padding: 4px;
}
li.orient { background-color: orange; }
li.resize {}
li.start { background-color: yellow; }
Script
var count = 0;
ons.ready(function() {
console.log("Onsen UI is ready!");
print(-1, check());
});
window.addEventListener('orientationchange', function(e) {
print(0, check());
});
window.addEventListener('resize', function(e) {
print(1, check());
});
function check() {
var result = (count++) + ">";
result += " i(" + window.innerWidth + "," + window.innerHeight + ")";
result += " o(" + window.outerWidth + "," + window.outerHeight + ")";
result += " s(" + screen.width + "," + screen.height + ")";
result += " *(" + window.orientation + ")";
return result;
}
function print(typ, msg) {
var results = document.getElementById("results");
var li = document.createElement("li");
switch (typ) {
case 0: li.className = "orient"; break;
case 1: li.className = "resize"; break;
case -1: li.className = "start"; break;
default: break;
}
li.appendChild(document.createTextNode(msg));
results.appendChild(li);
}
テストプログラムでは以下の3か所で値を取得し表示します。
黄色:起動時
白:resizeイベント
橙:orientationchangeイベント
端末はAndroid 4.4.2を使用しています。テストに先立ち、chromeインスペクタでinnerWidth/innerHeightの値を調べました。
| 縦置き | 360 × 567 |
|---|---|
| 横置き | 598 × 335 |
ソフトキーボードを表示した状態では以下のようになりました。縦置きでも(幅 > 高さ)になっています。
| 縦置き | 360 × 341 |
|---|---|
| 横置き | 598 × 151 |
(1) 端末を縦置きにしてアプリを起動
0 > orientation == 0
(2) 横置きに回転させる
1 > resizeイベント発火 orientation == 0
2 > resizeイベント発火 orientation == 0
3 > orientationchangeイベント発火 orientation == 90
(3) 縦置きに戻す
4 > resizeイベント発火 orientation == 90
5 > resizeイベント発火 orientation == 90
6 > orientationchangeイベント発火 orientation == 0
まとめ
(1) 端末を横置きにしてアプリを起動
0 > orientation == 0
(2) 縦置きに回転させる
1 > resizeイベント発火 orientation == 0
2 > resizeイベント発火 orientation == 0
3 > orientationchangeイベント発火 orientation == 0
(3) 横置きに戻す
4 > resizeイベント発火 orientation == 0
5 > resizeイベント発火 orientation == 0
6 > orientationchangeイベント発火 orientation == 90
(4) もう一度縦置きにしてみる
7 > resizeイベント発火 orientation == 90
8 > resizeイベント発火 orientation == 90
9 > orientationchangeイベント発火 orientation == 0
イベントの発火や各種サイズの値については縦置きから回転させた場合と同じでした。ただ、予期していなかった点が一つ。
横置きでアプリを起動したときのwindow.orientationの値は90ではなく0ということでした。
これは、回転が発生して初めてwindow.orientationの値が設定される、という感じでしょうか?
横置きにしてアプリのアイコンをタップしてからアプリ画面が表示されるまでをよく観察してみると、まずアプリ画面が表示される前にAndroid端末のフレーム表示(上部にあるバッテリーや時間の表示、下部にあるホームボタン、戻るボタンなど)が回転しています。その後アプリ画面が表示されますが、この間に回転イベントは発火していません。
はっきりとはいえませんが、とりあえず回転イベントが発生する前にwindow.orientationを参照しないほうがよさそうです。
縦置きでアプリを起動後、キーボードを表示した状態で回転させてみました。
(1) 端末を縦置きにしてアプリを起動
0 > orientation == 0
(2) キーボードを表示させる(一行目をタップするとcontenteditable属性によりキーボードが起動する)
1 > resizeイベント発火
2 > resizeイベント発火
(3) 横置きに回転させる
3 > resizeイベント発火
4 > resizeイベント発火
5 > orientationchangeイベント発火 orientation == 90
(3) 縦置きに戻す
6 > resizeイベント発火
7 > resizeイベント発火
8 > orientationchangeイベント発火 orientation == 0
9 > resizeイベント発火
10 > resizeイベント発火
11 > resizeイベント発火
12 > resizeイベント発火
まとめ
Note
数回テストしましたが、resizeイベントの発火回数やresizeイベントとorientationchangeイベントの発火順序がこの結果と異なることがたまにありました。おそらく順番に発火していくのではなくそれぞれが独立した発火タイミングを持っていて、再描画などの条件によりずれるのだと思います。
気をつける点
これらを踏まえて以下のように実装しました。
var orn = {
portrait: true, // true: portrait, false: landscape
orientgp: -1, // 0: 0 or 180, 1: 90 or -90
oflg: false,
initOrn: function() { // キーボードが表示されていない状態で使用する
this.portrait = window.innerWidth < window.innerHeight;
},
initGroup: function() { // orientationchangeイベントで呼び出す
if (this.oflg === false) { // 初回の回転時
this.oflg = true;
this.orientgp = !this.getOrnGroup(); // アプリ起動時のorientation値を設定
}
},
getOrnGroup: function() {
return (window.orientation === 0 || window.orientation === 180) ? 0 : 1;
},
// 回転が発生していない場合、orientation値は不定(-1)とみなし、この状態の縦横比に従う
isPortrait: function() {
var xor = (this.orientgp === -1) ? false : this.orientgp ^ this.getOrnGroup();
if ((!xor && this.portrait) || (xor && !this.portrait)) return true;
else return false;
}
};
document.addEventListener('init', function(event) { // DOM初期化完了イベント
orn.initOrn();
});
window.addEventListener('orientationchange', function(e) {
orn.initGroup();
});
アプリの起動時にinitOrn()により縦置きか横置きかを判定しportraitに保存しておきます。
初回の回転イベントでorientation値を取得し、その値の否定値をとることで、回転イベント発生前のorientation値とし保存します。このorientation値と先ほどのportrait値をペアとして、以降、縦横判定を行いたいときにisPortrait()により現在のorientation値と比較することで縦横を判定します。
使い方は以下のようになります。
// 回転発生時
window.addEventListener('orientationchange', function(e) {
orn.initGroup();
if (orn.isPortrait()) {
console.log("portrait"); // 縦置き
} else {
console.log("landscape"); // 横置き
}
});
// キーボード表示時(cordovaプラグイン)
window.addEventListener('native.keyboardshow', function(e) {
if (orn.isPortrait()) {
console.log("portrait"); // 縦置き
} else {
console.log("landscape"); // 横置き
}
});
キーボードさえ表示されなければ、orientationchangeイベントリスナでinnerWidthとinnerHeightを比較するだけで縦横判定できそうなんですけどね。
そういえば最初、キーボードが表示しているかどうかが分かればorientationchangeイベントリスナで縦横判定できるのではと思い、上記キーボード表示イベントリスナでフラグを立てるようにしたら、うまくいきませんでした。このリスナ、キーボードが表示されていなくても端末を回転させたときにイベントを受けていました。showもないなーと調べてみると、このkeyboardshowの後すぐにkeyboardhideも発生していました。それでキーボードは表示されずに何事もないように見えていたというわけです。いやーちょっと怖いですね。
最後蛇足になりましたが、以上です。