皆様こんにちわ!タカモリです。 カタカタジムで新規に会員登録をする場合に、スクロールで利用規約に同意する画面があったことにお気づきでしょうか?

今回、この利用規約への同意のスクロールについて、UIについて議論があり別の方法に切り替えることになったのですが、そのまま削除するのはもったいないので今回どのように僕が作成したかを皆様に紹介したいと思います。

簡略化の為に、端折っての説明となりますがご了承ください。

まずは、どのような動作をするかから見ていきましょう。

実際の動作について

まずは初期状態です。 こちらは、ただ表示しているだけですが、利用規約にチェックを入れるボックスは非活性(disabled)です。

スクリーンショット 2021-08-30 7.44.56.png

この状態でボックスをクリックすると、エラーが表示されます。

スクリーンショット 2021-08-30 7.45.13.png

一番下までスクロールをすることにより、ボックスが活性化されます。

スクリーンショット 2021-08-30 7.45.22.png

チェックを入れるとボタンが表示されます。

スクリーンショット 2021-08-30 7.45.28.png

Nuxtの私が書いたソースコード

続いてソースコードを見ていきましょう。 カタカタジムはnuxtで作成していますので、かなり上級者向けだとは思うのですが、普通のJavaScriptでも応用を効かすことができると思います。難しければ、是非、カタカタジムの質問に書き込んでくださいね!

こちらが、大まかに見た時のソースコードです。クラス名だったり不必要なものまで入ってますがご容赦ください。笑

<div class="form-item service-scroll-box">
 <label class="label" for="service">利用規約</label>
  <Scroll @scrollTrigger="scroll">
   <Service />
  </Scroll>
</div>
<div class="form-item">
 <div class="check">
  <span>利用規約に同意する</span>
  <CheckBox
   :active="read"
   :state="form.serviceCheck"
   @click="check"/>
 </div>
 <p class="error-message fc-red service" v-if="scrollError">
  利用規約を最後までお読みください。
 </p>
</div>

関係するスクリプトは以下です。

data() {
 return  {
  form: {
   serviceCheck: false,
  },
  read: false,           // 利用規約を読んだ
  scrollError: false  // 下までスクロールされてない時にクリックがあった
 }
},
methods: {
 /**
  * 利用規約のスクロール確認
  */
 scroll(e) {
  this.scrollError = false
  e ? this.read = true : this.read = false
  this.form.serviceCheck = false
 },
  /**
   * 利用規約に同意チェック
   */
  check() {
    if (!this.read) {
     this.scrollError = true
     return
    }
    this.form.serviceCheck = !this.form.serviceCheck
  },
}

上から順番に見ていきます。まずは、Scrollコンポーネントというのがありますが、中身はこうです。

<template>
    <div @scroll="scroll" class="service scroll" id="register-scroll">
        <slot>デフォルト</slot>
    </div>
</template>
<script>
export default {
    methods: {
        scroll(e) {
            const scrollHeight = e.target.scrollHeight // スクロールの高さ(中身)
            const clientHeight = e.target.clientHeight // スクロール枠の高さ
            const scrollTop = e.target.scrollTop // 現在のスクロール位置

            /**
             * スクロールの高さ(中身) - (スクロール枠の高さ + 現在のスクロール位置) == 0
             */
            if (scrollHeight - (clientHeight + scrollTop) <= 0) {
                this.$emit('scrollTrigger', true)
            } else {
                this.$emit('scrollTrigger', false)
            }
        },
    }
}
</script>

下手くそやとか言わんといてくださいね!w この id register-scrollというdivのスクロールイベント時、scrollメソッドにより、今のスクロールの値がtrueかfalseかを、親のコンポーネントにemitのscrollTriggerイベントで渡しているのですね。

それを受け取った親コンポーネントでは、それを親コンポーネントのscrollイベントに渡します。

この部分です。

<div class="form-item service-scroll-box">
 <label class="label" for="service">利用規約</label>
 <Scroll @scrollTrigger="scroll">
   <Service />
  </Scroll>
</div>

この部分で、scrollメソッドが動作します。

methods: {
 /**
  * 利用規約のスクロール確認
  */
 scroll(e) {
  this.scrollError = false
  e ? this.read = true : this.read = false
  this.form.serviceCheck = false
 },
}

毎回スクロールがあった時に、scrollErrorをfalseにしてエラーの表示を削除していますが、ここはUIの好き好みです。僕は毎回スクロール時にエラーを非表示にしていますが、いいかどうかはわからないです笑

scrollメソッドの引数にはスクロールが一番下にあるかどうかの結果が入っていますが、一番下ならreadをtrueに下ではないならfalseに。そのままですね笑

利用規約への同意後も、上にスクロールした場合にチェックが外れるよう毎回serviceCheckをfalseにしていました。(やりすぎか。。。)

readがtureになるとcheckBoxがactiveになります。

<div class="form-item">
 <div class="check">
  <span>利用規約に同意する</span>
  <CheckBox
   :active="read"
   :state="form.serviceCheck"
   @click="check"/>
 </div>
 <p class="error-message fc-red service" v-if="scrollError">
  利用規約を最後までお読みください。
 </p>
</div>

チェックボックスのコンポーネントは以下です。

<template>
  <span class="checkbox" @click.stop="click" :class="{active:active}">
    <img src="@/assets/images/check.svg" v-show="state" alt="チェックマーク">
  </span>
</template>
<script>
export default {
  props: {
      active: {
          type: Boolean,
          default: true
      },
      state: { 
          type: Boolean,
          default: false,
      }
  },
  methods: {
      click() {
          this.$emit('click')
      }
  }
}
</script>

実はただのチェックボックスではなく、オリジナルに作成したチェックボックスでした笑

activeがtrueになると、クラス名が代わり活性化されます。 CSSについては、今回は省略します。

$emit('click')をコンポーネントに直書きではなくmethodsに用意しているのは、他に処理を挟むかもしれないということを考えてだと思いますが、特に意味はありません。クリックされると、親コンポーネントにその情報が渡ります。

以下の部分です。

<div class="form-item">
 <div class="check">
  <span>利用規約に同意する</span>
  <CheckBox
   :active="read"
   :state="form.serviceCheck"
   @click="check"/>
 </div>
 <p class="error-message fc-red service" v-if="scrollError">
   利用規約を最後までお読みください。
  </p>
</div>

親コンポーネントのserviceCheckとCheckBoxのstateを連動させることにより、親コンポーネントと子コンポーネントで同じ情報を共有することができます。

clickイベントでcheckメソッドが発動します。以下です。

check() {
 if (!this.read) {
  this.scrollError = true
  return
 }
 this.form.serviceCheck = !this.form.serviceCheck
},

単純ですね!readがfalse。つまりスクロールが一番下ではない状態でクリックした場合は、scrollErrorをtrueにしてエラーを表示させます。 そうではない場合はチェックボックスの状態をトグルさせています。

あとはform.serviceCheckの値を見て、ボタンを活性化させたり次の処理につなげることができるとは思います。

というわけで、ざっと書いてみましたが。どうでしたか? 他にもやり方があると思いますが、僕はこの方法で処理していました!良い方法があれば、是非教えてくださいね!

それでは。今日も楽しくカタカタしましょう!