読者です 読者をやめる 読者になる 読者になる

Android2.3の闇 #1

ようやく仕事も一段落したので、今回の案件を通して得られた知識(というかバッドノウハウのようなもの)を残していきたいと思う。
今回は主に僕はPHPスマートフォン向けWebページのレイアウトを整えていく作業をしていた。
実際は内部処理とか結構書く羽目になったのだけれども。
今までスマートフォン向けサイトなんて作ったことがなかったから、Android対応がここまでブラックだとは知らなかったので、主にAndroidで生じるバグに対処する何かしらの手段を提示する。
(ちなみにシリーズ物っぽくタイトルをつけたけど、続くかどうかは気分次第)


Androidはとにかくバグが多い。
僕はAndroidオープンソースだから、どんどんバグが発見されては修正されていくものだと思っていたけど、よく考えたらそんなことあるわけない。
だって端末メーカがアップデートしないとアップデートすらできないし。
Android 4.xで幾分改善されたとはいえ、まだ満足のいくものではない。
そもそも端末毎にソースをいじっているという事例もある。
機種毎に画面サイズも違う。(これが一番大きいかな)

そんなAndroid、とりあえず2.3で起こった不具合の一つの解決策を紹介する。

overflow: scroll;

まずは下の赤枠ボックスを見てほしい。


テスト文章0
テスト文章1
テスト文章2
テスト文章3
テスト文章4
テスト文章5
テスト文章6
テスト文章7
テスト文章8
テスト文章9

PCブラウザから見ている場合は何の変哲もない、ただのスクロールボックスだ。
CSSは以下の通り。

.scroll-box {
    height: 100px;
    overflow: scroll;
}

今回のミソはoverflow。普通のボックスなら、heightを指定してしまうと今回のようにコンテンツのheightが大きいとはみ出てしまう。
そのときのはみ出し方を指定するのがoverflowプロパティ。

visible ボックスから普通にはみ出る
scroll スクロールして見られるようにする
hidden 非表示
auto ブラウザ依存

で問題は、Android2.3ではoverflow:scroll;が効かない。
hiddenが指定が指定されたのと同様になるのだ。
だから、上のスクロールボックスをAndroid2.3から見ようとすると、残念ながら最後まで見ることはできない。
(おそらくテスト文章2くらいまでしか見えないのではないだろうか)

対処法

ではどうするか。諦める。最も懸命だと思う。
シャドーコーディングの段階でoverflow:scrollを使わないようなデザインにするのがよい。
でも、今回の僕のように実装してみるまで知らなかったらどうするか。
まあJavaScriptを使うことになるでしょう。

今回はこんな感じに書いてみた。jQuery使用。

var scroll_start_x = 0;
var scroll_start_y = 0;

$('.scroll-box').each(function() {
	$(this).on({
		'touchstart'	: function(e) {
			scroll_start_x = e.originalEvent.touches[0].pageX;
			scroll_start_y = e.originalEvent.touches[0].pageY;
		},
		'touchmove'		: function(e) {
			var scroll_end_x = e.originalEvent.touches[0].pageX;
			var scroll_end_y = e.originalEvent.touches[0].pageY;
			$(this).scrollTop($(this).scrollTop() - (scroll_end_y - scroll_start_y));
			$(this).scrollLeft($(this).scrollLeft() - (scroll_end_x - scroll_start_x));
		}
	});
});

タップされたとき(touchstart)にタップされた位置を保存して、指を動かしたとき(touchmove)に移動の差分だけスクロールさせる、といった簡易な実装。
デメリットとしてはJSを使っているのでどうしても多少動作がもっさりすること。
だけど、スクロールがもっさりするのは他にも原因がある。
それは、指を動かしているときにtouchmoveはそれほど細かい間隔で発火されているのではないということ。
この実装だとtouchmoveイベントが発火したときにしかスクロールしない
だから少々カクカクすると感じると思う。

これを解決するには、隣り合った2つのtouchmoveイベントの時間差を拾って、その時間感覚でanimateさせればよい。
実証はしていないけれど、それなりに動くのではないのだろうか。