私のプロジェクトでは、キャレットのオフセット位置をtextarea
ピクセルで取得しようとしています。これはできますか?
ここで質問する前に、多くの links 、特にTim Downを試してみましたが、IE8 +、ChromeおよびFirefoxで機能する解決策を見つけることができませんでした。 Tim Downがこれに取り組んでいます 。
一部の otherlinks 私が見つけたものには、キャレット位置の上部オフセットが見つからないなど、多くの問題があります。
キャレットのオフセット位置に基づいて配置することでtextarea
内にオートコンプリートの候補ボックスを表示したいので、キャレットのオフセット位置を取得しようとしています。
PS:textarea
に関連するコードをたくさん書いたため、contenteditable div
を使用できません。
rangyinputs 、 rangy および jQuery を使用する方法を次に示します。
基本的には、テキスト全体をtextarea
内から同じサイズのdiv
にコピーします。すべてのブラウザーでtextarea
とdiv
がまったく同じ方法でコンテンツをラップするようにCSSを設定しました。
textarea
をクリックすると、キャレットが配置されている文字インデックスが読み取られ、span
内の同じインデックスにキャレットdiv
が挿入されます。そのようにするだけで、ユーザーが行の先頭をクリックしたときにキャレットspan
が前の行に戻る問題が発生しました。これを修正するために、前の文字がspace
であるかどうかを確認します(ラップが発生する可能性があります)。これがtrue
の場合は、span
でラップし、次の単語(キャレット位置の直後の単語)をspan
で囲みます。次に、これら2つのspan
の上位の値を比較します。異なる場合は、いくつかの折り返しが発生しているため、top
とleft
の値が_#nextword
_ span
はキャレット位置と同じです。
このアプローチはまだ改善することができます。おそらく問題が発生する可能性のあるすべてのことを考えていなかったと私は確信しています。私がそうしたとしても、私が持っていないので、それらすべての修正を実装することを気にしていません。現時点でそうするべき時、あなたが見る必要がある多くの事柄:
現在、Windows 8上のすべてのブラウザーでまったく同じように動作し、最新バージョンのChrome、Firefox、IEおよびSafariを使用しています。私のテストはそれほど厳密ではありません。
ここにあります jsFiddle。
私はそれがあなたに役立つことを願っています、少なくともそれはあなたに構築するいくつかのアイデアを与えるかもしれません。
適切な場所に配置されたul
を含め、DOM操作後にtextarea
の選択が元の場所にリセットされないというFirefoxの問題を修正しました。
IE7-IE9のサポートを追加し、コメントで指摘されている複数のWordの選択の問題を修正しました。
で挿入されたハードリターンのサポートを追加しました Enter 行の複数のスペース。
のデフォルトの動作に関する問題を修正しました ctrl+shift+left arrow テキスト選択方法。
JavaScript
_function getTextAreaXandY() {
// Don't do anything if key pressed is left arrow
if (e.which == 37) return;
// Save selection start
var selection = $(this).getSelection();
var index = selection.start;
// Copy text to div
$(this).blur();
$("div").text($(this).val());
// Get current character
$(this).setSelection(index, index + 1);
currentcharacter = $(this).getSelection().text;
// Get previous character
$(this).setSelection(index - 1, index)
previouscharacter = $(this).getSelection().text;
var start, endchar;
var end = 0;
var range = rangy.createRange();
// If current or previous character is a space or a line break, find the next Word and wrap it in a span
var linebreak = previouscharacter.match(/(\r\n|\n|\r)/gm) == undefined ? false : true;
if (previouscharacter == ' ' || currentcharacter == ' ' || linebreak) {
i = index + 1; // Start at the end of the current space
while (endchar != ' ' && end < $(this).val().length) {
i++;
$(this).setSelection(i, i + 1)
var sel = $(this).getSelection();
endchar = sel.text;
end = sel.start;
}
range.setStart($("div")[0].childNodes[0], index);
range.setEnd($("div")[0].childNodes[0], end);
var nextword = range.toHtml();
range.deleteContents();
var position = $("<span id='nextword'>" + nextword + "</span>")[0];
range.insertNode(position);
var nextwordtop = $("#nextword").position().top;
}
// Insert `#caret` at the position of the caret
range.setStart($("div")[0].childNodes[0], index);
var caret = $("<span id='caret'></span>")[0];
range.insertNode(caret);
var carettop = $("#caret").position().top;
// If preceding character is a space, wrap it in a span
if (previouscharacter == ' ') {
range.setStart($("div")[0].childNodes[0], index - 1);
range.setEnd($("div")[0].childNodes[0], index);
var prevchar = $("<span id='prevchar'></span>")[0];
range.insertNode(prevchar);
var prevchartop = $("#prevchar").position().top;
}
// Set textarea selection back to selection start
$(this).focus();
$(this).setSelection(index, selection.end);
// If the top value of the previous character span is not equal to the top value of the next Word,
// there must have been some wrapping going on, the previous character was a space, so the wrapping
// would have occured after this space, its safe to assume that the left and top value of `#nextword`
// indicate the caret position
if (prevchartop != undefined && prevchartop != nextwordtop) {
$("label").text('X: ' + $("#nextword").position().left + 'px, Y: ' + $("#nextword").position().top);
$('ul').css('left', ($("#nextword").position().left) + 'px');
$('ul').css('top', ($("#nextword").position().top + 13) + 'px');
}
// if not, then there was no wrapping, we can take the left and the top value from `#caret`
else {
$("label").text('X: ' + $("#caret").position().left + 'px, Y: ' + $("#caret").position().top);
$('ul').css('left', ($("#caret").position().left) + 'px');
$('ul').css('top', ($("#caret").position().top + 14) + 'px');
}
$('ul').css('display', 'block');
}
$("textarea").click(getTextAreaXandY);
$("textarea").keyup(getTextAreaXandY);
_
[〜#〜] html [〜#〜]
_<div></div>
<textarea>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.</textarea>
<label></label>
<ul>
<li>Why don't you type this..</li>
</ul>
_
[〜#〜] css [〜#〜]
_body {
font-family: Verdana;
font-size: 12px;
line-height: 14px;
}
textarea, div {
font-family: Verdana;
font-size: 12px;
line-height: 14px;
width: 300px;
display: block;
overflow: hidden;
border: 1px solid black;
padding: 0;
margin: 0;
resize: none;
min-height: 300px;
position: absolute;
-moz-box-sizing: border-box;
white-space: pre-wrap;
}
span {
display: inline-block;
height: 14px;
position: relative;
}
span#caret {
display: inline;
}
label {
display: block;
margin-left: 320px;
}
ul {
padding: 0px;
margin: 9px;
position: absolute;
z-index: 999;
border: 1px solid #000;
background-color: #FFF;
list-style-type:none;
display: none;
}
@media screen and (-webkit-min-device-pixel-ratio:0) {
span {
white-space: pre-wrap;
}
}
div {
/* Firefox wrapping fix */
-moz-padding-end: 1.5px;
-moz-padding-start: 1.5px;
/* IE8/IE9 wrapping fix */
padding-right: 5px\0/;
width: 295px\0/;
}
span#caret
{
display: inline-block\0/;
}
_
別個の(非表示の)要素を作成し、開始からカーソル位置までのテキスト領域コンテンツで埋めることができます。 Textareaと「クローン」には、一致するCSS(フォントプロパティ、パディング/マージン/ボーダー、幅)が必要です。次に、これらの要素を互いに積み重ねます。
実用的な例から始めて、コードをウォークスルーします: http://jsfiddle.net/g7rBk/
HTML:
<textarea id="input"></textarea>
<div id="output"><span></span></div>
<div id="xy"></div>
Textareaは自明です。出力は、テキストコンテンツを渡して測定を行う非表示の要素です。重要なのは、インライン要素を使用することです。 "xy" divは、テスト目的の単なる指標です。
CSS:
/* identical styling to match the dimensions and position of textarea and its "clone"
*/
#input, #output {
position:absolute;
top:0;
left:0;
font:14px/1 monospace;
padding:5px;
border:1px solid #999;
white-space:pre;
margin:0;
background:transparent;
width:300px;
max-width:300px;
}
/* make sure the textarea isn't obscured by clone */
#input {
z-index:2;
min-height:200px;
}
#output {
border-color:transparent;
}
/* hide the span visually using opacity (not display:none), so it's still measurable; make it break long words inside like textarea does. */
#output span {
opacity:0;
Word-wrap: break-Word;
overflow-wrap: break-Word;
}
/* the cursor position indicator */
#xy {
position:absolute;
width:4px;
height:4px;
background:#f00;
}
JavaScript:
/* get references to DOM nodes we'll use */
var input = document.getElementById('input'),
output = document.getElementById('output').firstChild,
position = document.getElementById('position'),
/* And finally, here it goes: */
update = function(){
/* Fill the clone with textarea content from start to the position of the caret. You may need to expand here to support older IE [1]. The replace /\n$/ is necessary to get position when cursor is at the beginning of empty new line.
*/
output.innerHTML = input.value.substr( 0, input.selectionStart ).replace(/\n$/,"\n\001");
/* the fun part!
We use an inline element, so getClientRects[2] will return a collection of rectangles wrapping each line of text.
We only need the position of the last rectangle.
*/
var rects = output.getClientRects(),
lastRect = rects[ rects.length - 1 ],
top = lastRect.top - input.scrollTop,
left = lastRect.left+lastRect.width;
/* position the little div and see if it matches caret position :) */
xy.style.cssText = "top: "+top+"px;left: "+left+"px";
}
[2] https://developer.mozilla.org/en/docs/DOM/element.getClientRects
編集:この例は、固定幅のテキストエリアでのみ機能します。ユーザーがサイズ変更可能なtextareaで機能させるには、イベントリスナーをresizeイベントに追加し、#outputディメンションを新しい#inputディメンションと一致するように設定する必要があります。
ピクセル単位のキャレット位置を取得するためのソリューションは、他の回答で提示されているものよりもはるかに単純です。
この質問は2008年の質問の複製であり、私はそれに答えましたここ。この質問は何年も前に重複して閉じられていたはずなので、私はそのリンクでのみ回答を維持します。
meteor-autocomplete のtextareaキャレット座標プラグインを探したので、GitHubで8つのプラグインをすべて評価しました。勝者は、はるかに textarea-caret-position fromComponentです。
ミラー<div>
が画面外に作成され、<textarea>
とまったく同じスタイルになります。次に、キャレットまでのテキストエリアのテキストがdivにコピーされ、その直後に<span>
が挿入されます。次に、スパンのテキストコンテンツは、偽のdivでのラッピングを忠実に再現するために、textarea内の残りのテキストに設定されます。
これは、長い行の折り返しに関するすべてのEdgeケースを処理することが保証されている唯一の方法です。 @ユーザードロップダウンの位置を決定するためにGitHubでも使用されます。
作業例のJsFiddle: http://jsfiddle.net/42zHC/2/
基本的に、幅に収まる列の数がわかります(モノスペースになるため)。スクロールバーを常に表示するように強制する必要があります。そうしないと、計算がオフになります。次に、幅に合う列の数を割り、1文字あたりのxオフセットを取得します。次に、textareaの行の高さを設定します。行にいくつの文字があるかわかっているので、それを文字数で割り、行番号を取得します。行の高さで、yオフセットができました。次に、textareaのscrollTopを取得して減算します。これにより、スクロールバーの使用を開始しても、正しい位置に表示されます。
JavaScript:
$(document).ready(function () {
var cols = document.getElementById('t').cols;
var width = document.getElementById('t').clientWidth;
var height = $('textarea').css('line-height');
var pos = $('textarea').position();
$('#t').on('keyup', function () {
el = document.getElementById("t");
if (el.selectionStart) {
selection = el.selectionStart;
} else if (document.selection) {
el.focus();
var r = document.selection.createRange();
if (r == null) {
selection = 0;
}
var re = el.createTextRange(),
rc = re.duplicate();
re.moveToBookmark(r.getBookmark());
rc.setEndPoint('EndToStart', re);
selection = rc.text.length;
} else { selection = 0 }
var row = Math.floor((selection-1) / cols);
var col = (selection - (row * cols));
var x = Math.floor((col*(width/cols)));
var y = (parseInt(height)*row);
$('span').html("row: " + row + "<br>columns" + col + "<br>width: " + width + "<br>x: " + x +"px<br>y: " + y +"px<br>Scrolltop: "+$(this).scrollTop()).css('top',pos.top+y-$(this).scrollTop()).css('left',pos.left+x+10);
});
});
HTML:
<textarea id="t"></textarea>
<br>
<span id="tooltip" style="background:yellow"></span>
CSS:
textarea {
height: 80px;
line-height: 12px;
overflow-y:scroll;
}
span {
position: absolute;
}