🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

textarea要素の文字数の扱いについて

textareaの闇を見たような気がした。

これは、文字数のカウンターっぽいものを実装するときに気付いて調べたものです。
いわゆるmaxlengthの敷居と、jsから文字列.lengthで取れる値で、文字数のカウントが違う・・ってなったところが事の発端。

20150624追記
コメントで教えていただいたのですが、この挙動はどうやらWebkitのみだそうです。
DEMOみたく適当な実装すると、現状Webkit以外で困ることになるのでご注意ください。

困ったこと

Twitterみたく、いわゆる文字数をカウントしたい場合。
楽できるところは楽したいので、こういう指定をしますよね。

<textarea maxlength="140" id="jsTweetArea"></textarea>

改行せずに文字を打った場合は、きっちり140字で打ち止めになってくれます。
ペーストしても、はみ出る分はばっさり切ってくれます。
これがHTMLだけでできる時代になりました。

ただ、文字数の表示は欲しいですよね?ってことで。

var tweetArea = document.getElementById('jsTweetArea');

// これで文字数を表示すれば良いのでは!
var count = tweetArea.value.length;

ってなりますよね?
だがしかし。

改行は2文字カウントになる

先にも書きましたが、改行してないテキストの場合は問題ないです。
おかしくなるのは改行を含む場合。

こういう文字列があったとして。

123
456
78

改行文字を1文字として、10文字です。

が、これをmaxlength="10"のtextareaに打ち込んでいくと、最後まで打てません。
改行文字が2文字カウントだからです。

お試しあれ

JS Bin - Collaborative JavaScript Debugging

うーむ。

仕様書曰く

For historical reasons, the element's value is normalised in three different ways for three different purposes.
The raw value is the value as it was originally set. It is not normalized.
The API value is the value used in the value IDL attribute. It is normalized so that line breaks use "LF" (U+000A) characters.
Finally, there is the form submission value. It is normalized so that line breaks use U+000D CARRIAGE RETURN "CRLF" (U+000A) character pairs, and in addition, if necessary given the element's wrap attribute, additional line breaks are inserted to wrap the text at the given width.

なんしか値には3段階あって、

  • raw value: ユーザーが入力したそのままの値
  • API value: (おそらく)jsから触れる値(textarea.valueと同じ)で、改行コードはLFになってる
  • submission value: HTMLがフォームデータとして保持する値で改行コードはCR/LFになり、そのほかwrap属性も加味

てなわけで、文字列.lengthで取れる値と、
maxlengthでバリデーションされる値では、改行文字の扱いが変わってるという話でした。

4.10 Forms — HTML5

結論

textareaでmaxlengthを使いたいなら、改行は2文字分として割り切る。
文字列カウントもDEMOのコードみたく2文字カウントして、表示もそれに従うように。

それができないなら、maxlength使うの諦めて自前で実装するしかないのかなーと。