前に作ったスクロールした時に位置固定のロゴをトップに戻る機能にすり替えるものを少し手直しして再導入した。今回はスムーズにスクロールさせようかと色々考えていたが、やはりJavaScriptでscrollTo()
を制御するのはコストが高い。CSSならどうだと試行錯誤したところ、どうやらbody
要素への負のマージンをCSS Transitionで滑らかに変化させれば良いようだ。
Demo: Scroll Smoothly with CSS Transition
デモのページにはダミーテキストの各セクションの最後にそれぞれ⇑ Back to Topというリンクがある。それをクリックすると1秒かけてスムーズにスクロールしながらトップに戻る。トリガーとスクロール自体はJavaScriptで行っているが、スクロールのアニメーション自体はCSS Transitionで行っている。具体的には以下のような処理と仕組みになる。
window.pageYOffset
)分だけマイナスにbody
要素のmargin-top
プロパティーを設定するtransition
プロパティーをbody
要素のmargin-top
プロパティーへ仕込むbody
要素のmargin-top
プロパティーの値を0
にするmargin-top
プロパティーの値を戻すアニメーションが起こるスクロールバー(があるなら)を注意深く見るとわかるが、実際にはスムーズにスクロールしているわけではなく、そのように見えるというだけである。CSS Transitionによるアニメーションはユーザーが途中で止める手段もないので、実用上はほとんど問題ないだろう。
実装のコードも9行ほどと短く、色々なものに組み込みやすいと思われる。コードも順序良くCSSを割り当てていくだけだ。
document.getElementById("to-top").addEventListener("click", function (evt) {
var styleBody = document.body.style;
styleBody.transition = "initial";
styleBody.marginTop = "-" + (window.pageYOffset - 1) + "px";
window.scrollTo(0, 0);
styleBody.transition = "margin-top 1s ease-in-out";
styleBody.marginTop = "0";
evt.preventDefault();
});
最初にbody
要素のtransition
プロパティーをinitial
に戻すことで、負のマージンを与えた時にアニメーションしないようにすることができる。最初にこうしておかないと、複数回このトップに戻る機能を利用した場合におかしなことになってしまう。initial
というキーワードの値はInternet Explorer 11を始めいくつかのブラウザーでまだサポートされていないが、不正な値を仕込んでも目的であるアニメーションを潰すことは可能だ。そのため行儀が良いとは言えないが、このままでも良いだろう。気になる人はその仕様で決まっている初期値であるall 0s ease 0s
にすると良い。
この辺りのアニメーションに使ったスタイルの後始末は、transitionEnd
イベントできれいに行える。明示的にスタイルをきちんと元に戻すことができるので、バグが潜みづらい実装になるはずだ。複雑なCSSを持つウェブサイトではそうすることも考えるべきだ。
スクロール位置を取得するwindow.pageYOffset
から1
を引いているのは、スクロールバーが一瞬消えてしまわないようにするためのおまじないだ。これは僕は気づいておらず、@ginpei_jpが考えてくれた。これがないとスクロールしきった状態でこのトップに戻る機能を利用すると、スクロールバーが一瞬消え、場合によってはレイアウトが一瞬ずれる可能性がある。
transition
プロパティーの長さとアニメーション関数は自分の好みでもっと色々試してみると良い。僕の感触では、長さが.5s
でアニメーション関数はease-in-out
とするのが一番スカッとスクロールしてくれると感じた。長さは移動距離に応じて調節しても良さそうだが、同じ時間で動かした方が好ましく感じる人が多いだろう。
このウェブサイトでは位置固定のロゴも同じようにスムーズにスクロールさせる必要があるので、ほんの少し実装がややこしくなっている。といってもロゴのmargin-top
プロパティーでbody
要素とは逆に正のマージンでずらしてやり、同じようにアニメーションさせているだけだ。うまく機能しているように思う。
ここまでくればPure CSSでもいけそうに思えるが、それはちょっと難しそうだ。アニメーションまではHTMLの助けを借りれば容易に実装することができるが、transition
プロパティーのリセットまたは:target
の解除がCSSだけではできない。
昨今は、とにかくCSSをうまく利用してアニメーションをさせた方が軽いものになることが多い。機能の発動のためのイベントなどでJavaScriptを使う事にはなるだろうが、それ以外では頭をひねってCSS Transition (またはAnimation)でやることを念頭に置くと、挙動と実装が共にスカッとしたアニメーションを提供できることだろう。