スムーススクロールをjQueryなしで実装

スムーススクロールをjQueryなしで実装

脱jQuery第2弾。スムーススクロールjQueryなしで実装していきます。
意外にこれって大変でした。。

制作コード

今回作ったコードは以下のものになります。

コードをクリップボードにコピー
// 関数定義 -------------------------
const smoothScroll = () =>{
  let links = document.querySelectorAll('a[href^="#"]');
  const speed = 200;          // スクロールスピード   
  const divisor = 100;        // 分割数
  const tolerance = 5;        // 許容誤差
  const headerHeight = 0;     // 固定ヘッダーがある場合はその高さ分ずらす
  const interval = speed/divisor;
  for(let i = 0; i < links.length; i++){
    links[i].addEventListener('click',(e)=>{
      e.preventDefault();
      let nowY = window.pageYOffset;
      const href = e.currentTarget.getAttribute('href');
      const target = document.querySelector(href);
      if( target != null){
        const targetRectTop = target.getBoundingClientRect().top;
        const targetY = targetRectTop + nowY - headerHeight;
        const minY = Math.abs((targetY - nowY)/divisor);
        doScroll(minY,nowY,targetY,tolerance,interval);
      }
    });
  }
}

const doScroll = (minY,nowY,targetY,tolerance,interval) =>{
  let toY ;
  if( targetY < nowY ){
    toY = nowY - minY;
  }else{
    toY = nowY + minY;
  }
  window.scrollTo(0, toY);
  if( targetY - tolerance > toY || toY > targetY + tolerance){
    window.setTimeout(doScroll,interval,minY,toY,targetY,tolerance,interval);
  }else{
    return false;
  }
}

// 関数実行 -------------------------
smoothScroll();

コードの説明

コードを見ながらのほうが説明は読みやすいと思うので、ここから先を読む人はポップアップでコードを出して読んでください。

コードをポップアップで見る

smoothScroll関数

4~8行目

2〜23行目にあるsmoothScroll関数について簡単に説明していきます。
スクロールのスピードを決めているのは、4行目のspeedの値です。
5行目の分割数というのは、目的の要素までたどり着くまでに何回 window.scrollTo();を実行するかということです。

例えばアンカーボタンを押した時に
アンカーの場所まで1000pxの時には
現在の設定値だと、window.scrollTo(0,10px);を2ミリ秒ごとに100回繰り返して、アンカーの場所まで移動します。

設定値はお好みで変えてみてください。

6行目 toleranceはズレの許容誤差です。毎回綺麗に割り切れる数字で分割される訳ではないので、アンカーの±toleranceまで来たら、アンカーのところまでスクロールさせて処理を終了させます。

7行目は固定ヘッダーがあった場合に、ヘッダーの高さを入れます。
固定ヘッダーがある場合は、その高さ分を考慮してアンカーの場所をずらします。
ヘッダーが可変の高さの場合は、7行目を下記のように書き換えればいいと思います。

const header = document.getElementById('ヘッダーのid');
const headerHeight = header.clientHeight;

11~20行目

11行目は、aタグをクリックした時に発生するイベントを1度全てキャンセルしています。キャンセルしておかないと、アンカーまで移動しちゃいます。

12行目は、アンカーリンクをクリックした時点のスクロール量を取得しています。これを用いて、アンカーまでの距離を計算します。

13行目は、クリックしたアンカーリンクのhrefを取得しています。

14行目は、13行目で取得したhrefを元にアンカーの要素を取得しています。
もしこの時に href の値の要素がなかった場合は、うまく動かないので、15行目でif文を入れています。

17行目は、16行目で取得した要素の絶対座標を元に、アンカー要素の絶対座標targetYを算出しています。
17行目では targetY に nowY を足して、18行目では nowYを引いているので、一見無駄なことをしているようですが、targetY は後述の繰り返しの時に必要な値なので、ここは気にしないでください。

18行目は1回ごとに進む最小のスクロール量をminYを出しています。

以上で出た値や、最初の設定値をdoScroll関数に渡しています。

doScroll関数

doScroll関数は divisor の数だけ繰り返し実行される関数です。
これが divisor回繰り返されることで、アンカーの要素までスクロールされます。一回の進み幅は minY です。

最初に定義されている toY はdoScroll関数が1回実行されるごとスクロールさせる絶対座標Yの位置です。
例えば1回目は、アンカーがアンカーリンクより下にある時は
toY = nowY1 + minY
です。

2回目は
toY = nowY2 + minY
となります。
ここで
nowY2 = nowY1 + minY
です。

ですので、最後のdivisor回目には
toY = nowY1 + minY×divisor ≒ targetY
となります。

doScroll関数の中で意外に困ったのが、setTimeoutで関数に値を渡すことでした。
intervalが2つあるので混乱しますが、
setTimeout( 's秒後に発火する関数' , s , 関数の引数 )
となっています。
意外にハマっちゃいました。

以上、スムーススクロールをjQueryなしで実装するでした。

arrow_circle_up