こんにちはWebCrewバッ苦エンドエンジニア1年目の久保田です。
今日は社内の座席方をいろいろあって作ることになり、その際に使ったVue Grid Layoutが面白かったので記事に残してみます。
Vue Grid Layoutとは
まずVue Grid Layoutとはなんぞやって話ですが、とりあえずgridで組んだオブジェクトを自由にドラッグ&ドロップで動かしたらサイズを変えたりできるVueのライブラリです。便利ですね。
物は試しなので、とりあえずリンク先でいろいろ動かしてみてください。
サイズを変えたりできるのはもちろんですが、staticにして一部だけ不可変に設定することもできます。
実装方法
使い方も簡単で、VueのCLI立ち上げて下のコードpackage.jsonに記載するだけです。
"vue-grid-layout": "^2.4.0"
これで各コンポーネントで使えるようになります。
今回は簡単な座席表っぽいものを作ってみます。
コンポーネントはなんでもいいですが、お試しなのでHelloWordl.vueにそのまま書いていきます。
基本的な書き方は以下の通りです。
<template>
<div class="hello">
<grid-layout :layout.sync="layout"
:col-num="24"
:row-height="48"
:is-draggable="draggable"
:is-resizable="resizable"
:vertical-compact="true"
:use-css-transforms="true"
>
<grid-item v-for="item in layout"
:key="item"
:static="item.static"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
>
<span class="text">{{itemTitle(item)}}</span>
</grid-item>
</grid-layout>
</div>
</template>
<script>
import { GridLayout, GridItem } from "vue-grid-layout"
export default {
components: {
GridLayout,
GridItem
},
data() {
return {
layout: [
{"x":1,"y":1,"w":1,"h":1,"i":"0", static: false}
,
draggable: true,
resizable: true,
index: 0
}
},
methods: {
itemTitle(item) {
let result = item.i;
if (item.static) {
result += " - Static";
}
return result;
}
}
}
</script>
<style scoped>
html {
margin: 0;
padding: 0;
}
.hello {
height: 100vh;
}
.vue-grid-layout {
background: #eee;
height: 100;
}
.vue-grid-item:not(.vue-grid-placeholder) {
background: #ccc;
border: 1px solid black;
}
.vue-grid-item .resizing {
opacity: 0.9;
}
.vue-grid-item .static {
background: #cce;
}
.vue-grid-item .text {
font-size: 24px;
text-align: center;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
height: 100%;
width: 100%;
}
.vue-grid-item .no-drag {
height: 100%;
width: 100%;
}
.vue-grid-item .minMax {
font-size: 12px;
}
.vue-grid-item .add {
cursor: pointer;
}
.vue-draggable-handle {
position: absolute;
width: 20px;
height: 20px;
top: 0;
left: 0;
background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>") no-repeat;
background-position: bottom right;
padding: 0 8px 8px 0;
background-repeat: no-repeat;
background-origin: content-box;
box-sizing: border-box;
cursor: pointer;
}
</style>
これだけでとりあえずオブジェクトを一つ作る事ができます。
dataの中身を増やしていくことでオブジェクトの数を増やす事が可能です。
便利な点としては、ハッシュでx,yの位置やw.hといったサイズを書くだけで簡単に実装できる点です。また、staticやdeaggableも個別に付け加えることで実装できるのでv-for内で完結します。
便利ですね。
主なプロパティについても記載しておきます。
プロパティ | 値 | 効果 |
---|---|---|
x | int | x軸の位置 |
y | int | y軸の位置 |
w | int | 横幅 |
h | int | 縦幅 |
static | boolean | 他のオブジェクトの変更も含めて動かせるかどうか |
draggable | boolean | ドラッグできるか否か |
resizable | boolean | サイズ変更できるか否か |
だいたいこんな感じです、めちゃくちゃ簡単です。
最後に簡単な座席表作って終わります。
<template>
<div class="hello">
<grid-layout :layout.sync="layout"
:col-num="24"
:row-height="48"
:is-draggable="draggable"
:is-resizable="resizable"
:vertical-compact="true"
:use-css-transforms="true"
>
<grid-item v-for="item in layout"
:key="item"
:static="item.static"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
>
<span class="text">{{itemTitle(item)}}</span>
</grid-item>
</grid-layout>
</div>
</template>
<script>
import { GridLayout, GridItem } from "vue-grid-layout"
export default {
components: {
GridLayout,
GridItem
},
data() {
return {
layout: [
{"x":1,"y":1,"w":1,"h":1,"i":"0", static: false},
{"x":8,"y":1,"w":1,"h":1,"i":"1", static: false},
{"x":12,"y":1,"w":1,"h":1,"i":"2", static: false},
{"x":16,"y":1,"w":1,"h":1,"i":"3", static: false},
{"x":19,"y":1,"w":1,"h":1,"i":"4", static: false},
{"x":20,"y":1,"w":1,"h":1,"i":"5", static: false},
{"x":19,"y":1,"w":1,"h":1,"i":"6", static: false},
{"x":20,"y":1,"w":1,"h":1,"i":"7", static: false},
{"x":1,"y":2,"w":23,"h":1,"i":"99", static: true},
{"x":1.5,"y":4,"w":1,"h":1,"i":"8", static: false},
{"x":1,"y":5,"w":1,"h":1,"i":"9", static: false},
{"x":1,"y":6,"w":1,"h":1,"i":"10", static: false},
{"x":1,"y":7,"w":1,"h":1,"i":"11", static: false},
{"x":1,"y":7,"w":1,"h":1,"i":"12", static: false},
{"x":2,"y":5,"w":1,"h":1,"i":"13", static: false},
{"x":2,"y":6,"w":1,"h":1,"i":"14", static: false},
{"x":2,"y":7,"w":1,"h":1,"i":"15", static: false},
{"x":2,"y":7,"w":1,"h":1,"i":"16", static: false},
{"x":4,"y":4,"w":1,"h":1,"i":"17", static: false},
{"x":4,"y":4,"w":1,"h":1,"i":"18", static: false},
{"x":4,"y":5,"w":1,"h":1,"i":"19", static: false},
{"x":4,"y":6,"w":1,"h":1,"i":"20", static: false},
{"x":4,"y":7,"w":1,"h":1,"i":"21", static: false},
{"x":5,"y":7,"w":1,"h":1,"i":"22", static: false},
{"x":5,"y":5,"w":1,"h":1,"i":"23", static: false},
{"x":5,"y":6,"w":1,"h":1,"i":"24", static: false},
{"x":5,"y":7,"w":1,"h":1,"i":"25", static: false},
{"x":5,"y":7,"w":1,"h":1,"i":"26", static: false},
{"x":7,"y":3,"w":1,"h":1,"i":"27", static: false},
{"x":7,"y":4,"w":1,"h":1,"i":"28", static: false},
{"x":7,"y":5,"w":1,"h":1,"i":"29", static: false},
{"x":7,"y":6,"w":1,"h":1,"i":"30", static: false},
{"x":7,"y":7,"w":1,"h":1,"i":"31", static: false},
{"x":8,"y":3,"w":1,"h":1,"i":"32", static: false},
{"x":8,"y":4,"w":1,"h":1,"i":"33", static: false},
{"x":8,"y":5,"w":1,"h":1,"i":"34", static: false},
{"x":8,"y":6,"w":1,"h":1,"i":"35", static: false},
{"x":8,"y":7,"w":1,"h":1,"i":"36", static: false},
{"x":10,"y":3,"w":1,"h":1,"i":"37", static: false},
{"x":10,"y":4,"w":1,"h":1,"i":"38", static: false},
{"x":10,"y":5,"w":1,"h":1,"i":"39", static: false},
{"x":10,"y":6,"w":1,"h":1,"i":"40", static: false},
{"x":10,"y":7,"w":1,"h":1,"i":"41", static: false},
{"x":11,"y":3,"w":1,"h":1,"i":"42", static: false},
{"x":11,"y":4,"w":1,"h":1,"i":"43", static: false},
{"x":11,"y":5,"w":1,"h":1,"i":"44", static: false},
{"x":11,"y":6,"w":1,"h":1,"i":"45", static: false},
{"x":11,"y":7,"w":1,"h":1,"i":"46", static: false},
{"x":13,"y":3,"w":1,"h":1,"i":"47", static: false},
{"x":13,"y":4,"w":1,"h":1,"i":"48", static: false},
{"x":13,"y":5,"w":1,"h":1,"i":"49", static: false},
{"x":13,"y":6,"w":1,"h":1,"i":"50", static: false},
{"x":13,"y":7,"w":1,"h":1,"i":"51", static: false},
{"x":14,"y":3,"w":1,"h":1,"i":"52", static: false},
{"x":14,"y":4,"w":1,"h":1,"i":"53", static: false},
{"x":14,"y":5,"w":1,"h":1,"i":"54", static: false},
{"x":14,"y":6,"w":1,"h":1,"i":"55", static: false},
{"x":14,"y":7,"w":1,"h":1,"i":"56", static: false},
{"x":16,"y":3,"w":1,"h":1,"i":"57", static: false},
{"x":16,"y":4,"w":1,"h":1,"i":"58", static: false},
{"x":16,"y":5,"w":1,"h":1,"i":"59", static: false},
{"x":16,"y":6,"w":1,"h":1,"i":"60", static: false},
{"x":16,"y":7,"w":1,"h":1,"i":"61", static: false},
{"x":17,"y":3,"w":1,"h":1,"i":"62", static: false},
{"x":17,"y":4,"w":1,"h":1,"i":"63", static: false},
{"x":17,"y":5,"w":1,"h":1,"i":"64", static: false},
{"x":17,"y":6,"w":1,"h":1,"i":"65", static: false},
{"x":17,"y":7,"w":1,"h":1,"i":"66", static: false},
{"x":19,"y":3,"w":1,"h":1,"i":"67", static: false},
{"x":19,"y":4,"w":1,"h":1,"i":"68", static: false},
{"x":19,"y":5,"w":1,"h":1,"i":"69", static: false},
{"x":19,"y":6,"w":1,"h":1,"i":"70", static: false},
{"x":19,"y":7,"w":1,"h":1,"i":"71", static: false},
{"x":20,"y":3,"w":1,"h":1,"i":"72", static: false},
{"x":20,"y":4,"w":1,"h":1,"i":"73", static: false},
{"x":20,"y":5,"w":1,"h":1,"i":"74", static: false},
{"x":20,"y":6,"w":1,"h":1,"i":"75", static: false},
{"x":20,"y":7,"w":1,"h":1,"i":"76", static: false},
{"x":22,"y":3,"w":1,"h":1,"i":"77", static: false},
{"x":22,"y":4,"w":1,"h":1,"i":"78", static: false},
{"x":22,"y":5,"w":1,"h":1,"i":"79", static: false},
{"x":22,"y":6,"w":1,"h":1,"i":"80", static: false},
{"x":22,"y":7,"w":1,"h":1,"i":"81", static: false},
{"x":23,"y":3,"w":1,"h":1,"i":"82", static: false},
{"x":23,"y":4,"w":1,"h":1,"i":"83", static: false},
{"x":23,"y":5,"w":1,"h":1,"i":"84", static: false},
{"x":23,"y":6,"w":1,"h":1,"i":"85", static: false},
{"x":23,"y":7,"w":1,"h":1,"i":"86", static: false},
{"x":1,"y":8,"w":23,"h":1,"i":"100",
],
draggable: true,
resizable: true,
index: 0
}
},
methods: {
itemTitle(item) {
let result = item.i;
if (item.static) {
result += " - Static";
}
return result;
}
}
}
</script>
<style scoped>
html {
margin: 0;
padding: 0;
}
.hello {
height: 100vh;
}
.vue-grid-layout {
background: #eee;
height: 100;
}
.vue-grid-item:not(.vue-grid-placeholder) {
background: #ccc;
border: 1px solid black;
}
.vue-grid-item .resizing {
opacity: 0.9;
}
.vue-grid-item .static {
background: #cce;
}
.vue-grid-item .text {
font-size: 24px;
text-align: center;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
height: 100%;
width: 100%;
}
.vue-grid-item .no-drag {
height: 100%;
width: 100%;
}
.vue-grid-item .minMax {
font-size: 12px;
}
.vue-grid-item .add {
cursor: pointer;
}
.vue-draggable-handle {
position: absolute;
width: 20px;
height: 20px;
top: 0;
left: 0;
background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>") no-repeat;
background-position: bottom right;
padding: 0 8px 8px 0;
background-repeat: no-repeat;
background-origin: content-box;
box-sizing: border-box;
cursor: pointer;
}
</style>
以上です、Vueには他にもドラッグ&ドロップできるライブラリいくつかあるみたいなので試してみたいと思います。
ありがとうございました。