行番号を右側に表示できてテキストを折り返して表示できる JS 不要の入力ボックスを作る。
完成品はこちら。
See the Pen Text Editor by Mizuki HAGIMOTO (@mizkichan) on CodePen.
編集可能な要素を作る
<textarea>
はスタイリングが難しすぎる。<input type="text">
だと1行しか入力できず、複数並べて使おうとすると JS が必須になる。そこで contenteditable
属性1を使う。
<divid="editor"contenteditable="true"></div>
これで内容を編集できる <div>
要素が作れる。それぞれの行は <div>
要素で囲まれて <div id="editor">
の子ノードになる。たとえば foo
⏎bar
と入力すると
<divid="editor"contenteditable="true"><div>foo</div><div>bar</div></div>
のような形で DOM に格納される2。
行番号を表示する
行番号の計算と表示には CSS Counter が使える。counter-increment
プロパティ3を使うと、このプロパティが適用される要素が現れるたびにカウンタを加算することができる。各行の <div>
要素にこのプロパティを適用することで、行番号カウンタ lineno
が加算されるようにする。
#editor>div{counter-increment:lineno;}
カウンタの値は counter()
関数4を使って文字列に変換でき、 content
プロパティ5の値として用いることができる。各行の <div>
要素の ::before
疑似要素6にこれらのプロパティを適用することで、各行の直前に行番号を表示することができる。
#editor>div::before{content:counter(lineno);}
レイアウト等を調整する
表示すべきものはこれで十分だが、行番号とテキストとの間に隙間が全くないし、行番号が2桁になるとその行からテキストの表示開始位置がズレてしまうし、テキストが長くなると折り返しが行番号の真下から始まってしまう。
まず行番号をどうにかしよう。::before
疑似要素は他のインライン要素と同様の仕方でスタイリングできる。インラインブロック要素に変更して、幅を指定して、右側にマージンをつけて、右寄せにする。
#editor>div::before{display:inline-block;width:2em;margin-right:1em;text-align:right;}
テキスト折り返しを考慮しないなら7これだけでよいが、考慮するならもう一工夫いる。要するに、1行ごとに行番号とテキストが同じ高さで並ぶようにすればよい。Flexbox8の出番である。この場合は単に display
プロパティの値に flex
を指定すればよい。先ほどの例の #editor > div::before { display: inline-block; }
は削除されたい。
#editor>div{display:flex;}#editor>div::before{/* 削除: display: inline-block; */}
おわりに
これだけでは勝手が悪い動作をするので何だかんだいっても JS でイベントを待機していじくり回す必要がある。がんばれ。
https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable ↩
正確には、改行を入力しない場合は単一のテキストノードが
<div id="editor">
の直接の子ノードになるという例外がある。この記事における実装では、改行なしの一行しかテキストがない場合のスタイルを用意していないので、その場合は行番号が表示されない。そもそも CSS でテキストノードないしはテキストノードしか含まない要素を選択する方法がない(と思う)。 ↩https://developer.mozilla.org/en-US/docs/Web/CSS/counter-increment ↩
white-space: nowrap;
を指定する。https://developer.mozilla.org/en-US/docs/Web/CSS/white-space ↩