【javascript】スムーススクロールを✅

5時30起き23時30寝をすると14時に必ず眠くなるという法則を見つけて謎に喜んでいる23期中野昴です。

多くの方からラジオボタンを押したら次の項目にぬるっと動いたらいいよねという声をいただきました。

ユーザーの意見を即反映させることが僕のポリシーなので、今回はスクロールイベントに関して学習していきたいと思います。

 

スクロールイベントとは

画面を上下左右に移動させた時に始まる出来事。

スクロールイベントを分解すると

  1. クリックするとクリックイベントが発火
  2. クリックイベントに定義されている関数を実行

になる。

クリックイベントを発火

これに関しては、vueのイベントハンドリング機能が使えそう。

いわゆるv-onディレクティブを指し、jqueryだったらイライラDOM操作をしないといけないが、vueだと@click=だけで済む。Vueの良いところっすね。

イベントハンドリング

v-on ディレクティブを使うことで、DOM イベントの購読、イベント発火時の JavaScript の実行が可能になります。

下記のようbuttonをクリックするとplusOneという関数が実行されるようになっている。

<div id="example-1">
  <button @click="plusOne()">Add 1</button>
  <p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
  el: '#example-1',
  data: {
    counter: 0
  }
,
methods:{

plusOne(){
counter += 1

},
}
})

言ってしまえば、vanila javascriptで書いていた下記のようなDOM操作を@click一つで省略することができているんです。素晴らしい。

<input type="button" value="button" id="xxx">

<script>
function butotnClick(){
    alert('Click');
}

let button = document.getElementById('xxx');
button.onclick = butotnClick;
</script>

Event Handling - Vue.js

クリックしたら関数を実行する

実際に実行される関数を定義していきます。

Vue-scrolltoといった優れたライブラリもありますが、今回は組み込みライブラリである、window.scrollToを使用していきます。

window.scrollto

windowオブジェクトのscrollTo()メソッドは、スクロール位置を指定座標(モニタ上の絶対位置)へ移動します。 モニタ上の絶対位置とは、モニタの左上を基準にして水平方向・垂直方向の距離を指定した位置のことです。

ちなみにscrollBy(x,y)は、現在の表示位置からの相対的な移動に使う。

使用方法

下記のように、window.scrollToの引数にx軸とy軸どのくらい移動させたいのかを書きます。

window.scrollTo(x-coord, y-coord)
#引数は任意の値に設定することができる。
window.scrollTo(options)

https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo

試しに、

window.scrollTo(0,200)でやってみました。

Image from Gyazo

あれ、なんか一つ目しか適用されてないし、なんか違和感あるな。。

このようにリストレンダリングを使用している場合、次の要素までの距離が分からないという問題が生じてしまいます。

絶対的な値ではなく、相対的な値を引数に設定したい。

そこで、下記3つのメソッドを使って解決していきます。

  • Event.currentTarget
  • Element.getBoundingClientRect()
  • Window.pageYOffset

Event.currentTarget

イベントの現在のターゲットを識別します。イベントが発生した要素を特定する event.target とは対照的に、常にイベントハンドラがアタッチされた要素を参照します

要するに、クリック感知することですね。

Element.getBoundingClientRect()

要素の寸法と、そのビューポートに対する位置を返します。

クリックされた位置を取得しています。

Window.pageYOffset

pageYOffset is an alias for [scrollY](<https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY>); as such, it returns the number of pixels the document is currently scrolled along the vertical axis (that is, up or down) with a value of 0.0,

scrollY等のエイリアスで、現在スクロールされているY軸の座標を取得できるメソッドになります。

実際に使ってみる

最終的なメソッドは以下の通りになります。

clickScroll(e) {
      const targetArea = e.currentTarget.getBoundingClientRect().top;
      window.scrollTo({
        top: window.pageYOffset + targetArea,
        behavior: 'smooth',
      });
    },

まず、eという仮引数に、ユーザーがクリックした座標が代入されます。

window.scrollToのtopに先程取得したy軸の座標を、遷移時の挙動をぬるっとしたいので、behaviorはsmoothに設定してあります。

このメソッドをイベントハンドリングを用いてradio buttonタグに渡しておきます。

<v-col
            v-for="question in questions"
            :key="question.num"
            cols="12"
            xs="12"
            sm="12"
            md="12"
            lg="12"
          >
            <v-layout justify-center>
              <v-card-title>
                <v-container>
                  <v-card
                    class="text-center mx-auto my-5 form rounded"
                    elevation="24"
                    shaped
                    width="500"
                    id="form"
                  >
                    <v-card-title
                      style="width: 100%"
                      class="headline justify-center"
                      :id="'bigq' + question.num"
                    >
                      Q{{ question.num }}.
                      {{ question.title }}
                    </v-card-title>
                    <v-radio-group :id="'smallq' + question.num" row class="mx-16 px-9">
                      <v-radio
                        class="mx-auto justify-center"
                        fab
                        light
                        :ripple="{ center: false, class: 'gray--text' }"
                        @click="
                          clickScroll($event);
                          countAnswer(question.num, 1);
                        "
                        label="1: いつも"
                      ></v-radio>
                      <v-radio
                        class="mx-auto justify-center"
                        fab
                        light
                        :ripple="{ center: false, class: 'gray--text' }"
                        @click="
                          clickScroll($event);
                          countAnswer(question.num, 2);
                        "
                        label="2: 時々"
                      ></v-radio>
                      <v-radio
                        class="mx-auto justify-center"
                        fab
                        light
                        :ripple="{ center: false, class: 'gray--text' }"
                        @click="
                          clickScroll($event);
                          countAnswer(question.num, 3);
                        "
                        label="3: 全くない"
                      ></v-radio>
                    </v-radio-group>
                    <p v-if="question.answer === '未回答'">
                      <strong class="red--text accent-3">未回答です!</strong>
                    </p>
                    <p class="py-3" style="font-size: 16px">あなたの回答: {{ question.answer }}</p>
                  </v-card>
                </v-container>
              </v-card-title>
            </v-layout>
          </v-col>

実際の挙動

Image from Gyazo

まとめ

  • クリック遷移をぬるっとさせたい場合は、下記メソッドを頼る。

    Event.currentTarget

    Element.getBoundingClientRect()

    Window.pageYOffset

    behavior: 'smooth'

  • scrollTo(x,y)とscroll(x,y)は同じ挙動をする。

一言

アドベントカレンダやらなきゃーーーー