Android のブラウザのフォーカスが z-index を無視しているバグがなおらない
Android のブラウザはフォーカスの処理がうんこで、見えてない A 要素 (例えば position: absolute な要素が上に重なっている) も、タップしたときにフォーカスをとりやがります。
どうしようもないので、どうするかというと、スマートな方法はおそらく「ない」ので、うんこ的方法を使って解決します。重なっている要素でフォーカスがあたる要素をかたっぱしからフォーカスがあたらないようにします。
具体的には全部の a と input をそれぞれ、a は href 要素をはずす、input は disabled にするということをしますが、実際のところドキュメント中全ての要素についてこれをやると激重いので必要な範囲をだけやるようにします。
実際はてなスターをAndroidでロードしたときの画面で使われてますが、適当に書き抜くと以下のような感じです。
var ys = 15; // y start
var ye = 300; // y end
var xs = 100; // x start
var xe = 100; // x end
var checkedElements = [];
function showOverlay () {
for (var y = ys; y < ye; y += 5) {
for (var x = xs; x < xe; x += 5) {
var e = document.elementFromPoint(x, y); // x, y の位置の要素を取得
if (!e) continue;
if (e._checked) continue; // 何度も取得されることもあるのでチェック済みならスキップ
if (e.nodeName == 'INPUT' || e.nodeName == 'TEXTAREA') {
e._orig_disabled = e.disabled;
e.disabled = true;
} else
if ((a = ancestor(e, 'A', 3))) { // a > span のような構造があるとき
if (a._checked) continue; a._checked = true; checkedElements.push(a);
a._orig_style = a.getAttribute('style');
a.setAttribute('style', document.defaultView.getComputedStyle(a, "").cssText);
// a.style.outline = "1px solid red";
a.setAttribute('xhref', a.getAttribute('href'));
a.removeAttribute('href');
}
e._checked = true; checkedElements.push(e);
}
}
function ancestor(e, name, deep) {
if (e.nodeName == name) return e;
if (e.parentNode) {
if (deep < 0) return null;
return ancestor(e.parentNode, name, deep - 1);
} else {
return null;
}
}
}
function hideOverlay () {
var links = document.querySelectorAll('a[xhref]');
for (var i = 0, len = links.length; i < len; i++) {
var a = links[i];
a.setAttribute('href', a.getAttribute('xhref'));
a.removeAttribute('xhref');
a.setAttribute('style', a._orig_style);
}
var inputs = document.querySelectorAll('input, textarea');
for (var i = 0, len = inputs.length; i < len; i++) {
inputs[i].disabled = inputs[i]._orig_disabled;
}
for (var i = 0, len = checkedElements.length; i < len; i++) {
checkedElements[i]._checked = false;
}
}幸い elementFromPoint が使えるので、強引に、該当する範囲の要素をほぼ全て列挙して a と input について上記のような処理をしています。
ただ、この方法ですと、onclick ハンドラが設定されている場合に対処できません (onclick ハンドラは、設定するだけで「フォーカス可能」な要素となるみたいです)。これについては対処方法が思いつかなかったのでひとまず放置されています。
また、豆知識ですが、Android において、フォーカスされたときのハイライトを消すには以下のようなCSSで可能です。
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);