LoginSignup
1
2

More than 3 years have passed since last update.

【Nuxt.js】props実践編:switchボタンを作ろう!

Last updated at Posted at 2020-02-21

switchlast.gif

前置き

今回はSwitchボタンです!✅
gifカクカクですが
実際はなめらかになります💦
(予告と違う内容ですみません)
inputの真偽値によって
クラスバインディングを行います!

propsを主に使います。
前回書いた記事と違うのは
Stringを渡すだけではなく
computed関数で切り替えを行う部分です🤗

propsでのクラスバインディングはこちら
https://note.com/aliz/n/nc4110b1128f0

構成

まずはtemplateの構成
inputをlabelで囲うとコードが
スッキリするのでこちらを採用🌱

書き方①
<label><input type="text"></label>

囲わない場合は

書き方②
<label for="name"></label>
<input type="text" id="name">

Step1: まずはlabel, inputを作成

switch0.gif

◾️label
・文字列は親で指定したいのでprops
 文字列だけなのでslotでも大丈夫です!
 今回はpropsを複数使うためpropsにします。

css
labelの幅を88pxにしたい!
が、labelに指定してしまうと
inputも含んだ長さのためspanを追加します。

◾️input
・input type="checkbox"に
 指定可能な属性checkedを使用
 後でクラスバインディングを紐づけるため、
 まずはcheckedされた状態から作ります✅

・inputイベント(@input)で$emit
 $emit基礎編はこちら
 https://note.com/aliz/n/nd6e771724cd7

・チェックが付けられるとイベント実行
 基本イベントハンドラは
 まとめてあるここが参考になります!
 https://blog.capilano-fw.com/?p=2787

css
まず全体をdivで囲い
そのdivにborderをつけ
●を作るdivを追加します🍩
●はcheckされた時の右側の配置を書きたいので
左側の分もまとめて書いちゃいましょう!✍️

👩‍💻ここまでのコード
scssは他のコンポーネントに影響が出ないよう、
基本的に直下セレクタ > をつけています。

SwitchButton.vue

<template>
 <label class="input switch-button">
   <span class="label">
     {{ label }}
   </span>
   <div class="box">
     <input
       :checked="checked"
       type="checkbox"
       class="input"
       @input="$emit('switch')"
     >
     <div class="mark" />
   </div>
 </label>
</template>

<script>
export default {
 props: {
   label: {
     type: String,
     default: 'label'
   },
   checked: {
     type: Boolean,
     required: true,
   },
 },
}
</script>

<style lang="scss" scoped>
 .switch-button {
   display: flex;
   align-items: center;

   > .label {
     width: 88px;
     min-width: 88px;
     color: red;
   }

   > .box {
     position: relative;
     width: 48px;
     height: 24px;
     border: 2px solid red;
     border-radius: 26px;

     > .mark {
       position: absolute;
       top: 2px;
       left: 2px;
       width: 16px;
       height: 16px;
       background-color: red;
       border-radius: 50%;
       transition: 0.12s;
     }

     > .input {
       display: none;

       &:checked ~ .mark {
         transform: translateX(24px);
         background-color: red;
       }
     }
   }
 }
</style>

親でpropsのlabelは
直接テキストを渡しています。
:label="変数名"でももちろんOK。
$emitに関しては現時点ではスルーでOK。

index.vue
<template>
 <div class="page">
   <SwitchButton
     label="LABEL"
     :checked="checked"
   />
 </div>
</template>

<script>
import SwitchButton from '~/components/SwitchButton.vue'

export default {
 components: {
   SwitchButton,
 },
 data () {
   return {
     checked: true,
   }
 }
}
</script>

Step2: クラスバインディングを追加

propsとcomputed関数を使った
クラスバインディングをしていきます。
まずはinputによる真偽値は無視しましょう。

やりたいこと
チェックされたかどうかに関係なく
クラスの付け替えで全体の色を変える🎈🧸
なのでこうなればOKです!
switch2.gif

あとでチェックされていない状態
(●が左にある状態)で
この色に変わるように変更します。

Point!
・labelにクラスバインディング
・computed関数で
 propsをswitch関数で切り替え
・親でstatusがinactiveになれば
 inactiveクラスがついてピンクになる
・inactiveクラスがついた時のcssを追加

👩‍💻ここまでのコード

SwitchButton.vue
<template>
 <label
   :class="classes"
   class="input switch-button"
 >
   <span class="label">
     {{ label }}
   </span>
   <div class="box">
     <input
       :checked="checked"
       type="checkbox"
       class="input"
       @input="$emit('switch')"
     >
     <div class="mark" />
   </div>
 </label>
</template>

<script>
export default {
props: {
  status: {
    type: String,
    default: '',
    required: false,
    validator (value) {
      return [
        '',
        'inactive',
      ].includes(value)
    },
  },
  label: {
    type: String,
    default: 'label'
  },
  checked: {
    type: Boolean,
    required: true,
  },
},
computed: {
  classes() {
    switch (this.status) {
      case '':
       return ''
     case 'inactive':
       return 'inactive'
     default:
       return ''
    }
  },
},
}
</script>

<style lang="scss" scoped>
.switch-button {
 display: flex;
 align-items: center;

 > .label {
   width: 69px;
   min-width: 69px;
   color: red;
 }

 > .box {
   position: relative;
   width: 48px;
   height: 24px;
   border: 2px solid red;
   border-radius: 26px;

   > .mark {
     position: absolute;
     top: 2px;
     left: 2px;
     width: 16px;
     height: 16px;
     background-color: red;
     border-radius: 50%;
     transition: 0.12s;
   }

   > .input {
     display: none;

     &:checked ~ .mark {
       transform: translateX(24px);
       background-color: red;
     }
   }
 }

 &.inactive {
   > .label {
     color: pink;
   }

   > .box {
     border: 2px solid pink;

     > .mark {
       background-color: pink;
     }

     > .input:checked ~ .mark {
       background-color: pink;
     }
   }
 }
}
</style>
index.vue
<template>
 <div class="page">
   <SwitchButton
     :checked="checked"
     label="LABEL"
     status="inactive"
   />
 </div>
</template>

<script>
import SwitchButton from '~/components/SwitchButton.vue'

export default {
 components: {
   SwitchButton,
 },
 data () {
  return {
   checked: true,
  }
 },
}
</script>

これで準備はOK✨💪

switchlast.gif

Step3: 真偽値とクラスバインディングの連携

いよいよinputによるクラス付け替えです!
子ではもう全てのpropsを
書いているのでやることはありません。

親で渡したpropsの
・Boolean
・String
これを変数で連携させましょう!

Point!
・:checked="value"で
 valueの初期値をfalseに
 最初は選択されていない状態に変更
・statusに三項演算を使うので:を忘れず追加
 valueがtrueなら'', falseなら'inactive'
@switch="value = !value"で
 switchイベント発火時にtrue, falseの切り替え

つまり初期値チェックしてない状態の時は
inactiveクラスがついて
ピンクになって●が左にある状態🍩
チェックをつけるとinactiveが外れて
通常クラスの赤になって●が右にある状態🍩

👩‍💻最終コード

index.vue
<template>
 <div class="page">
   <SwitchButton
     :checked="value"
     label="LABEL"
     :status="value ? '' : 'inactive'"
     @switch="value = !value"
   />
 </div>
</template>

<script>
import SwitchButton from '~/components/SwitchButton.vue'

export default {
 components: {
   SwitchButton,
 },
 data () {
   return {
     value: false,
   }
 }
}
</script>
SwitchButton.vue
<template>
 <label
   :class="classes"
   class="input switch-button"
 >
   <span class="label">
     {{ label }}
   </span>
   <div class="box">
     <input
       :checked="checked"
       type="checkbox"
       class="input"
       @input="$emit('switch')"
     >
     <div class="mark" />
   </div>
 </label>
</template>

<script>
export default {
props: {
  status: {
    type: String,
    default: '',
    required: false,
    validator (value) {
      return [
        '',
        'inactive',
      ].includes(value)
    },
  },
  label: {
    type: String,
    default: 'label'
  },
  checked: {
    type: Boolean,
    required: true,
  },
},
computed: {
  classes() {
    switch (this.status) {
      case '':
       return ''
     case 'inactive':
       return 'inactive'
     default:
       return ''
    }
  },
},
}
</script>

<style lang="scss" scoped>
.switch-button {
 display: flex;
 align-items: center;

 > .label {
   width: 69px;
   min-width: 69px;
   color: red;
 }

 > .box {
   position: relative;
   width: 48px;
   height: 24px;
   border: 2px solid red;
   border-radius: 26px;

   > .mark {
     position: absolute;
     top: 2px;
     left: 2px;
     width: 16px;
     height: 16px;
     background-color: red;
     border-radius: 50%;
     transition: 0.12s;
   }

   > .input {
     display: none;

     &:checked ~ .mark {
       transform: translateX(24px);
       background-color: red;
     }
   }
 }

 &.inactive {
   > .label {
     color: pink;
   }

   > .box {
     border: 2px solid pink;

     > .mark {
       background-color: pink;
     }

     > .input:checked ~ .mark {
       background-color: pink;
     }
   }
 }
}
</style>

次回はVuexの続きをやります!
公開予定日は2/28(金)です。

記事が公開したときにわかる様に、
note・Twitterフォローをお願いします😀

https://twitter.com/aLizlab

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2