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

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

ただいまの
回答率

87.78%

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

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 475

score 134

前提・実現したいこと

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

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

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

試したこと

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

補足情報

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

check解決した方法

+2

selection.modify(); を使って一旦解決としました。

.modify() を利用すると見た目上の行頭や行末、次の行、前の行へ移動したり選択範囲を拡張することができます。
つまり見た目上は複数行になっているけど改行コードは存在していない長い一行のテキストでも、見た目通りにキャレットや選択範囲を操作することが出来るとても便利なメソッドです。

動作サンプル(サンプルの都合上、クリックしたときだけ判定しています)
https://codepen.io/KimTom/pen/bGNyWGW

以下、判定部分を抜粋

var selection = getSelection();

// まず現在のキャレットのoffsetを取得
var positionBefore = Number(selection.focusOffset);

// modify() でキャレットを行末に移動する。
// 同じoffset=10でも、
// // 「こ」右にキャレットがあるとき…キャレット移動しない
// // 「さ」左にキャレットが有るとき…「と」右へキャレット移動
selection.modify("move", "forward", "lineboundary");

// 移動したあとのキャレットのoffsetを取得
var positionAfter = Number(selection.focusOffset);

// offsetの値が同じ…キャレットは行末にある
// offsetの値が異なる…キャレットは行末以外にある
var eol = Boolean(positionBefore == positionAfter);
// これで変数eolがtrueかfalseかで判定できるようになった

// modify() で移動したキャレットを元の位置に戻す
if(!eol){
 var range = document.createRange();
 range.setStart(selection.focusNode, positionBefore);
 range.collapse(true);
 selection.removeAllRanges();
 selection.addRange(range);
}

ということで大変シンプルに判定できました。
ただし大きな問題がひとつ。
今回利用した .modify() は非標準メソッドです。

https://developer.mozilla.org/en-US/docs/Web/API/Selection/modify
http://js.studio-kingdom.com/javascript/selection/modify

IE,Edgeを除いて動作は確認できました。
趣味の範囲を超えた開発で使うことは難しいかもしれません。

ご回答いただいたお二方のおかげで諦めずに調べることが出来ました。本当にありがとうございました。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

座標位置をとるなら

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


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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/01/23 14:54 編集

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

    codepen:https://codepen.io/yambejp/pen/VwYgbRd

    キャンセル

  • 2020/01/23 14:57

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

    キャンセル

  • 2020/01/29 14:31

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

    キャンセル

0

Unicodeで「1字」毎に split する目的で書いた正規表現です。

/*
サロゲートペア
    ([\u0800-\uDBFF][\uDC00-\uDFFF])
発音記号など、合成文字
    ([\1\w\d][
        \u3099-\u309A
        \u0300-\u036F
        \u1AB0-\u1AFF
        \u1DC0-\u1DFF
        \u2000-\u20D0
        \uFE2F-\uFE2F])|([^1\2])/g;

 */
const RE_CHARACTERS = /([\u0800-\uDBFF][\uDC00-\uDFFF])|([\1\w\d][\u3099-\u309A\u0300-\u036F\u1AB0-\u1AFF\u1DC0-\u1DFF\u2000-\u20D0\uFE2F-\uFE2F])|([^1\2])/g;


もっとスマートなパターンがあるかもしれませんが、ご参考になれば。

追記)
上記、正規表現ではコントロールコード(水平タブなど)は取得できませんでしたので、無視してください。

「マウスの押下ポイントも考慮する」という判別方法は思いつきましたが、無理矢理感ありそうですね。

let mousedownPoint = null; // getSelection() から参照できるスコープに保持
textarea.addEventListener("mousedown", function( event ) {
  console.log( event )// マウス押下ポイントも考慮?
})

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/01/29 14:36

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

    キャンセル

  • 2020/01/29 17:00

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

    キャンセル

  • 2020/01/29 19:41

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

    キャンセル

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

  • ただいまの回答率 87.78%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る