見出し画像

Vue.jsと連携しやすいCanvasライブラリのKonvaで並べ替え問題アプリを作ることにした話。

最近は寒すぎて朝起きれません。


1.とりあえずインストール

とりあえず、このページを見ながらインストールしました。

まずはインストールして

npm install vue-konva konva --save

main.tsにコードを追加する。

import './assets/main.css'

import { createApp } from 'vue'
import { createPinia } from 'pinia'

import App from './App.vue'
import router from './router'
import VueKonva from 'vue-konva'; //これを追加

const app = createApp(App)

app.use(createPinia())
app.use(router)
app.use(VueKonva); //これを追加

app.mount('#app')

2.並べ替える文字ブロックを作るまで

src/router/index.tsにコードを追加

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'


import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'


const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('../views/AboutView.vue')
    },
    //ここから追加
    {
      path: '/test',
      name: 'test',
      component: () => import('../views/TestViewV2.vue')
    }
    //ここまで追加
  ]
})

export default router

src/views/TestViewV2.vueを作成する。

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
    data() {
        return {
            configKonva: {
                width: 700,
                height: 200
            },
            configTextBox: [] as any, //[{text:{},rect:{},group:{},data:{}}]
            box_text: [
                "<div>",
                "Hello",
                "World",
                "</div>"
            ],
            padding: 6,
            margin: 6
        };
    },
    created() {
        this.box_text.forEach((item:string,index:number) => {
            this.configTextBox.push({
                text:{
                    text: item,
                    fontSize: 30,
                    fontFamily: 'Calibri', 
                },
                rect: {
                    width: 200,
                    height: 80,
                    fill: "#f0f8ff",
                },
                group: {
                    draggable: true
                }
            })
        })
    },
    mounted() {
        this.configKonva.width = document.getElementById('stage')!.clientWidth
        window.addEventListener("resize",() => {
            this.configKonva.width = document.getElementById('stage')!.clientWidth
        })
        this.box_text.forEach((item,index) => {
            const textset:any = this.$refs.text
            const size:any = textset[index].getNode().size() //プロックの大きさ
            //自身の要素より左にある要素の幅を計算する
            //mountedのタイミングですることで文字列全体の幅を計算することができる。
            let left_box_width:number = 0
            this.box_text.slice(0,index).forEach((item_a,index_a) => {
                left_box_width += textset[index_a].getNode().size().width + this.padding + this.margin * 2
                console.log(`width${index_a}: ${textset[index_a].getNode().size().width}`);
            })
            this.configTextBox[index]["text"] = {
                text: item,
                fontSize: 30,
                fontFamily: 'Calibri',
                x:this.padding / 2,
                y:this.padding / 2
            }
            this.configTextBox[index]["rect"] = {
                fill: "#f0f8ff",
                width: size.width + this.padding,
                height: size.height + this.padding,
            }
            this.configTextBox[index]["group"] = {
                x: left_box_width,
                draggable: true
            }
        })
    },
    methods:{
        start_move(event:any){
            console.log(event.target.index);
            const group:any = this.$refs.group
            console.log(`zindex:${group[event.target.index].getNode().zIndex()}`)
            group[event.target.index].getNode().zIndex(this.configTextBox.length - 1)
        },
        end_move(event:any){
            const group:any = this.$refs.group
            group[event.target.index].getNode().zIndex(event.target.index)
            console.log(`zindex:${group[event.target.index].getNode().zIndex()}`)
        }
    }
})
</script>

<template>
    <div id="stage" class="w-full">
        <v-stage :config="configKonva">
            <v-layer>
                <v-group v-for="(item, index) in configTextBox" :key="index" :config="item['group']" ref="group" @dragstart="start_move" @dragend="end_move">
                    <v-rect :config='item["rect"]' ref="rect">
                    </v-rect>
                    <v-text :config='item["text"]' ref="text">
                    </v-text>
                </v-group>
            </v-layer>
        </v-stage>
    </div>
</template>

3.this.$refsの仕様を読み解くのに苦労しました。

 各要素の幅を取得するため、タグにrefプロパティをつけて、script側でthis.$refsを使って参照する必要があった。

 しかし、this.$refsの説明がDocsには存在しなかったので、推測とconsole.logに変数を入れて推理をするしかなかった。

 それでも、最終的には例えば、this.$refs.groupというコードを入力して要素を取得しようとして、refの値がgroupであるタグの要素が複数存在する場合、コードの一番上に存在する、refの値がgroupであるタグの要素から順番に挿入されている配列が取得できることが分かったので、完成するに至った。

こんな感じ

4.ただ、ドラッグ&ドロップするときに違和感が…

 一応、文字のブロックを並べ替えることは出来たが、ブロックをドラッグするときにドラッグされていないブロックがドラッグされているブロックより前に描画される時があるので、それを今後修正しようと思っている。


この記事が気に入ったらサポートをしてみませんか?