Vueだけでつくる!ページトップへ自動スクロールするコンポーネント(DL可)

さてさて、前回は時・分が連携する時間入力コンポーネントでユーザビリティがテーマの記事をお届けしましたが、今回もその流れで「あれば地味に便利」という機能をつくってみたいと思います。

それは何かと言うと「クリックするとページの一番上まで自動的にスクロールしてくれる」機能です。

特にページが縦に長い場合だと便利なんですが、例えば以下のようなボタンをクリックすると一番上まで移動してくれたら便利じゃありませんか??

なお、実は自動スクロールする部分はJavaScriptライブラリのパイオニアjQueryで以下のようにすぐ実装することができます。

$('html,body').animate({
    scrollTop: 0
}, 1000); // 1秒で移動

でも!

最近はjQueryを使わないサイトもじわじわ増えつつありますので、今回はjQueryは一切使わずVueJavaScriptだけでコンポーネントをつくってみます。

ぜひ皆さんのお役に立てると嬉しいです😊✨

開発環境: Vue 2.6

やりたいこと

せっかくコンポーネント化するので、次のような7つの特徴があるボタンを作ってみます。

  1. クリックしたら自動スクロール(しかも、アニメーションする)
  2. ボタンはある一定以上スクロールされた時だけ表示
  3. 表示/非表示のはカッコ良く「フェードイン」&「フェードアウト」
  4. ボタンをページの左上・右上・左下・右下のどれかに固定する
  5. 移動する速度(秒数)を指定できる
  6. ボタンのテキストは自由に変更可能
  7. CSSだって自由に追加できちゃう!

では、実際につくっていきましょう。

Vueでページトップに自動スクロールするコンポーネントをつくる

では、今回もVue初心者にもわかるように順を追って説明をしていきます。

Vueを使えるようにしてコンポーネントの基本をつくる

では、まずはをcdn(インターネット上で公開されているパッケージ)からVueを読み込んで、「v-move-to-top」という名前のコンポーネントをつくっていきます。

<html>
<body>
<div id="app">

    <!-- コンポーネント -->
    <v-move-to-top></v-move-to-top>

</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>
<script>

    Vue.component('v-move-to-top', {
        template: '<button type="button">上へ</button>'
    });

    new Vue({
        el: '#app'
    });

</script>
</body>
</html>

Vueコンポーネントは第1引数に名前、そして第2引数にオプションを指定します。

そして、templateは実際に目に見えるHTMLを指定するのですが、今の段階では次のような、普通のボタンが表示されるだけです。

では、このボタンを基本としていろいろな機能を追加していきましょう。

クリックしたら自動スクロールでページトップへ移動するようにする

では、メインになる自動スクロールの機能を実装していきます。

Vue.component('v-move-to-top', {
    template: '<button type="button" @click="moveToTop">上へ</button>',
    methods: {
        moveToTop() {

            const duration = 1000;  // 移動速度(1秒で終了)
            const interval = 25;    // 0.025秒ごとに移動
            const step = -window.scrollY / Math.ceil(duration / interval); // 1回に移動する距離
            const timer = setInterval(() => {

                window.scrollBy(0, step);   // スクロール位置を移動

                if(window.scrollY <= 0) {

                    clearInterval(timer);

                }

            }, interval);

        }
    }
});

この中では、スクロール位置を移動させるmoveToTop()というメソッドを追加し、さらにこのメソッドをクリックで実行できるように先ほどのボタンに@click=""でセットしています。

なお、このメソッドではsetInterval()を使って0.025秒ごとに何度もコードが実行されます。そして、その度にscrollBy()で少しずつスクロール位置が変更するので、あたかもアニメーションのように少しずつ移動するように見えるという訳ですね。

特定のスクロール位置で表示・非表示を切り替える

では続いて、スクロール位置によってボタンを「表示/非表示」にする機能をつくっていきます。

ちなみに、なぜこんな機能をつけるかというと、

「スクロールしてないのに “上へ” ボタンが表示されのは、ちょっと邪魔・・・😅」

だからです。しかも、それほど縦が長くないページはスクロール自体が必要ないので、自動的に非表示になるようにしておくほうがユーザビリティ的にはグッド!だと思います。

Vue.component('v-move-to-top', {
    data() {
        return {
            show: false
        };
    },
    template: '<transition name="fade">'+
        '<button type="button" v-if="show" @click="moveToTop">上へ</button>'+
        '</transition>',

    // 省略

    mounted() {

        // CSSを追加
        let style = document.createElement('style');
        style.innerHTML = `
                    .fade-enter-active, .fade-leave-active {
                        transition: opacity .5s;
                    }
                    .fade-enter, .fade-leave-to {
                        opacity: 0;
                    }
                `;
        document.getElementsByTagName('head')[0].appendChild(style);

        // スクロール・イベントを追加
        window.addEventListener('scroll', () => {

            this.show = (window.scrollY > 150);

        });

    }
});

まずこの中で変更したのは、まずtemplateの部分です。

先ほどは、<button>〜</button>だけでしたが、その外側を<transition name="fade">〜</transition>で囲っています。

そして、mounted()内で、このタグがフェードイン&フェードアウトに必要な以下のCSSを「ページ本体」に追加しています。

// name が "fade" なので以下のようになります

.fade-enter-active, .fade-leave-active {
    transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
    opacity: 0;
}

※ JS内でCSSを定義することには議論があると思いますが、今回はコンポーネントひとつだけで完結させるためにあえてこの形をとりました。気になる方は通常通りCSSで定義してください。

さらに、表示/非表示を切り替えるためにshow変数を定義してボタンにv-if="show"として追加し、さらにその切り替わりはスクロール・イベントで「スクロール位置が150を超えてるかどうか」で判断するようにしています。

これでフェードイン・アウトが実装できました。

ボタンをページの左上・右上・左下・右下に固定する

では、続いてボタンをどこにスクロールしていても常に、

  • 左上
  • 右上
  • 左下
  • 右下

で固定できるようにしてみましょう。

Vue.component('v-move-to-top', {
    props: ['position'],

    // 省略

    template: '<transition name="fade">'+
        '<button type="button" :style="styles" v-if="show" @click="moveToTop">上へ</button>'+
        '</transition>',

    // 省略

    computed: {
        styles() {

            let styles = { position: 'fixed' };

            if(this.position === 'top-left') {

                styles['top'] = '15px';
                styles['left'] = '10px';

            } else if(this.position === 'top-right') {

                styles['top'] = '15px';
                styles['right'] = '10px';

            } else if(this.position === 'bottom-left') {

                styles['bottom'] = '15px';
                styles['left'] = '10px';

            } else {

                styles['bottom'] = '15px';
                styles['right'] = '10px';

            }

            return styles;

        }
    },

    // 省略

});

ここでやったことは、まずpropsの追加です。
これは、以下のようにコンポーネントがプロパティを使えるようにする設定です。

<!-- ボタンを左下で固定 -->
<v-move-to-top position="bottom-left"></v-move-to-top>

つまり、ページのどの位置にボタンを表示するかはpositionで指定することができます。

そして、このpositionプロパティによって位置を固定するスタイルシートを作っているのがcomputedstyles()です。

例えば、positiontop-rightの場合、作成されるスタイルシートは以下のようになります。

{
    position: 'fixed',
    top: '15px',
    right: '10px'
}

最後にボタンに:style="styles"として適用すれば4つの位置で固定できるようになります。(ちなみに指定しない場合は右下で固定されます)

移動速度(秒)を指定できるようにする

冒頭にjQueryでスクロール位置を変更するサンプルを紹介しましたが、その中では1000ミリ秒(=1秒)で移動を完了するという設定でした。

せっかくですので、今回のコンポーネントにもこの機能が使えるようにしましょう。

Vue.component('v-move-to-top', {
    props: ['position', 'duration'],

    // 省略

    methods: {
        moveToTop() {

            const duration = parseInt(this.duration);
            
            // 省略

        }
    },
    
    // 省略

});

といっても、ほぼコードは出来上がっているのでpropsdurationを追加し、moveToTop()に適用するだけです。

なお、parseInt()を使っているのはdurationには文字列が入ってくる可能性があるためです。実際にはエラーは発生しませんでしたが、今後の拡張などを見据えてこういう部分は正確にしておくことをおすすめします。

※ ちなみに以下のように:をつけてデータとして指定すると数値として扱ってくれます。

<v-move-to-top :duration="1000"></v-move-to-top>

表示するテキストを自由に変更できるようにする

ほぼ完成に近づいてきましたが、ここでより汎用的に使えるようにボタンのテキストを自由に変更できるようにしてみましょう。

Vue.component('v-move-to-top', {

    // 省略

    template: '<transition name="fade">'+
        '<button type="button" :style="styles" v-if="show" @click="moveToTop"><slot></slot></button>'+
        '</transition>',
   
    // 省略

});

この中で、ボタンのテキストを<slot></slot>に変更しました。

こうすることで、以下のようにタグで囲んだ部分が自動的に置き換わるようになる、つまり、ボタンのテキストが指定できるようになるという訳です。

<v-move-to-top>ここが自由に変更できる</v-move-to-top>

CSSを自由に追加できるようにする

実は、コード的にはすでに完成していてコンポーネントのタグに直接CSSを追加することができます。

では、サンプルとしてCSSフレームワークのTailwindを使ってボタンを装飾してみましょう。

<html>
<head>
    <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
<div id="app">

    <v-move-to-top
        position="bottom-right"
        duration="300"
        class="bg-gray-800 text-white font-bold p-2 px-4 rounded opacity-50">
        上へ
    </v-move-to-top>

</div>

<!-- 省略 -->

</body>
</html>

これで実行したものが以下のボタンです。

お疲れ様でした😊✨

テストしてみる

では、今回作ったコンポーネントが実際に動くかをテストしてみましょう。

なお、スクロールできないとチェックができないので以下のように100個の改行を作って実行してみます。

<div id="app">

    <!-- テスト用に改行100個 -->
    <div v-for="i in 100">
        改行<span v-text="i"></span>
    </div>

    <v-move-to-top
        position="bottom-right"
        duration="300"
        class="bg-gray-800 text-white font-bold p-2 px-4 rounded opacity-50">
        上へ
    </v-move-to-top>

</div>

では、これで実行してみましょう!(ボタンはページ右下で固定されます)

うまくいきました😊✨

ダウンロードする

今回実際に開発したソースコードを以下からダウンロードすることができます。

cdnを使っているので展開してすぐ試すことができますよ!

ページトップへ自動スクロールするVueコンポーネント

おわりに

ということで今回は、あれば便利なVueコンポーネントとして自動スクロール機能を開発してみました。

実は、今回はじめて<transition>〜</transition>を使ったのですがシンプルに使えるようになっていてちょっと感動しました。さらに、フェードイン・アウトだけじゃなく他にも使い方があるようなので、jQueryを使っていないサイトでは重宝するかもしれません。

また、普段はwindow.scrollTo()を使っているのですが今回はあえてwindow.scrollBy()を使ってみました。

なぜかというと、当初は以下のようにしていたのですがこれだと毎回スクロール位置を計算する必要があったからかintervalが短いほど移動速度が遅くなってしまったからです。

const duration = 3000;
const interval = 25;
const step = Math.ceil(window.scrollY / interval);

const timer = setInterval(() => {

    window.scrollTo(0, window.scrollY-step);

    if(window.scrollY <= 0) {

        clearInterval(timer);

    }

}, interval);

アイデア次第で解決できることも多いだなと実感しました😊✨

なにわともあれ、今回はこのへんで終了です。
ぜひ皆さんも「あると便利かも」を探してみてくださいね。

ではでは〜!

この記事が役立ちましたらシェアお願いします😊✨