前置き
今回はSwitchボタンです!✅
gifカクカクですが
実際はなめらかになります💦
inputの真偽値によって
クラスバインディングを行います!
propsを主に使います。
前回書いた記事と違うのは
Stringを渡すだけではなく
computed関数で切り替えを行う部分です🤗
propsでのクラスバインディングはこちら
構成
templateの構成
inputをlabelで囲うとコードが
スッキリするのでこちらを採用🌱
1 |
<label><input type="text"></label> |
囲わない場合は
1 2 |
<label for="name"></label> <input type="text" id="name"> |
Step1: まずはlabel, inputを作成
解説
label
props
文字列は親で指定したいのでprops
文字列だけなのでslotでも大丈夫です!
今回はpropsを複数使うためpropsにします。
css
labelの幅を88pxにしたい!
が、labelに指定してしまうと
inputも含んだ長さのためspanを追加します。
input
input type="checkbox"
inputタグに指定可能な属性checkedを使用
後でクラスバインディングを紐づけるため、
まずはcheckedされた状態から作ります✅
@input="$emit('switch')"
inputタグにcheckした時(@input)に、
カスタムイベント$emitを渡します。
イベントハンドラ 一覧
$emit基礎編はこちら
コード
) components
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
<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> |
直下セレクタ >
scssは他のコンポーネントに影響が出ないよう、
基本的に直下セレクタ > をつけています。
) pages
propsのlabelは
直接テキストを渡しています。
:label="変数名"でももちろんOK。
$emitに関しては現時点ではスルーでOK。propsのlabelは
直接テキストを渡しています。
:label="変数名"でももちろんOK。
$emitに関しては現時点ではスルーでOK。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<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です!
あとでチェックされていない状態
(●が左にある状態)で
この色に変わるように変更します。
解説
Point!
- labelにクラスバインディング
- computed関数でpropsをswitch関数で切り替え
- 親でstatusがinactiveになればinactiveクラスがついてピンクになる
- inactiveクラスがついた時のcssを追加
コード
) components
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
<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> |
) pages
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<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✨💪
Step3: 真偽値とクラスバインディングの連携
いよいよinputによるクラス付け替えです!
子ではもう全てのpropsを
書いているのでやることはありません。
親で渡したpropsの
・Boolean
・String
これを変数で連携させましょう!
解説
Point!
- :checked="value"でvalueの初期値をfalseに
最初は選択されていない状態に変更 - statusに三項演算を使うので:を忘れず追加
valueがtrueなら'', falseなら'inactive' - @switch="value = !value"で
switchイベント発火時にtrue, falseの切り替え
つまり初期値チェックしてない状態の時は
inactiveクラスがついて
ピンクになって●が左にある状態🍩
チェックをつけるとinactiveが外れて
通常クラスの赤になって●が右にある状態🍩
コード
) pages
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<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> |
) components
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
<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> |