Quantcast
Channel: CSSタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8725

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

$
0
0

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

コード

html

sample.html
<!DOCTYPE html><html><head><title>サンプル</title><linkrel="stylesheet"href="sample.css"><script src="sample.js"></script></head><body><divstyle="height: 25vh;background-color:#fcc;"></div><divstyle="height: 25vh;background-color:#cfc;"></div><divstyle="height: 25vh;background-color:#ccf;"></div><divstyle="height: 25vh;background-color:#fcc;"></div><divstyle="height: 25vh;background-color:#cfc;"></div><divstyle="height: 25vh;background-color:#ccf;"></div><buttonid="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
functionbackToTop(){letscrollPosition=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
//スクロール量を取得letscrollPosition=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');},0);

次に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><linkrel="stylesheet"href="sample.css"><script src="omake.js"></script></head><body><buttonid="toTop"onclick="backToTop(r1)">2つめの赤</button><buttonid="toTop"onclick="backToTop(r2)">3つめの赤</button><buttonid="toTop"onclick="backToTop(r3)">4つめの赤</button><divstyle="height: 25vh;background-color:#fcc;"></div><divstyle="height: 25vh;background-color:#cfc;"></div><divstyle="height: 25vh;background-color:#ccf;"></div><divid="r1"style="height: 25vh;background-color:#fcc;"></div><divstyle="height: 25vh;background-color:#cfc;"></div><divstyle="height: 25vh;background-color:#ccf;"></div><divid="r2"style="height: 25vh;background-color:#fcc;"></div><divstyle="height: 25vh;background-color:#cfc;"></div><divstyle="height: 25vh;background-color:#ccf;"></div><divid="r3"style="height: 25vh;background-color:#fcc;"></div><divstyle="height: 25vh;background-color:#cfc;"></div><divstyle="height: 25vh;background-color:#ccf;"></div><buttonid="toTop"onclick="backToTop()">トップに戻る</button></body></html>

JavaScript

omake.js
//id位置まで飛べるスムーススクロール風functionbackToTop(id){//現在位置を取得とbodyの高さ以上にスクロールしないよう制限letscrollPosition=window.pageYOffset;letscrollLimit=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(typeofid=="undefined"){//引数がない場合は画面トップまでdocument.body.setAttribute('style','top:0px');}elseif(typeofid=="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');}}},0);window.setTimeout(function(){//スクロール完了後に追加したクラス・インラインスタイルを削除するdocument.body.removeAttribute('class');document.body.removeAttribute('style');//実際のidの位置までスクロールするif(typeofid=="object"){if(id.getBoundingClientRect().top>scrollLimit){window.scrollTo(0,scrollLimit);}else{window.scrollTo(0,id.getBoundingClientRect().top);}}},1000);}

簡単な解説

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

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


Viewing all articles
Browse latest Browse all 8725

Latest Images

Trending Articles

<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>