jQueryで「このページの先頭に戻る」ボタンなどでanimateを使っている場合、callback関数が2回呼ばれる問題を回避する方法

いろんなWebサイトで「このページの先頭に戻る」「このページの一番上に戻る」というようなボタンを見かけたことがある方もいらっしゃると思います。

押すと「スルスル~」っと滑らかに、表示しているページの上部に向かってスクロールするボタンですね。

こんなボタン

ところで、このボタンを実装する方法の1つとしてjQueryを使うと割と簡単にできます。

例えば下のコードはclass名「to_top」を持つ要素(「このページの先頭に戻る」ボタンなど)を押した時に、表示しているページの上部へ向かって0.3秒間でスクロールさせて、コンソールに「done」と表示させるものです。

var callback = function() { console.log('done'); };
$('.to_top').click(function() {
 $('html,body').animate({'scrollTop':0}, 300, callback);
});

このような動きをさせるために「html」「body」の2つの要素を指定する方法が割と知られています。

「html」「body」の2つの要素を指定する理由

なんで2つの要素を指定する必要があるのか、ですが・・・その理由は、上記のコードのような実装をしたい時に「animate」を使うと、webkitが使えるブラウザ(Google Chrome,Safari,Android,iOSなど)と、webkitが使えないブラウザ(Internet Explorerなど)によって指定する要素が変わるからです。

ブラウザによって指定する要素が変わる

ブラウザ 指定する要素
webkit使える body
webkit使えない html

例えば指定する要素が「body」だけではInternet Explorerでは「animate」が効きません。「スルスル~」が動かないです。

なので「どちらも対応できるように2つまとめて指定しておこう」というわけですね。

便利なんだけどちょっと問題がある

上記の2つの要素「html」「body」をまとめて指定する方法は便利ですが、この方法だとcallback関数が2回呼ばれてしまいます。

つまり、先ほどのコードを実行すると2回「done」という文字がコンソールに表示されます。

今回はコンソールに表示される例でしたが、処理が2回実行されるのは避けたいですよね。

回避策を考える方がいた

callback関数が2回呼ばれてしまう問題を以下の方法で回避されている方もいらっしゃいます。

  • jQuery.browser.webkitを使って条件分岐する(jQuery1.3からはサポート外になっています。今後使い続けるのは難しそう)
  • jQuery.supportを使って条件分岐する(これを使っても主要ブラウザに対応するにはとても大変みたいです)
  • ブラウザ判定のコードを書いて条件分岐する(ブラウザの仕様が変わるたびにコードをメンテナンスしないといけない。巷にはブラウザがたくさんあるぜ・・・これは大変だ・・・)
  • 「html」「body」のどちらのanimateが動くのかちょっとスクロールさせて判別して、動いた要素のみ指定する

それらの方法でもいいと思うんですが、私は別の角度でもっとシンプルにできないかなと思いました。

フラグを持たせてみよう

試しに下のコードのようにフラグを持たせて「フラグが立っていれば処理する」というように条件分岐する方法でやってみました。

var callback = function() { console.log('done'); };
$('.callback_once').click(function() {
 var flag = false;
 $('html,body').animate({'scrollTop':0}, 300, function() {
 if (flag) {
  flag = false;
  callback();
 } else { 
  flag = true;
 }
 });
 return false;
});

コードがやっていること

class名に「.callback_once」を持つ要素(「このページの先頭に戻る」ボタンなど)を押すと、コンソールには「done」という文字が1回だけ表示されます。フラグが立っていれば1回実行されるように処理を加えています。

簡単なサンプルをcodepenで作ったので、コンソールを開いてお試しください。

webkitが使える・使えないに関わらず「animate」を動かすことができて、callback関数も1度だけ呼ばれるようになりました。

おお・・・これでいいんじゃないか!?

まとめ

jQueryも勉強中なので、他にももっと方法があるかもしれませんし「この方法だとうまくいかないよ」という意見がありましたら、ご教示くださいますと嬉しいです。(コードももっと簡略化できそうです)

よりよい方法で実装できるのが理想ですね。

著者:bouya Imamura