はじめに
あるプロジェクトで、inputタグを使って実装していた箇所に対して「改行できるようにして欲しい」と言われました。「そうだなぁ、複数行であれば、textareaタグを使うしかないな。」となるのですが、ここで問題が。改行できるようにするのは、タグを変えるだけだけど、可変で制限かけるって難しくね?
結構苦戦したので、その備忘録
やり方
やり方は2段階になります。
まず、 「テキストエリアの高さを可変&制限をかける」そして「改行を制限する」の二つです。では、一つずつ見てみましょう。
1.テキストエリアの高さを可変&制限をかける
デフォルトのテキストエリアであれば、 高さは可変ではありません。
rows
を指定したり、スタイルから高さを指定することで固定になります。
このようにEnterを押すことで改行していきますがスクロールが出現します。
まずはこの高さを仮に3行で制限してみましょう。
<template>
<textarea
v-model="text"
class="textarea"
:style="{height:`${getTextareaHeight}px`}"
/>
</template>
<script>
export default {
data() {
return {
text: ''
}
},
computed: {
getTextareaHeight () { // テキストエリアの高さ制限
let rowCount = (`${this.text}\n`).match(/\n/g).length
if (rowCount > 3) rowCount = 3 // ここで3行で制限をかける
const LINEHEIGHT = 20 * 1.5 // 1行あたりの高さ(font-size:20pxとline-height: 1.4を考慮)
const PADDING_TOP_AND_BOTTOM = 5 * 2 // padding上下の5pxを考慮
const BORDER_TOP_AND_BOTTOM = 1 * 2 // border上下の1pxを考慮
return LINEHEIGHT * rowCount + PADDING_TOP_AND_BOTTOM + BORDER_TOP_AND_BOTTOM
},
}
}
</script>
<style scoped>
.textarea {
padding: 5px;
border: 1px solid #000;
font-size: 20px;
line-height: 1.5;
}
</style>
高さに関しては、JSで動的に指定するのでインラインスタイルになります。
テキストエリアにかかっているスタイルを考慮しなければならないので、少し面倒な記述になりますね。
うまく、3行以上の高さにならないように可変でありながらも制限はかけることができました。
2.改行を制限する
次にここが、ネックとなりました。テキストエリアの改行は、Enterを押すことで行われます。
なので、enterキーを押した時に処理を発火させ、制限をかければ良いと思っていました。
@keydown
とか@keyup
イベントを使って、そのキーコードなり、キー名で判別すれば良いと考えます。
しかし、それはうまくいきません。
というのも、日本語入力変換時の確定Enterと改行のEnterの判別ができないからです。
@keydown="handleKeydown"
として、console.warn(e.key)
を仕込んでみました。
このように、変換確定でもEnter
が取れてしまいます。
これの解決方法が、composition
イベントを使うという方法です。
https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
compositionstart
: IMEテキスト編集開始時に発火
compositionupdate
: IME編集中のテキストが変更された時に発火
compostionend
: IMEテキスト編集終了時に発火
compositionstart
とcompositionend
を使ってみます。
<template>
<textarea
v-model="text"
class="textarea"
@compositionstart="composing=true"
@compositionend="composing=false"
@keydown="handleKeydown"
:style="{height:`${getTextareaHeight}px`}"
/>
</template>
<script>
export default {
data() {
return {
text: '',
composing: false
}
},
computed: {
//(略)
},
methods: {
handleKeydown() {
if (this.composing) return // IME対応(日本語変換中のEnterを止める)
console.warn(e.key)
}
}
}
</script>
<style scoped>
.textarea {
...(略)
}
</style>
無事に日本語変換確定時のEnterは弾いています。
これで準備は整いました。
先ほどのconsole.warn(e.key)
を置き換えましょう。
methods: {
handleKeydown() {
if (this.composing) return // IME対応(日本語変換中のEnterを止める)
- console.warn(e.key)
+ const rowCount = (`${this.text}\n`).match(/\n/g).length
+ if (rowCount >= 3 && e.key === 'Enter') {
// テキストエリアからフォーカスを外すことで、Enterの連打を防ぐ
+ document.activeElement.blur()
}
}
これで、すでにテキストエリア内が3行に達している場合にEnterを押しても改行できなくなりました。
警告メッセージなど出してあげるとわかりやすいですね!
最後に
これで終わりなのですが、こちらコピペなどで貼り付けると3行を越してしまいます。
やり方は色々あると思うのですが、watch
でtext
を監視して制限処理を入れるなど方法があります。
computed: {
//(略)
},
watch: {
text (val) {
if (val.length >= 30) val = val.slice(0, 30) // 30文字制限
// pasteに対応した改行制限
let rows = val.split(/\n/)
this.text = rows.slice(0, 3).join('\n') // textを更新して改行を入れない
}
},
methods: {
//(略)
}
他にも、ご自分でtextareaにwrap="off"
やmaxLength
などをかけて、制限を追加してみると面白いですね!