モーダルウィンドウの背景を閉じられなくなるバグ
新年度になりリニューアルされてるサイトが多いかもしれない。そんなリニューアルされたあるサイトで、表示されてるウェブページのテキストやリンクなどに全くアクセスできなくなるバグに遭遇した。
それは、ポップアップ広告のように多用されている最近の流行?のモーダルウィンドウを表示している時のことである。少し苛々していて必要ないのにキーボードのEnterキーを叩いた。すると、モーダルウィンドウの外側の幕(背景?)が少し暗くなり、情報が表示されてるモーダルウィンドウを閉じた後も残ってる。そのサイトは背景をクリックしても閉じない設計だったので、「閉じる」ボタンが無い状態で背景を閉じることができなくなった。その背景が閉じなければ、その下に透けて見える元のページにはアクセスできない。そんなバグに遭遇した。
放置すれば良かったのだが、好奇心が刺激されてバグを再現したくなった。
まずはシンプルなモーダルウィンドウのデモを提供しているサイト(初心者でも分かる!モーダルウィンドウの作り方)からソースを拝借して改造してみたが、再現できない。次に同じバグのあるサイトを探したのだが、なかなか見つからない。結局は見つからなかったのだが、FirefoxのF12 でWeb開発ツールを表示した状態でバグに遭遇したサイトのモーダルウィンドウを表示し、Enterキーを叩いたらソースの末尾に<div>ブロックが次々に追加されていく。「閉じる」ボタンでモーダルウィンドウを消すと、追加されたブロックの一部は消えるが残ったブロックもあった。そのブロックがモーダルウィンドウの背景だった。背景が閉じられなくなるバグは無くても、Enterキーを叩くことでモーダルウィンドウのブロックが次々に追加されるサイトなら見つかりそうなので探した。そして、一つだけ見つけた。
【表示コンテンツの切り替えも出来るシンプルなモーダルウィンドウを実現するjQueryのスクリプト】のデモでは、モーダルウィンドウが表示された直後にEnterキーを叩くと、モーダルウィンドウの<div>(id="modal-win")ブロックが一つづつ追加されて、背景を数回クリックしないとモーダルウィンドウが閉じなくなる。このデモのスクリプトを改造すれば、モーダルウィンドウの背景が閉じられなくなるバグを再現できそうである。そして、作って再現した。
バグを解消したデモも作った。
まず、モーダルウィンドウが表示された直後にEnterキーを叩いてモーダルウィンドウのブロックが次々に追加されるのは、モーダルウィンドウを表示するリンクが選択されたままの状態、フォーカスがある状態だかららしい。その<a>リンクに「href="~"」があると、Enterキーを叩く度にリンクを実行する。私がバグに遭遇したサイトでは「href="javascript:void(0);"」とあった。バグを再現するために利用したデモでは「href="#box1"」となっている。このデモの場合、Javascriptが「return false;」で終わらないと、フォーカスを失い、href= で指定したアンカーポイント(#box1)に移動してしまう。実際にはページは動かないが、ブラウザのアドレスバーを見るとURLにアンカーポイント名(#box1)が追加されている。これは、Javascriptが「return false;」で終わらなければ、リンク元はフォーカスを失い、モーダルウィンドウが表示された状態でEnterキーを叩いても、モーダルウィンドウのブロックは追加されないことになる。バグの解消法の一つである。
ところで、Enterキーを叩くのがモーダルウィンドウ表示直後でないと、他の作業でフォーカスを失い、モーダルウィンドウのブロックが次々に追加されるバグは生じない。
Javascriptから「return false;」を削除すると、リンクをクリックした後でアドレスバーのURLが変わってしまうように、実際は移動しなかったとしても気持ちの良いものではない。そこで、アンカーポイントに移動させずに、スクリプトでフォーカスを失わせることもできる。それが、.blur() である。私が作ったバグ再現のデモでは、「return false;」の前に「$('.modal').blur();」を入れることでバグが消えた。
しかし、ほとんどのサイトのモーダルウィンドウでバグが生じなかったのは、どうやら<a>タグに「href="~"」が無いかららしい。だからモーダルウィンドウ表示直後にEnterキーを叩いてもモーダルウィンドウのブロックは追加されなかったらしい。ただ、私が参考にさせてもらった【表示コンテンツの切り替えも出来るシンプルなモーダルウィンドウを実現するjQueryのスクリプト】では、「href="#~"」の情報をJavascriptで読み取って、そのリンクに応じたモーダルウィンドウを表示している。これはとても自然な方法である。モーダルウィンドウが実際は別のhtmlソースを持つ別のウィンドウではなく、同じhtmlソース内の別の場所のブロックをJavascriptとスタイルシートを用いて既に表示されているブロックの上に表示するだけなので、アンカーポイントを使うのは自然である。同じことを他のサイトではどのように実現しているのか。私が最初に見た【初心者でも分かる!モーダルウィンドウの作り方】に載っていた。
それぞれのボタン要素に、data-targetという属性値を付けて、その値には、開くコンテンツのID名を指定しておきます。(初心者でも分かる!モーダルウィンドウの作り方)
モーダルウィンドウのコンテンツ
<a class="modal-syncer" data-target="modal-01">1つ目のモーダルウィンドウを開く</a>それぞれのボタン要素をクリックした時に、data-targetの値を参照することによって、その時に開くモーダルウィンドウのコンテンツが決定するという仕組みです。このようにして、1ページに複数種類のモーダルウィンドウを設置することが可能となります。
<a class="modal-syncer" data-target="modal-02">2つ目のモーダルウィンドウを開く</a>
<a class="modal-syncer" data-target="modal-03">3つ目のモーダルウィンドウを開く</a>
「data-target」とは、HTML5 カスタムデータ属性「data-*」らしい。
HTML5では「data-*="value"」の形式で属性名にプライベートな値を設定できるカスタムデータ属性の仕様と、そのカスタムデータ属性にJavaScriptからアクセスするAPIが定義されました。
(中略)
1. HTML5 カスタムデータ属性「data-*="value"」とは
カスタムデータ属性には、既存の要素や属性に適切なものがなかった場合に、独自のデータを属性に格納することで、より意味を持たせる意図があります。全てのHTML要素に対し、複数設定することができます。
(中略)
9. [jQuery] attr()でカスタムデータ属性にアクセスする方法
.attr('data-name')
.attr('data-name','value')
attr()でカスタムデータ属性の値を取得する場合は、引数はキャメルケースに変換せず、そのまま「data-」を含めた文字列を'data-type-str'のように指定します。また第2引数を指定した場合には属性値を保存します。
<div id='cat' data-name='Tama' data-cat-breed='persian'>ペルシャ猫のたま</div>(HTML5 カスタムデータ属性「data-*」にJavaScript、jQueryからアクセスする方法)
<script>
// attr()でカスタムデータ属性の値を取得
$('#cat').attr('data-name'); // Tama
// attr()でカスタムデータ属性の値を設定
$('#cat').attr('data-cat-breed','himalayan'); // ヒマラヤンに変身
</script>
これらを参考にして、私の作ったバクの再現デモでは、htmlソースの<a>タグを「href="#~"」を「data-link="#~"」に変更し、それに合わせてJavascriptの方を「.attr('href');」を「.attr('data-link');」に変更したら、バグが解消された。
興味のある方は、次のページで確認してほしい。
バグが生じるソースは次の通りである。
modal.html
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Enterキーによるモーダルウィンドウの重複と消せないバグ</title>
<link rel="stylesheet" href="modal.css" media="all">
<script type="text/javascript" src="http://code.jquery.com/jquery-2.1.1.js"></script>
<script src="modal.js"></script>
</head>
<body>
<h1>Enterキーによるモーダルウィンドウの重複と消せないバグ</h1>
<ul>
<li><a class="modal" href="#box1">モーダルウィンドウ表示</a></li>
</ul>
<div id="box1">
<p>モーダルウィンドウ</p>
<p class="modal-close"><a href="#">閉じる</a></p>
</div>
<h2>解説:</h2>
(以下略)
</body>
</html>
modal.css
#box1 {
display: none;
background-color: #ffffff;
padding: 20px;
width: 200px;
text-align:center;
}
.link {
text-align: center;
}
.modal-close {
margin-top: 1em;
text-align: center;
}
#modal-win {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .50);
position: fixed;
top: 0;
left: 0;
z-index: 100;
}
#modal-win-inner {
box-shadow: 0 0 5px rgba(0, 0, 0, .25);
margin: 0 auto;
position: relative;
z-index: 101;
border-radius: 10px;
}
#modal-win-inner {
position: fixed;
width: 200px;
top: 50%;
right: 50%;
}
#modal-win-inner > * {
border-radius: 10px;
}
modal.js
$(function(){
simpleModalWindow();
});
function simpleModalWindow(){
var sp = 500; //アニメーション速度
var win = $(window);
var body = $('body');
//モーダルウィンドウ表示クリックイベント
$(document).on('click', '.modal', function(){
var py = win.scrollTop();
var wh = win.height();
var self = $(this);
var link = self.attr('href');
//var mWin = $('<div id="modal-win"><div id="modal-win-inner"></div></div>');
//var mInner = mWin.find('#modal-win-inner');
var mWin = $('<div id="modal-win"></div>');
var mInner = $('<div id="modal-win-inner" class="inner"></div>');
mInner.css('opacity', '0');
body.append(mWin);
body.append(mInner);
var contents = $(link);
mInner.append(contents);
contents.css({display: 'block', zIndex: '101'});
view(contents);
function view(a_elm){
var w = a_elm.outerWidth();
var h = a_elm.outerHeight();
var mt = (wh - h) / 2 + py;
mInner.css({width: w, height: h, top: mt+'px'}).animate({opacity: '1'}, sp);
}
return false;
});
//モーダルウィンドウクローズクリックイベント
//$(document).on('click', '#modal-bg, .modal-close', function(){
$(document).on('click', '.modal-close', function(){
var mWin = $('#modal-win');
//var mInner = mWin.find('#modal-win-inner');
var mInner = $('#modal-win-inner');
var contents = mInner.children();
if(contents.attr("id")){
body.append(contents);
contents.hide();
}
$('#modal-win').remove();
$('.inner').remove();
return false;
});
}
})(jQuery);
バグを解消したソースは次とおりである。
modal2.html
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Enterキーによるモーダルウィンドウの重複と消せないバグ</title>
<link rel="stylesheet" href="modal2.css" media="all">
<script type="text/javascript" src="http://code.jquery.com/jquery-2.1.1.js"></script>
<script src="modal2.js"></script>
</head>
<body>
<h1>Enterキーによるモーダルウィンドウの重複と消せないバグ</h1>
<ul>
<li><a class="modal" data-link="#box1">モーダルウィンドウ表示</a></li>
</ul>
<div id="box1">
<p>モーダルウィンドウ</p>
<p class="modal-close"><a href="#">閉じる</a></p>
</div>
<h2>解説:</h2>
(以下略)
</body>
</html>
modal2.css
#box1 {
display: none;
background-color: #ffffff;
padding: 20px;
width: 200px;
text-align:center;
}
.link {
text-align: center;
}
.modal-close {
margin-top: 1em;
text-align: center;
}
#modal-win {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .50);
position: fixed;
top: 0;
left: 0;
z-index: 100;
}
#modal-win-inner {
box-shadow: 0 0 5px rgba(0, 0, 0, .25);
margin: 0 auto;
position: relative;
z-index: 101;
border-radius: 10px;
}
#modal-win-inner {
position: fixed;
width: 200px;
top: 50%;
right: 50%;
}
#modal-win-inner > * {
border-radius: 10px;
}
a { color:blue; text-decoration:underline; }
a:hover { color:red; text-decoration:underline; }
modal2.js
$(function(){
simpleModalWindow();
});
function simpleModalWindow(){
var sp = 500; //アニメーション速度
var win = $(window);
var body = $('body');
//モーダルウィンドウ表示クリックイベント
$(document).on('click', '.modal', function(){
var py = win.scrollTop();
var wh = win.height();
var self = $(this);
var link = self.attr('data-link');
//var mWin = $('<div id="modal-win"><div id="modal-win-inner"></div></div>');
//var mInner = mWin.find('#modal-win-inner');
var mWin = $('<div id="modal-win"></div>');
var mInner = $('<div id="modal-win-inner" class="inner"></div>');
mInner.css('opacity', '0');
body.append(mWin);
body.append(mInner);
var contents = $(link);
mInner.append(contents);
contents.css({display: 'block', zIndex: '101'});
view(contents);
function view(a_elm){
var w = a_elm.outerWidth();
var h = a_elm.outerHeight();
var mt = (wh - h) / 2 + py;
mInner.css({width: w, height: h, top: mt+'px'}).animate({opacity: '1'}, sp);
}
return false;
});
//モーダルウィンドウクローズクリックイベント
//$(document).on('click', '#modal-bg, .modal-close', function(){
$(document).on('click', '.modal-close', function(){
var mWin = $('#modal-win');
//var mInner = mWin.find('#modal-win-inner');
var mInner = $('#modal-win-inner');
var contents = mInner.children();
if(contents.attr("id")){
body.append(contents);
contents.hide();
}
$('#modal-win').remove();
$('.inner').remove();
return false;
});
}
})(jQuery);
コメント 0