質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

3回答

900閲覧

[js] キャレットが文章の折り返される位置にあるか判定することは可能か

KimTom

総合スコア134

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

2グッド

0クリップ

投稿2020/01/23 03:00

編集2020/01/23 04:50

前提・実現したいこと

Web上で動作するエディタを作ろうとしています。
キャレットの位置を判定するために getSelection() でキャレットが何文字目にあるのかを取得していたのですが、一文が長く文章が折り返されたときにキャレットの位置が正確に取得できない問題に直面しました。

言葉では非常にわかりにくいのでサンプルを用意しました。
https://jsfiddle.net/8hrmd1po/1/
サンプル内で「こ」の右をクリックしたとき offset の値は10です。
しかし「さ」の左をクリックしても offset の値は10となります。
(「こ」の右でも「さ」の左でも10文字目と11文字目の間だから同じoffsetの値になるのは理解しています)

この2つの状態を区別して判定する方法はないでしょうか?

試したこと

  • getSelection()で取得できる情報は一通り見たのですが、差異はなさそうでした。
  • 様々なキーワードで検索もしました。キャレットの座標を取得する方法などヒットしましたが、文章の折り返し地点についての言及は見つかりませんでした。

補足情報

サンプルと同様、contenteditable な div で開発しています。

x_x, oikashinoa👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答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
KimTom

総合スコア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
AkitoshiManabe

総合スコア5432

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KimTom

2020/01/29 05:36

回答ありがとうございます。クリック時の判定ならこれで座標取得してそこから頑張る、という方法が取れそうですね。矢印キーで移動してきたときに判定出来ないのがもどかしいですが…
AkitoshiManabe

2020/01/29 08:00

ブラウザによっては、「こ」の手前から[→]キーを押すと、次行の「さ」の手前に移動するものがあるので、yambejp さんの示された対応策で「キーボード操作は統一し、マウスだけ例外」とするのはどうでしょうか。キーボード操作は、そのほうが範囲指定しやすそうに感じました。 周囲の方にもキーボードでの操作感を聞いてみて方針を決めるも良いと思います。
KimTom

2020/01/29 10:41

コメントありがとうございます。実は先程自力で解決できたのでこれから解決方法を更新します!
guest

0

座標位置をとるなら

javascript

1window.getSelection().getRangeAt(0).getBoundingClientRect()

でDOM Rectが取れるのでxやyプロパティを参照ください

投稿2020/01/23 03:31

yambejp

総合スコア114843

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yambejp

2020/01/23 03:33

ただし、selectionchangeイベントでは「こ」の右から「さ」の左に 移動しても反応しません(selectionがchangeされないから)
KimTom

2020/01/23 04:00 編集

ご回答いただきありがとうございます。ただ残念ながら、これで取得できるのはどちらも「さ」のバウンディングボックスのようです。 getRangeAt(0)で取得したoffsetの値に該当する文字のバウンディングボックスを取得しているようですが、「こ」右も「さ」左もoffsetの値は10なので同じ情報を取得してしまうようです。
yambejp

2020/01/23 04:44

失礼しました。確認しましたがたしかに同じ位置をとっていますね もうちょい調べてみます
KimTom

2020/01/23 04:52

大変心強いです。ありがとうございます。この問題について解決策が思いつかず非常に時間を使ってしまったため反応してくれる人がいるだけでもとてもありがたいです。ありがとうございます。
yambejp

2020/01/23 05:54 編集

いろいろやってみましたが「こ」の後ろと「さ」の前を 判断するのは難しいですね。(たぶんなんかあるんでしょうけど) 対応策なのですが「こ」の後ろにキャレットを置かせないように するのはだめでしょうか? (どうせ「こ」の後ろに文字挿入はできないのだし・・・) codepen:https://codepen.io/yambejp/pen/VwYgbRd
yambejp

2020/01/23 05:57

※setTimeoutでずらしているのはfirefoxのようにキーボードによる カーソル移動時に「さ」の前で左をおすと「こ」の後ろにcaretが 移動してしまうブラウザ対策です。 setTimeoutしないと「こ」の前にキーボードで移動できない
KimTom

2020/01/29 05:31

大変遅くなってしまい申し訳ございません… アイデアありがとうございます! なるほど、キャレットを置かせないという発想ですね。これは状況によっては有効な方法かもしれないです。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問