2018年03月22日追記
アコーディオンひとつ分をコンポーネント化して、コンポーネント内だけで状態管理する方が良いので書き直しました。
vue.js 2.x その0004-02 アコーディオンを作る(jQueryのslideToggleみたいなものを作る) - Motomichi Works Blog
サンプルコード(v-forで出力した要素にそれぞれ)
v-forで出力した要素それぞれにアコーディオンを入れるイメージで、以下のように書いてみました。
<style> .accordion{} .accordion__trigger{} .accordion__contents-outer-wrap{ transition: max-height 2s; max-height: 0; overflow: hidden; background: #eeeeff; } .accordion__contents-inner-wrap{ padding: 100px 10px; } </style> <div id="accordion-app"> <ul> <li class="accordion" v-for="(item, index) in items"> <button class="accordion__trigger" v-on:click="slideToggle(index)"> accordion-trigger-{{index}} </button> <div class="accordion__contents-outer-wrap" v-bind:style="{ maxHeight: items[index].contents.maxHeight + 'px' }"> <div class="accordion__contents-inner-wrap" ref="contentsInner"> {{item.title}}<br> {{item.contents.lead}} </div> </div> </li> </ul> </div> <script src="js/vue.js"></script> <script> var items = [ { title: 'content-0', contents: { lead: 'lead-0', isActive: false, maxHeight: 0, } }, { title: 'content-0', contents: { lead: 'lead-0', isActive: false, maxHeight: 0, } }, ]; var vueApp = new Vue({ el: '#accordion-app', data: { items: items, }, methods: { slideToggle: function(index){ this.items[index].contents.isActive = !this.items[index].contents.isActive; if(this.items[index].contents.isActive){ this.items[index].contents.maxHeight = this.$refs.contentsInner[index].clientHeight; }else{ this.items[index].contents.maxHeight = 0; } }, }, }); </script>
サンプルソース(table-rowを開閉)
trタグの開閉も書いてみましたがHTMLタグが多くなりますね。 display: table-rowでも同じですね。
<style> .container{ width: 600px; } table{ width: 100%; border-collapse: collapse; border-spacing: 0; } .contents-wrap{ padding: 10px; background: #eeeeff; } tr + tr .contents-wrap{ border-top: 1px solid #aaaaaa; } th,td{ border: 0; padding: 0; } .accordion{ transition: max-height 2s; max-height: 0; overflow: hidden; } </style> <div id="vue-app"> <div class="container"> <table> <tr> <th> <div> <div class="contents-wrap"> ラジオボタンrow </div> </div> </th> <td> <div> <div class="contents-wrap"> <span class="controll"> <input type="radio" name="hoge-radio" value="true" v-model="hoge"> <label for="">開く</label> </span> <span class="controll"> <input type="radio" name="hoge-radio" value="false" v-model="hoge"> <label for="">閉じる</label> </span> </div> </div> </td> </tr> <tr> <th> <div class="accordion" :style="{'max-height': state.hoge.maxHeight + 'px'}"> <div class="contents-wrap" ref="hoge"> トグルrow </div> </div> </th> <td> <div class="accordion" :style="{'max-height': state.hoge.maxHeight + 'px'}"> <div class="contents-wrap"> トグルコンテンツ </div> </div> </td> </tr> <tr> <th> <div> <div class="contents-wrap"> ラジオボタンrow </div> </div> </th> <td> <div> <div class="contents-wrap"> <span class="controll"> <input type="radio" name="foo-radio" value="true" v-model="foo"> <label for="">開く</label> </span> <span class="controll"> <input type="radio" name="foo-radio" value="false" v-model="foo"> <label for="">閉じる</label> </span> </div> </div> </td> </tr> <tr> <th> <div class="accordion" :style="{'max-height': state.foo.maxHeight + 'px'}"> <div class="contents-wrap" ref="foo"> トグルrow </div> </div> </th> <td> <div class="accordion" :style="{'max-height': state.foo.maxHeight + 'px'}"> <div class="contents-wrap"> トグルコンテンツ </div> </div> </td> </tr> </table> </div> </div> <script src="js/vue.js"></script> <script> var vueApp = new Vue({ el: '#vue-app', data: { hoge: 'false', foo: 'false', piyo: 'false', state: { hoge: { isVisible: false, maxHeight: 0, }, foo: { isVisible: false, maxHeight: 0, }, } }, watch: { hoge: function(val){ this.slideToggle('hoge', val); }, foo: function(val){ this.slideToggle('foo', val); }, }, methods: { slideToggle: function(key, val){ this.state[key].isVisible = !this.state[key].isVisible; if(this.state[key].isVisible){ this.state[key].maxHeight = this.$refs[key].clientHeight; }else{ this.state[key].maxHeight = 0; } console.log(this.state[key]); }, }, }); </script>