JavaScriptのAccordionなどでハマってしまった時の話

久々の当番なので肩慣らし程度にライトなお話を。
決して何も考えていなかったから焦って書いたとかそういうのではないです。

最近某案件でハマった時の話です。

jQueryライブラリ+ハッシュリンクでハマった!

よくjQueryなどのライブラリで、AccordionやTabなど元々のコンテンツを隠したり縮めたりするスクリプトがあります。
あたりまえですが、そのスクリプトを使うと全体のコンテンツの高さ(長さ)が変化する訳なんですね。

そして!!!その際に不具合を起こすのが下記のページの上にある例…
(※不具合が怒らない場合もあります。)

デモページ

ハッシュリンクを利用してAccordionを利用しているページの特定部分にリンクしようとしているのですが、てんで違うあちゃらな部分にフォーカスしてしまいます。

なぜか

すぐに分かる話なんですが、

  1. ページ移動!!!!!!
  2. ハッシュリンク発動!!!!!!
  3. スクリプト発動(ページの高さが縮む)!!!!!!
  4. なんてこったい!!!!!!!!!

という動作をしているからなんですね。

こいつはさすがに難しいなと思ったんですが、なんとなくやってみたら解決できました。

ナヤミムヨウの解決策

要は上記の流れを

  1. ページ移動!!!!!!
  2. スクリプト発動(ページの高さが縮む)!!!!!!
  3. ハッシュリンク発動!!!!!!
  4. それみたことか!!!!!!!!!

という具合に変えてやればいいんですね。

そこで、まずハッシュリンクのタイミングをズラすためにリンク元のパスを変更してやります。
ハッシュリンクはページ遷移してからすぐに発動するため、遷移先ではどうすることもできませんでした。

<a href="accordion.html#TEST04">TEST04へリンク</a>
        ↓
<a href="accordion.html#jq-TEST04">TEST04へリンク</a>

なんでもいいのですが、全然存在しないIDを指定してやります。
こうすることでリンク直後にハッシュリンクが発動することがなくなります。

しかしこれを直にHTMLで変更してしまうとJavaScriptオフの時なんかに不具合が起きてしまうので、以下のようなスクリプトを挿入。

$(function(){
	var _link = $('#fix a');
	var _path = _link.attr('href'); // 指定したリンクのhrefを取得
	var _fixPath = _path.replace(/#/,'#jq-'); // パスをリプレイス
	_link.attr('href',_fixPath);
});

これでJavaScriptがオフの場合には普通に遷移してくれます。

しかしこれだけでは存在しない部分へリンクを貼っただけになるので、遷移先で以下のようなスクリプトを設置。

var _hash = location.hash; // アドレスのハッシュ以下を取得
if(_hash.match(/^\#jq\-/)){ // リンク元のページで指定したprefixが付いているか
	_point = _hash.replace(/^\#jq\-/,'#'); // 正式なID名に変換
	var _t = $.support.boxModel ? navigator.appName.match(/Opera/) ? "html" : "html,body" : "body";
	var _p = $(_point).offset().top;
	$(_t).animate({scrollTop:_p},{duration:0,queue:false}); // 0秒でスクロール
	location.replace(_point); // 最後にアドレスバーも正式なIDに変更する
};

これでようやく目的の場所へフォーカスしてくれるようになりました。

実装したのが先ほどのデモページの下の例です。

デモページ

あくまでも上記をAccordionなどの初期化の後に記述するようにしないといけません。

終わりに

真にJavaScriptを知っている人が考えればもっと合理的な解決策があるかもしれないので、その場合にこの記事はゴミクズになります。

あとちなみにCSS側で「display:none;」などを指定して、はじめから表示していないぜっていう場合にはそもそもこの不具合事態が起こらないと思うので、その場合にもこの記事はゴミクズになります。

しかしながらゴミクズになった場合でもJavaScript少し触れたので良かったと自分に言い聞かすことが大事だと僕は思います。