2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ネイティブjavascript+cssでスムーススクロールもどきを実装する

Last updated at Posted at 2019-11-05

きっかけ:画面トップへ戻るボタンを作る際に調べてみたら長いコードが出てきた
→指定位置までスクロールする機能は必要ないから短いの書こう
(「もどき」の理由は厳密にはスクロールしていないからです)
##コード
###html

sample.html
<!DOCTYPE html>
<html>
<head>
	<title>サンプル</title>
	<link rel="stylesheet" href="sample.css">
	<script src="sample.js"></script>
</head>
<body>
	<div style="height: 25vh;background-color:#fcc;"></div>
	<div style="height: 25vh;background-color:#cfc;"></div>
	<div style="height: 25vh;background-color:#ccf;"></div>
	<div style="height: 25vh;background-color:#fcc;"></div>
	<div style="height: 25vh;background-color:#cfc;"></div>
	<div style="height: 25vh;background-color:#ccf;"></div>
	<button id="toTop" onclick="backToTop()">トップに戻る</button>
</body>
</html>

###css

sample.css
body{
	overflow-y: scroll;
	width:100%;
	height: 100%;
	margin: 0;
	padding: 0;
	transition:1s;
}

body.fixed{
	position: fixed;
	left: 0;
	background-attachment : fixed;
}

###JavaScript

sample.js
function backToTop(){
	let scrollPosition = window.pageYOffset;

	document.body.classList.add('fixed');
	document.body.setAttribute('style', 'top:-' + scrollPosition + 'px;');
	window.setTimeout(
		function(){
			document.body.setAttribute('style', 'top:0px');
		},0
	);
	window.setTimeout(
		function(){
			document.body.removeAttribute('class');
			document.body.removeAttribute('style');
		},1000
	);
}

##解説
まず挙動についてですが「スクロールして画面トップへ戻る」ではなく「トップへ戻った後でスクロールしているように見せる」が正確です。

sample.js
	//スクロール量を取得
	let scrollPosition = window.pageYOffset;

	//bodyをfixedで固定
	document.body.classList.add('fixed');
	//topでスクロール量と同じだけbodyをずらす
	document.body.setAttribute('style', 'top:-' + scrollPosition + 'px;');

この1行目にてスクロール量を取得しています。
次にbodyに対してdisplay:fixedが働くので強制的に画面トップに戻されます。
ただ、これだけだと一瞬で遷移してしまってスムースさの欠片もないので、画面が動かないように固定されたbodyをスクロール量の分だけ上に動かすことで、見かけ上は動いていないように見せることができます。
(モーダルウィンドウを開いた際の背景固定と同様の手段です)

sample.js
	//ずらしたbodyを一番上に戻す
	window.setTimeout(
		function(){
			document.body.setAttribute('style', 'top:0px');
		},10
	);

次にtopの値を0に変更します。
この時setTimeout()を使って、遅延させることでtransitionが働かせています。
これによってbodyがあたかもスクロールしているように見えます。
実際には固定された領域が動いているだけでスクロールはしていません。
何度も言いますが、画面トップへ戻るという目的はbodydisplay:fixedを適用した時点で達成しています。

sample.js
	//スクロール完了後、追加したクラス・インラインスタイルを削除する
	window.setTimeout(
		function(){
			document.body.removeAttribute('class');
			document.body.removeAttribute('style');
		},1000
	);
}

最後に一連の動作でつけたクラスとインラインスタイルを消去します。
こちらもアニメーションが終わるまでの間は実行されないようにtransition:1sと同じだけ遅延させます。

###メリット
・アニメーション部分をCSSに任せているのでややこしい計算が不要
・ease-inなどスクロール速度の調整もtransition-timing-functionで簡単に実装できる
・15行程度の簡単なことしかしていない分管理が楽

###デメリット
・指定位置までスクロールさせることはできない
・一瞬画面を固定にすることでスクロールバーが消え、アニメーション中だけ横幅が変わるのでoverflow-y:scrollでスクロールバーを常時表示にしておく必要がある
::-webkit-scrollbar{display:none}でも可
・背景画像を使っている場合調整が少し面倒

##おまけ(id位置までスクロールさせたい)
せっかくなのでid位置までスクロールさせられるようにしました。
最初からこっちだけ書けばよかった気もしますね。

###html

omake.html
<!DOCTYPE html>
<html>
<head>
	<title>サンプル</title>
	<link rel="stylesheet" href="sample.css">
	<script src="omake.js"></script>
</head>
<body>
	<button id="toTop" onclick="backToTop(r1)">2つめの赤</button>
	<button id="toTop" onclick="backToTop(r2)">3つめの赤</button>
	<button id="toTop" onclick="backToTop(r3)">4つめの赤</button>
	<div style="height: 25vh;background-color:#fcc;"></div>
	<div style="height: 25vh;background-color:#cfc;"></div>
	<div style="height: 25vh;background-color:#ccf;"></div>
	<div id="r1" style="height: 25vh;background-color:#fcc;"></div>
	<div style="height: 25vh;background-color:#cfc;"></div>
	<div style="height: 25vh;background-color:#ccf;"></div>
	<div id="r2" style="height: 25vh;background-color:#fcc;"></div>
	<div style="height: 25vh;background-color:#cfc;"></div>
	<div style="height: 25vh;background-color:#ccf;"></div>
	<div id="r3" style="height: 25vh;background-color:#fcc;"></div>
	<div style="height: 25vh;background-color:#cfc;"></div>
	<div style="height: 25vh;background-color:#ccf;"></div>
	<button id="toTop" onclick="backToTop()">トップに戻る</button>
</body>
</html>

###JavaScript

omake.js

//id位置まで飛べるスムーススクロール風
function backToTop(id){

	//現在位置を取得とbodyの高さ以上にスクロールしないよう制限
	let scrollPosition = window.pageYOffset;
	let scrollLimit = document.body.clientHeight - window.innerHeight; //高さ制限 = bodyの高さ - 表示画面の高さ

		//bodyをfixedで固定
		document.body.classList.add('fixed');
		//topで現在表示位置までbodyをずらす
		document.body.setAttribute('style', 'top:-' + scrollPosition + 'px;');
		//ずらしたbodyを一番上に戻す
		window.setTimeout(
		//引数=idの有無で動作を分ける
			function(){
				if(typeof id == "undefined"){ //引数がない場合は画面トップまで
					document.body.setAttribute('style', 'top:0px');

				}else if(typeof id == "object"){ //引数=idがある場合はidの位置まで
					if (id.getBoundingClientRect().top > scrollLimit) {
						document.body.setAttribute('style', 'top:-' + scrollLimit + 'px');
					}else{
						document.body.setAttribute('style', 'top:-' + id.getBoundingClientRect().top + 'px');
					}
				}
			},10
		);
	window.setTimeout(
		function(){
			//スクロール完了後に追加したクラス・インラインスタイルを削除する
			document.body.removeAttribute('class');
			document.body.removeAttribute('style');
			//実際のidの位置までスクロールする
			if(typeof id == "object"){
				if (id.getBoundingClientRect().top > scrollLimit) {
					window.scrollTo(0, scrollLimit);
				}else{
					window.scrollTo(0, id.getBoundingClientRect().top);
				}
			}
		},1000
	);
}

###簡単な解説
やっていることはほぼ同じです。
onclick='backToTop(id)'と引数を渡すことでidまでスクロールするようにしただけです。
こちらは最後にscrollTo()を使っているのでかろうじてスムースクロールといってもいいかもしれません。
なお、引数を入れなければ画面トップに移動します。

こうなるとファンクション名が不適当すぎるので、使う際はちゃんとgoTo()とかに変更してくださいね

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?