2014年01月26日の日記です


iOS の position:fixed バグ回避方法  2014-01-26 17:32:32  コンピュータ

お手伝いしている仕事で、バッドノウハウばかりが溜まっていく気がする…


デザイン上の都合で、CSS で position:fixed が使われていた。


テキスト入力を促す箇所で、画面の真ん中に入力窓が出てくる。

まぁ、ネットではよく見るインターフェイスです。


ところが、これは iOS では「使ってはならない」技だった。

バグ報告があり、たまたまバグが出た個所が僕の担当箇所だったので僕が調べることに。


でも、プログラムのバグではなく、iOS のバグだと判明。




position:fixed では、ページをスクロールさせても、ウィンドウに対して常に同じ位置に要素を表示できる。


常に同じ、というとスクロールさせるのをサボるだけで難しくないように思われるが、話は逆だ。

画面に収まらない大きな板があって、その一部を表示しているのがスクロールだ。

通常の WEB ページは、この方法で作られている。


それを、「常に画面に対して同じ位置」に表示しようとすると、スクロールのたびに位置を計算し続けなくてはならなくなる。

これは結構大変な作業で、iOS では 5 から搭載された。それ以前は使えなかったのだ。


でも、7 になった現在でも、バグが残っている。



バグと言うのはこういうことだ。

先に書いた通り、「スクロールのたびに位置を計算する」のが、position:fixed による表示だ。


ところで、iOS にはソフトウェアキーボードと言うものがある。

キーボードを出すと、画面は上の方に寄せられ、狭くなる。

逆にキーボードを消すと、出ていたときに比べて広くなる。


でも、キーボードが出るのはスクロールではない。要素の位置は再計算されない。


まぁそれでも、キーボードが出て、消えるだけなら元に戻るだけなので心配はいらない。

問題は、キーボードを出している間にスクロールが行われ、その後キーボードが消える場合だ。


狭い画面でスクロールが行われ、その画面に対して要素の位置が計算される。

その後画面を広げても、要素の位置は再計算されず、結果的に本来指定したものとは違うものとなっている。



うん。何も問題はない。キーボードは「表示」されるだけで、スクロールが起きるわけではない。

画面が狭くなるのだからリサイズイベントは起こしてほしい気がするが、これも起きない。

こちらはバグっぽい気もするが、iOS ではリサイズ=画面の向き(縦・横)変更、というルールがあるので仕様と言うべきなのだろう。


でも、結果的に意図しない画面になっているのであれば、バグだと考える人もいる。

そして、今回がそのケースだった。




試行錯誤の末、次の方法で回避。


問題はキーボードが消えるときだ。

iOS でキーボードが出る条件は、文字入力が行われる時だ。


WEB 的には、INPUT TYPE=TEXT の要素にフォーカスした際にキーボードが出る。

そして、フォーカスが失われるとキーボードが消える。


フォーカスが失われることを、Javascript では blur と呼ぶ。

そこで、INPUT TYPE=TEXT の blur イベントを拾い、画面表示を更新してやれば良い。


画面表示の更新は、しつこく書いたようにスクロールによって起こる。

だから、スクロールしてやれば良いだけ。

この際、blur してからキーボードがアニメーションしながら消え、完全に消えるまでに 0.1 秒ほどかかることを考慮する必要がある。


スクロールすると言っても、blur のたびに画面が動いていくのは困るので、blur の時に下に 1px、キーボードの消え失せた 0.2秒後に上に 1px 動かし、結果としてスクロールは起こらないことにした。



今回の仕事は iOS 用だった。このため、jQuery は「重いから使わない」との判断で、ある程度互換性を持ちながら、機能を削減して軽くした zepto が使われている。


そこで、zepto 用のプログラムは次のようになる。



$(function(){
  $("input").each(function(){
    if(this.type!="text")return;
    $(this).bind("blur",function(e){
      window.scrollBy(0,1);
      setTimeout(function(){window.scrollBy(0,-1)},200);
    });
  });
});


window に仕掛けてバブリングしてきたものをキャプチャ出来るといいのだが、残念ながら blur イベントはバブリングしない。

そのため、input を取り出し、type=text のときだけ blur を付ける、という泥臭い方法になっている。

(zepto のセレクタでは input[type=text] のような属性フィルタは使えない)


もちろん、DOM 構築以降に動的生成された input が position:fixed だったりすると困ったことになる。

今回の仕事ではそのようなことは無さそうなので、そこは割り切った。


textarea にも対応させてやるとよさそうだが、そこも仕事では無さそうなので割り切り。


とりあえずこれでバグ回避できた。



同じテーマの日記(最近の一覧)

コンピュータ

関連ページ

バーベキュー【日記 17/07/02】

Programming Tips

別年同日の日記

17年 後藤英一 誕生日(1931)

21年 ノートパソコン修理


申し訳ありませんが、現在意見投稿をできない状態にしています


戻る
トップページへ

-- share --

170028

-- follow --




- Reverse Link -