jsで""で囲まれた文字の色を変えたい <div contenteditable="true"></div>
jsで""で囲まれた文字の色を変えたい <div contenteditable="true"></div> の要素に"で囲まれた文字を入力すると"とそれに囲まれた文字だけをオレンジ色に変えたいです "で囲まれた文字を正規表現で読み取りreplaceでcolor:orangeのspanで囲ってみたのですがそれ以降spanのほうに入力されてしまったり spanを入れてしまうと入力してるときの|みたいなのの位置が最初(1文字目の前)の場所に戻ってしまいます。 どうすればよいのでしょう? 理想像としてはVScodeの""で囲った感じのやつです
JavaScript・118閲覧
ベストアンサー
■■■ エディタブル要素 = 意外と厄介 ■■■ エディタブル要素における、 テキストへのタグ付けについては、 やりたいことはシンプルな割に厄介な処理となる。 その厄介な問題について説明したあとで、 実際のソースコードの実施例を示す。 【 タグ付けが厄介 】 エディタブル要素(contenteditable="true")で、 入力テキストを部分的に色を変える場合は、 色を変えたい部分を <span class="~"></span> で囲って、 CSS で色を指定すればいい。 ─[ 例 ]──────────── あいうえお . . ↓ あい<span class="highlight">うえ</span>お ──────────────── ところが、 改行が絡んでくると、 このタグ付けが一気に面倒になる。 エディタブル要素は、 編集で改行が入力されると、 改行位置に改行コード \n や改行タグ <br> を挿入するのではなく、 <div> や <p> で囲んでノードを分離してしまう。 そのため、 色を変えたい部分が改行位置をまたいでいる場合に、 そのまま <span>~</span> で囲んでしまうと、 ──────────────── <div>あいう<span class="highlight">えお</div> <div>かき</span>くけこ</div> ──────────────── のようになり、 <span>~</span> と <div>~</div> がクロスして、 構造がおかしなことになってしまう。 そのため、 keydown イベントにて、 改行入力(keyCode==13)の場合だけ、 通常動作をキャンセル( preventDefault() )して、 代わりに手動で改行コード \n を挿入することで、 改行でエディタブル要素内がノード分離されるのを 防いでおくのが得策。 その場合、 改行コード \n を実際の改行として機能させるため、 エディタブル要素は、 CSS で「white-space: pre」にしておく必要がある。 【 末尾の改行が厄介 】 エディタブル要素では、 内部テキスト末尾の改行コード \n が1つ無視されるため、 末尾で改行したい場合は、 改行コードを2つ \n\n のようにしないと改行できない。 また、 末尾に改行コードが常時1つなければ、 文字数のカウントが合わなくなるなどのバグもある。 そこで、 入力の際には毎回、 テキスト末尾が改行コード \n 以外の文字であれば、 その文字+改行コードに置換する必要がある。 ──────────────── textContent.replace( /(^|[^\n])(?=$)/, "$1\n" ); ──────────────── 【 innerHTML 書き換えが厄介 】 エディタブル要素内のテキストに対して、 <span> 要素でタグ付けする場合、 innerHTML を、 囲いのない HTML のコードから、 囲いをつけた HTML のコードに書き換えることになる。 innerHTML が書き換えられると、 エディタブル要素のテキストは、 まるごと新しいテキストに置き換えられた扱いになるから、 入力カーソル(キャレット)の位置が、 テキストの先頭位置にリセットされてしまう。 そのため、 エディタブル要素でテキストにタグ付けするときは、 innerHTML を書き換える前に キャレットの位置を記憶しておいて、 innerHTML を書き換えた後に キャレットの位置を再びセットしないといけない。 【 キャレット位置の管理が厄介 】 キャレット位置は、 Javascript ではノード単位のオフセットで管理されるから、 <span> によるタグ付けなどにより テキストノードが複数に分割されていると、 分割されたテキストノードを 1つ1つ辿って文字数をカウントしながら、 エディタブル要素の先頭からのオフセット位置を 算出しないといけない。 【 IME が厄介 】 エディタブル要素でテキストを入力している際、 全角文字を入力しているときは、 IME モードが発動して変換状態になるけど、 入力時の keyup や input イベントで、 innerHTML を操作すると、 IME モードが解除されてしまうため、 1文字打つたびに確定されて漢字入力ができなくなる。 これを防ぐため、 compositionstart イベントを検知したら、 IME モードの発動状態を示す変数を true にして、 IME モード発動中は innerHTML の操作を行なわないようにする必要がある。 ■■■ 実施例 ■■■ 以上の問題点を解決した上での ソースコード全体の実施例を示す。 [ 実施例:jsfiddle ] https://jsfiddle.net/5m37axur/ ■■■ 簡単に解説 ■■■ 【 CSS 】 エディタブル要素内では 改行コード \n でテキストを改行できるように、 ──────────────── [ contenteditable="true" ] { white-space: pre; } ──────────────── を指定しておく。 【 すべてのエディタブル要素 】 Javascript では、 今回実装する "~" をハイライトする機能を、 すべてのエディタブル要素に対して実装するために、 document.querySelectorAll().forEach() で すべてのエディタブル要素のリストに対して操作する。 ──────────────── document.querySelectorAll( "[ contenteditable='true' ]" ). forEach( editor => { ~ 各 editor に機能を実装する Javascript コードを記述~ } ); ──────────────── 【 改行管理 】 キーボード入力された際の keydown イベントにて、 押されたのが [Enter] キー(keyCode===13)だった場合、 デフォルトの改行動作をキャンセル(preventDefault)し、 代わりに手動で改行コード \n を挿入する。 ──────────────── editor.addEventListener( "keydown", evt => { if( evt.keyCode===13 ) { //----]----] 改行キャンセル evt.preventDefault(); //----]----] キャレット位置:取得 const caret = getCaret(); //----]----] 改行コード挿入 const text = editor.textContent; editor.innerHTML = ( text.substr( 0, caret )+"\n"+text.substr( caret ) ); //----]----] テキスト置換 fmtText(); //----]----: キャレット位置:セット setCaret( caret+1 ); } }, { "passive": false, "capture": false } ); ──────────────── キャレット位置を取得する関数 getCaret()、 キャレット位置をセットする関数 setCaret()、 ハイライトや末尾の改行コード追加など、 テキストの置換を行う関数 frmText() については、 別途作成している。 【 入力時の処理 】 エディタブル要素に入力があった際の input イベントでは、 ハイライトや末尾の改行コード追加など、 テキストの置換処理を行う。 置換の前後では、 キャレット位置の取得、セットを忘れずに実施。 また、 IME モード(変換操作)の発動中は、 return false で処理を抜ける。 ──────────────── editor.addEventListener( "input", evt => { //----] 例外:IME if( IME ){ return false } //----] キャレット位置:取得 const caret = getCaret(); //----] ハイライト fmtText(); //----] キャレット位置:セット setCaret( caret ); }, false ); ──────────────── >> 返信へつづく。
【 IME モードへの対応 】 全角文字を入力して、 IME モードが発動(compositionstart)したときは、 変数 IME を true にする。 IME モードが終了(compositionend)したときは、 変数 IME を false に戻すと同時に、 入力時(input)と同様に置換処理を実行しておく。
質問者からのお礼コメント
BAにするのを忘れてました 理想通りかつわかりやすい説明ありがとうございました!
お礼日時:3/20 0:16