はじめに
簡単な見出しスクロールを作ってみる。デフォルトではscroll-behaviorというのがあるが、それに頼らず作ってみる。イメージ的には指数関数的に減衰して見出しのところで止まるものを作る。
こっちで遊べるみたいです。
完成イメージ
こんな風に、クリックすると同じ色の見出しが上に来るようにする。
なお文章の内容はロレムイプサムだと無味乾燥でつまんないので、以前書いた数学関連のPDFを雑にコピペしたものを使っている。色々面倒なので。
コードはこんな感じ
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.9/lib/p5.js"></script>
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.9/lib/addons/p5.sound.min.js"></script>
<script src="mySketch.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<main>
<div id="main">
<!-- メインコンテンツ -->
<h2 class="index" id="index-top">おれは!げんき!!!</h2>
<!-- なんか長い文章 -->
<h2 class="index" id="index0">おれは!げんき!!!</h2>
<!-- なんか長い文章 -->
<h2 class="index" id="index1">おれは!げんき!!!</h2>
<!-- なんか長い文章 -->
<h2 class="index" id="index2">おれは!げんき!!!</h2>
<!-- なんか長い文章 -->
<h2 class="index" id="index3">おれは!げんき!!!</h2>
<!-- なんか長い文章 -->
</div>
<div id="index">
<a class="link" id="link-top" href="#index-top">みだし</a>
<a class="link" id="link0" href="#index0">みだし</a>
<a class="link" id="link1" href="#index1">みだし</a>
<a class="link" id="link2" href="#index2">みだし</a>
<a class="link" id="link3" href="#index3">みだし</a>
</div>
</main>
</body>
</html>
html,
body {
margin: 0;
padding: 0;
}
main{
display:flex;
flex-direction:row;
}
#main{
width:100%;
max-width:900px;
height:100dvh;
overflow:scroll;
}
.link{
display:block;
margin:10px;
color:black;
}
#index-top, #link-top{
background-color:#ffcc44;
}
#index0, #link0{
background-color:#ff44cc;
}
#index1, #link1{
background-color:#44ff44;
}
#index2, #link2{
background-color:#44ccff;
}
#index3, #link3{
background-color:#cc44ff;
}
// いろいろ
普通にリンクを張ってみる
まず、スクロール位置だけ合わせるようにしてみる。次のようにする。
function setup() {
createCanvas(100,100);
background(100);
}
function draw() {
circle(mouseX, mouseY, 20);
}
function createJump(){
// step1. classがlinkのすべての要素を取得
const indices = document.querySelectorAll(".link");
// step2. それぞれにaddEventListener. preventDefaultでデフォルトの挙動を殺す
for(const index of indices){
index.addEventListener("click", (e) => {
e.preventDefault();
// step3. メインタグを取得
const mainTag = document.querySelector("#main");
// step4. スクロールの限界を計算
const scrollLimit = mainTag.scrollHeight - mainTag.offsetHeight;
// step5. targetのDOMをhrefから計算する
const targetDom = document.querySelector(index.getAttribute("href"));
// step6. 目標値を計算
const targetValue = Math.min(scrollLimit, targetDom.offsetTop);
// step7. 設定する
mainTag.scrollTop = targetValue;
});
}
}
document.addEventListener("DOMContentLoaded", createJump);
まずquerySelectorAllでリンクをすべて取得します。それらにclickのイベントリスナーを付けるんですが、これらはaタグなのですでにデフォルトでhrefに「#」で指定したidのところにジャンプする機能が備わっています。これを「preventDefault」でつぶします。
次に、今回のスクロールの対象はidがmainのタグのエリアなので(overflowをscrollにしてある)、それのタグを取得します。スクロールの限界は、これの実際の高さであるscrollHeightから表示上の高さであるoffsetHeightを引いた値となります。
さらに、リンク先である p タグを取得します。これはリンクのDOMのhrefをquerySelectorに入れれば自動的に手に入るよう作られています。これとoffsetTopを比較して小さい方を取ります。
そこで、それをscrollTopに代入すれば、ひとまず完成です。クリックするとその位置に飛びます。
滑らかに動くようにする
今のままだと遷移がダイレクトすぎるので、滑らかに移動するようにします。
function setup() {
createCanvas(100,100);
background(100);
}
function draw() {
circle(mouseX, mouseY, 20);
}
function createJump(){
const damper = {
id:-1,
factor:0.8,
threshold:1,
target:0,
value:0
}
// step1. classがlinkのすべての要素を取得
const indices = document.querySelectorAll(".link");
// step2. それぞれにaddEventListener. preventDefaultでデフォルトの挙動を殺す
for(const index of indices){
index.addEventListener("click", (e) => {
e.preventDefault();
// step3. メインタグを取得
const mainTag = document.querySelector("#main");
// step4. スクロールの限界を計算
const scrollLimit = mainTag.scrollHeight - mainTag.offsetHeight;
// step5. targetのDOMをhrefから計算する
const targetDom = document.querySelector(index.getAttribute("href"));
// step6. 目標値を計算
const targetValue = Math.min(scrollLimit, targetDom.offsetTop);
if(damper.id >= 0) return;
damper.value = mainTag.scrollTop - targetValue;
damper.target = targetValue;
damper.id = setInterval(() => {
damper.value *= damper.factor;
mainTag.scrollTop = damper.target + damper.value;
if(Math.abs(damper.value) < damper.threshold){
mainTag.scrollTop = damper.target;
clearInterval(damper.id);
damper.id = -1;
}
}, 16);
});
}
}
document.addEventListener("DOMContentLoaded", createJump);
damper(減衰機)を使います。まず目標値との差分を設定し、一定の割合で掛け算を施して小さくしていきます。それを目標値に足してその都度scrollTopに設定します。これで滑らかに変化します。閾値より小さくなったら0にしてclearIntervalで終了します。
以上です。
おわりに
Firefoxとかだとあんま滑らかじゃないですね...スマホだときれいでした。横幅を変化させてもきちんとスクロールするのを確かめてみてください。多分もっときれいな方法があると思います。
拝読感謝。