という不具合に出くわしたので、調べた内容をまとめておく。
再現HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,minimum-scale=1,maximum-scale=1,user-scalable=no">
<script src="//code.jquery.com/jquery-2.1.1.min.js"></script>
<script>
$(function(){
var loop = navigator.userAgent.split("").slice(0, 20);
loop.forEach(function(){
// そこそこの大きさのtableを作成して追加していく
$("#contents_main").append(
"<table border=1>" + loop.map(function(){return "<tr><td>1</td><td>2</td><td>3</td><tr>"}).join("") + "</table>"
);
});
$(".open_select").click(function(){
$("#contents_main").hide();
$("#contents_select").show();
});
});
</script>
</head>
<body style="padding: 10px;">
<div id="contents_main">
<a class="open_select">自分自身のaタグを含むメインコンテンツをhideしつつセレクトを開く</a><br/>
</div>
<div id="contents_select" style="display: none;">
<select><option>セレクトをタップするとフリーズしたりしなかったり</option></select>
</div>
</body>
</html>
これで、
- aタグをタップ ⇒ セレクトボックス表示
- セレクトボックスをタップ ⇒ フリーズ(そのうち復帰)
という事象が確認できる。
事象の特徴
- iOS 8のMobileSafariでは発生しない
- 端末モデルは関係なし、iPhone4, iPhone5 iPhone5c で事象確認
- hideされるメインコンテンツのDOMを大きくするとフリーズする時間が延びる
- 性能のよい端末モデルほどフリーズする時間は短い
レンダリングエンジンによるCPUバウンドな処理が誘発されていると推測。
ワークアラウンド
aタグのhref属性にjavascript:void 0;
を付与してあげると事象は発生しなくなる。
<a class="open_select" href="javascript:void 0;">...</a>
似たような話、IE 6にもあったな...
これはどういう話かというと、aタグのブラウザデフォルト挙動というものがあって、通常は画面遷移とみなすのでDOMに対してページを破棄するような処理が内部で走っているとかどうとか(破棄しているページのgitアニメーションを動かし続ける必要がないから停止させられる、という話)。
考察
つまりこういうことだろう。
- aタグのブラウザ内部動作(hrefの有無で変わる)+DOMの変更によりレンダリングエンジン内部処理を行うフラグがセットされる(リフローやらリレンダリング的なやつ、あくまでフラグだけで処理は遅延されている)
- セレクトボックスをタップすることで内部イベントにより結果レンダリングエンジンの処理が走る(iOSのセレクトボックスはレンダリング領域外下方からUIがせり出してくるタイプなのでレンダリングに寄与するでしょう)
フリーズを抑止する他のHTML記述
なんか変なパターンも見つけた。hideされるコンテンツの外に要素を追加することで挙動が変わる。
:
<body style="padding: 10px;">
<h1>aaa</h1>
<div id="contents_main">
<a class="open_select">自分自身のaタグを含むメインコンテンツをhideしつつセレクトを開く</a><br/>
</div>
:
これは依然としてフリーズ事象が発生するが、
:
<body style="padding: 10px;">
<a>aaa</a>
<div id="contents_main">
<a class="open_select">自分自身のaタグを含むメインコンテンツをhideしつつセレクトを開く</a><br/>
</div>
:
こうするとフリーズ事象が抑止される。
これも考えてみると、aタグはユーザーイベントに反応しなければいけない要素なので、イベント反応する領域は事前に計算されなければならないはず(そしてそれは関連する全ての要素の表示位置とサイズが計算されなければならないのでリフロー相当の処理が行われているはずだ)。その計算必要有無でフリーズ事象に寄与しているのかなと推測したい。
まとめ
- href属性のないaタグってそもそも行儀の悪い書き方かもなのでモダンブラウザ全盛の昨今でも気をつけたほうが良さげ
- 実際問題レンダリングエンジンの挙動にそこまで明るいわけではないので間違ってたらツッコミ欲しい
- ブラウザこわい(><)