前提・実現したいこと
Web上で動作するエディタを作ろうとしています。
キャレットの位置を判定するために getSelection() でキャレットが何文字目にあるのかを取得していたのですが、一文が長く文章が折り返されたときにキャレットの位置が正確に取得できない問題に直面しました。
言葉では非常にわかりにくいのでサンプルを用意しました。
https://jsfiddle.net/8hrmd1po/1/
サンプル内で「こ」の右をクリックしたとき offset の値は10です。
しかし「さ」の左をクリックしても offset の値は10となります。
(「こ」の右でも「さ」の左でも10文字目と11文字目の間だから同じoffsetの値になるのは理解しています)
この2つの状態を区別して判定する方法はないでしょうか?
試したこと
- getSelection()で取得できる情報は一通り見たのですが、差異はなさそうでした。
- 様々なキーワードで検索もしました。キャレットの座標を取得する方法などヒットしましたが、文章の折り返し地点についての言及は見つかりませんでした。
補足情報
サンプルと同様、contenteditable な div で開発しています。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

回答3件
0
自己解決
selection.modify(); を使って一旦解決としました。
.modify() を利用すると見た目上の行頭や行末、次の行、前の行へ移動したり選択範囲を拡張することができます。
つまり見た目上は複数行になっているけど改行コードは存在していない長い一行のテキストでも、見た目通りにキャレットや選択範囲を操作することが出来るとても便利なメソッドです。
動作サンプル(サンプルの都合上、クリックしたときだけ判定しています)
https://codepen.io/KimTom/pen/bGNyWGW
以下、判定部分を抜粋
js
1 2var selection = getSelection(); 3 4// まず現在のキャレットのoffsetを取得 5var positionBefore = Number(selection.focusOffset); 6 7// modify() でキャレットを行末に移動する。 8// 同じoffset=10でも、 9// // 「こ」右にキャレットがあるとき…キャレット移動しない 10// // 「さ」左にキャレットが有るとき…「と」右へキャレット移動 11selection.modify("move", "forward", "lineboundary"); 12 13// 移動したあとのキャレットのoffsetを取得 14var positionAfter = Number(selection.focusOffset); 15 16// offsetの値が同じ…キャレットは行末にある 17// offsetの値が異なる…キャレットは行末以外にある 18var eol = Boolean(positionBefore == positionAfter); 19// これで変数eolがtrueかfalseかで判定できるようになった 20 21// modify() で移動したキャレットを元の位置に戻す 22if(!eol){ 23 var range = document.createRange(); 24 range.setStart(selection.focusNode, positionBefore); 25 range.collapse(true); 26 selection.removeAllRanges(); 27 selection.addRange(range); 28}
ということで大変シンプルに判定できました。
ただし大きな問題がひとつ。
今回利用した .modify() は非標準メソッドです。
https://developer.mozilla.org/en-US/docs/Web/API/Selection/modify
http://js.studio-kingdom.com/javascript/selection/modify
IE,Edgeを除いて動作は確認できました。
趣味の範囲を超えた開発で使うことは難しいかもしれません。
ご回答いただいたお二方のおかげで諦めずに調べることが出来ました。本当にありがとうございました。
投稿2020/01/29 11:12
編集2020/01/30 06:35総合スコア134
0
Unicodeで「1字」毎に split する目的で書いた正規表現です。
javascript
1/* 2サロゲートペア 3 ([\u0800-\uDBFF][\uDC00-\uDFFF]) 4発音記号など、合成文字 5 ([\1\w\d][ 6 \u3099-\u309A 7 \u0300-\u036F 8 \u1AB0-\u1AFF 9 \u1DC0-\u1DFF 10 \u2000-\u20D0 11 \uFE2F-\uFE2F])|([^1\2])/g; 12 13 */ 14const RE_CHARACTERS = /([\u0800-\uDBFF][\uDC00-\uDFFF])|([\1\w\d][\u3099-\u309A\u0300-\u036F\u1AB0-\u1AFF\u1DC0-\u1DFF\u2000-\u20D0\uFE2F-\uFE2F])|([^1\2])/g; 15
もっとスマートなパターンがあるかもしれませんが、ご参考になれば。
追記)
上記、正規表現ではコントロールコード(水平タブなど)は取得できませんでしたので、無視してください。
「マウスの押下ポイントも考慮する」という判別方法は思いつきましたが、無理矢理感ありそうですね。
javascript
1let mousedownPoint = null; // getSelection() から参照できるスコープに保持 2textarea.addEventListener("mousedown", function( event ) { 3 console.log( event )// マウス押下ポイントも考慮? 4})
投稿2020/01/23 10:35
編集2020/01/28 01:11総合スコア5434
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

0
座標位置をとるなら
javascript
1window.getSelection().getRangeAt(0).getBoundingClientRect()
でDOM Rectが取れるのでxやyプロパティを参照ください
投稿2020/01/23 03:31
総合スコア117667
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。