Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
21
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

@zaru

Vue.js で contenteditable を v-model 風に使う

Vue.js でとても便利な v-model ですが contenteditable な要素では使えません。 v-model は内部的には v-bind:valuev-on:input をまとめてやってくれているからです。つまり input 要素じゃない contenteditable では効かないという感じです。

そこで自前でイベントを使って雑に書くとこんな風に書いてしまいますが、これには問題があります。入力するたびにキャレットが文字列の先頭に飛んでしまいます。これは v-text が更新されるため自身も render が走ってしまうのが原因です。かといって v-once を付けてしまうと、今度は自身を変更するすべがなくなってしまいます。

<template>
  <div>
    <div contenteditable="true" v-text="content" @input="sync"></div>
    <div>{{ content }}</div>
  </div>
</template>

<script>
import Vue from 'vue'

export default Vue.extend({
  name: 'HelloWorld',
  data () {
    return {
      content: 'sample text'
    }
  },
  methods: {
    sync (e) {
      this.content = e.target.innerText
    }
  }
})
</script>

回避方法は至って単純で、もう一つ contenteditable 用の変数を用意すること。

<template>
  <div class="hello">
    <div contenteditable="true" v-text="innerContent" @input="sync"></div>
    <div v-html="content"></div>
  </div>
</template>

<script>
import Vue from 'vue'

export default Vue.extend({
  name: 'HelloWorld',
  data () {
    return {
      innerContent: '',
      content: 'sample text'
    }
  },
  methods: {
    sync (e) {
      this.content = e.target.innerHTML
    }
  },
  mounted() {
    this.innerContent = this.content
  }
})
</script>

ついでにコンポーネントにしておきます。

<template>
    <div contenteditable="true" v-text="innerContent" @input="sync"></div>
</template>

<script>
import Vue from 'vue'

export default Vue.extend({
  name: 'Editor',
  props: ['content'],
  data () {
    return {
      innerContent: ''
    }
  },
  methods: {
    sync (e) {
      this.$emit('update', e.target.innerHTML)
    }
  },
  mounted() {
    this.innerContent = this.content
  }
})
</script>
<template>
  <div class="hello">
    <editor :content="content" @update="sync"></editor>
    <div v-html="content"></div>
  </div>
</template>

<script>
import Vue from 'vue'
import Editor from './Editor'

export default Vue.extend({
  name: 'HelloWorld',
  components: {
    Editor
  },
  data () {
    return {
      content: 'sample text'
    }
  },
  methods: {
    sync (content) {
      this.content = content
    }
  }
})
</script>

雑なサンプルですが、こんな感じで contenteditablev-model っぽいデータの同期ができます。

21
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
21
Help us understand the problem. What is going on with this article?