スムーズスクロールについて無駄に考えてみたメモ

このエントリーをはてなブックマークに追加

やりたい事は、ムーズスクロールとハッシュを更新させるです。単純なのですがハッシュの更新にこだわっていたのでこの2個の共存がちょっと面倒でした。

ページが縦に長かったり FAQ のように細かい項目がたくさんあるページでは折角ページ内リンクがあってもハッシュが更新されてないと、リンクをしたり他の人に URL を伝える時めんどくさいので URL のハッシュを更新するのははずせないところ。

というわけで、以下の条件の組み合わせを実装してみようと思います。

・スムーズスクロールする
・URL のハッシュを更新させる
・スクロールに合わせてトップに戻るリンクをフェードインで表示する

ページトップへ戻るリンクのアニメーション

とりあえずトップへ戻るリンクにスクロール量に依存したアニメーションをつけます。アニメ効果は CSS で付けています。それと個人的にイージングの easeOutQuart が好きなのでこれだけ追加しています。本編のスクロールさせる要素は webkit とそれ以外で異なるため、以下の scrollElement のような振り分けを使用しても良いと思います。

window.scrollElement = window.navigator.userAgent.toLowerCase().indexOf('webkit') != -1 ? 'body' : 'html';
$.extend($.easing, {
    easeOutQuart: function(x, t, b, c, d) {
        return -c * ((t = t / d - 1) * t * t * t - 1) + b;
    }
});
$(window).on('scroll', function() {
    $('#PageTop').toggleClass('enabled', $(this).scrollTop() > 200);
});

Test1 アニメーションでIDにジャンプさせる

$(document).on('click', 'a[href*="#"]', function(event) {
    if (this.href.indexOf("#") === 0 || (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname)) {
        event.preventDefault();
        if ($(this.hash).length) {
            var position = $(this.hash).offset().top;
            $('html,body').animate({
                scrollTop: position
            }, 1000, 'easeOutQuart');
        }
    }
});

テストページ

すごくオーソドックスなタイプのスクロールです。
余計なことをしていないのでスクロールの動きは古い PC を使っていても多分これが一番スムーズかなぁと思います。
でもハッシュが更新されないので URL のコピペが出来ないのが難点…。

Test2 location.hashでハッシュを更新するようにしてみた

$(document).on('click', 'a[href*="#"]', function(event) {
    if (this.href.indexOf("#") === 0 || (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname)) {
        event.preventDefault();
        var hash = this.hash.replace(/^#/, '');
        if ($('#' + hash).length) {
            var position = $('#' + hash).offset().top;
            $('html,body').animate({
                scrollTop: position
            }, 1000, 'easeOutQuart');
            location.hash = hash;
        }
    }
});

テストページ

location.hashを加えてハッシュを更新するようにしてみました。(またはevent.preventDefault()を使わない)
タイミングで2回分アニメーションが実行されるのと、Firefox では問題ないのですが Chrome と IE だとスクロールバーがブルっててちょっと気持ち悪い。
スクロールのアニメーション中にハッシュが更新されるためかなと思われます。

Test3 コールバックでlocation.hashを更新してみた

$(document).on('click', 'a[href*="#"]', function(event) {
    if (this.href.indexOf("#") === 0 || (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname)) {
        event.preventDefault();
        var hash = this.hash.replace(/^#/, '');
        if ($('#' + hash).length) {
            var position = $('#' + hash).offset().top;
            $('html,body').animate({
                scrollTop: position
            }, 1000, 'easeOutQuart', function() {
                location.hash = hash;
            });
        }
    }
});

テストページ

ならばスクロールのアニメーションで移動し終わったあとにlocation.hashでハッシュを更新してみました。
結構期待通りの動きになってきました。
が、ブラウザの戻るを押したときに直前居た位置に戻れないというのが難点かな…。
ブラウザの移動を使っているとこれが割りと不便です。

Test4 location.hashで更新したあと位置を戻してみた

$(document).on('click', 'a[href*="#"]', function(event) {
    if (this.href.indexOf("#") === 0 || (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname)) {
        event.preventDefault();
        var hash = this.hash.replace(/^#/, '');
        if ($('#' + hash).length) {
            var tmptop = $(window).scrollTop();
            location.hash = hash;
            var position = $('#' + hash).offset().top;
            $('html,body').scrollTop(tmptop).animate({
                scrollTop: position
            }, 1000, 'easeOutQuart');
        }
    }
});

テストページ

ハッシュを更新したあとに再度現在の位置を指定したらどうかな。
ハッシュの更新で飛んだ後に→現在の位置に戻っる この処理にスクロールバーがカクついたりとか見た目的な違和感があるかなー…と思いながら試してみたのですが意外と違和感はありませんでした。

一旦飛んだ後に戻るとか処理上のモヤッと感はありますが、pushState 非対応まで考えるならこれがベストなのでしょうか。

Test5 IDを一時的に削除してみた

$(document).on('click', 'a[href*="#"]', function(event){
	if (this.href.indexOf("#") === 0 || (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname)) {
		event.preventDefault();
		var hash = this.hash.replace(/^#/, '');
		var el = $('#' + hash);
		if (el.length) {
			el.removeAttr('id');
			location.hash = hash;
			el.attr('id', hash);
			var position = el.offset().top;
			$('html,body').animate({
				scrollTop : position
			}, 1000, 'easeOutQuart');
		}
	}
});

テストページ

location.hashでジャンプしないのは「IDが存在しない時」というわけなので、一時的にIDを削除する暴挙。
削除されるのはハッシュを更新する一瞬ですが、そのID自体を使って何か処理をしている途中とかだとなにか不具合が出そうな気も。割りと論外なきがしますね。

Test6 IEの事は忘れてみた(せいかい)

$(document).on('click', 'a[href*="#"]', function(event){
	if (this.href.indexOf("#") === 0 || (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname)) {
		if (typeof window.history.pushState == 'function') {
			event.preventDefault();
			var hash = this.hash.replace(/^#/, '');
			if ($('#' + hash).length) {
				var position = $('#' + hash).offset().top;
				$('html,body').animate({
					scrollTop: position
				}, 1000, 'easeOutQuart');
				history.pushState(null, null, location.pathname + location.search + '#' + hash);
			}
		}
	}
});

テストページ

pushState対応ブラウザのみに絞ってみました。
この記事を書いた頃は色々悩みましたが、クライアントからの「IE7 とか IE8 も対応してよ」というお腹痛くなりそうな要望でもない限りもはやこれで良い気がします。さよなら IE9 以下(っ'o'c)


Edited on 2017.01.25 Created on 2013.01.29 JavaScript jQuery Webデザイン
PAGE TO TOP