例として、この単純なHTMLがあります。
<div id="editable" contenteditable="true">
text text text<br>
text text text<br>
text text text<br>
</div>
<button id="button">focus</button>
シンプルなものが欲しい-ボタンをクリックすると、編集可能なdivの特定の場所にキャレット(カーソル)を配置したい。 Web上で検索すると、ボタンクリックにこのJSが添付されますが、機能しません(FF、Chrome):
var range = document.createRange();
var myDiv = document.getElementById("editable");
range.setStart(myDiv, 5);
range.setEnd(myDiv, 5);
このように手動でキャレット位置を設定することは可能ですか?
ほとんどのブラウザでは、 Range
および Selection
オブジェクトが必要です。各選択境界をノードおよびそのノード内のオフセットとして指定します。たとえば、キャレットをテキストの2行目の5番目の文字に設定するには、次のようにします。
var el = document.getElementById("editable");
var range = document.createRange();
var sel = window.getSelection();
range.setStart(el.childNodes[2], 5);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
IE <9はまったく異なる動作をします。これらのブラウザをサポートする必要がある場合は、別のコードが必要です。
jsFiddleの例: http://jsfiddle.net/timdown/vXnCM/
Contenteditableカーソルポジショニングで見つかるほとんどの答えは、単純なバニラテキストの入力にのみ対応するという点でかなり単純です。コンテナ内でhtml要素を使用すると、入力されたテキストがノードに分割され、ツリー構造全体に自由に分散されます。
カーソル位置を設定するには、指定されたノード内のすべての子テキストノードをループし、初期ノードの開始からchars.count文字までの範囲を設定するこの関数があります。
function createRange(node, chars, range) {
if (!range) {
range = document.createRange()
range.selectNode(node);
range.setStart(node, 0);
}
if (chars.count === 0) {
range.setEnd(node, chars.count);
} else if (node && chars.count >0) {
if (node.nodeType === Node.TEXT_NODE) {
if (node.textContent.length < chars.count) {
chars.count -= node.textContent.length;
} else {
range.setEnd(node, chars.count);
chars.count = 0;
}
} else {
for (var lp = 0; lp < node.childNodes.length; lp++) {
range = createRange(node.childNodes[lp], chars, range);
if (chars.count === 0) {
break;
}
}
}
}
return range;
};
次に、この関数を使用してルーチンを呼び出します。
function setCurrentCursorPosition(chars) {
if (chars >= 0) {
var selection = window.getSelection();
range = createRange(document.getElementById("test").parentNode, { count: chars });
if (range) {
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
}
}
};
Range.collapse(false)は、カーソルを範囲の最後に設定します。 Chrome、IE、Mozilla、Operaの最新バージョンでテストしましたが、すべて正常に動作します。
PS。誰かが興味があるなら、私はこのコードを使用して現在のカーソル位置を取得します:
function isChildOf(node, parentId) {
while (node !== null) {
if (node.id === parentId) {
return true;
}
node = node.parentNode;
}
return false;
};
function getCurrentCursorPosition(parentId) {
var selection = window.getSelection(),
charCount = -1,
node;
if (selection.focusNode) {
if (isChildOf(selection.focusNode, parentId)) {
node = selection.focusNode;
charCount = selection.focusOffset;
while (node) {
if (node.id === parentId) {
break;
}
if (node.previousSibling) {
node = node.previousSibling;
charCount += node.textContent.length;
} else {
node = node.parentNode;
if (node === null) {
break
}
}
}
}
}
return charCount;
};
このコードはset関数の逆を行います。現在のwindow.getSelection()。focusNodeおよびfocusOffsetを取得し、containerIdのIDを持つ親ノードに到達するまで、検出されたすべてのテキスト文字を逆方向にカウントします。 isChildOf関数は、実行される前に、指定されたノードが実際に指定されたparentIdの子であることを確認します。
コードは変更せずにそのまま動作しますが、開発したjQueryプラグインから取得したため、いくつかのthis'sがハッキングされました-何も機能しない場合はお知らせください!
const el = document.getElementById("editable");
el.focus()
let char = 1, sel; // character at which to place caret
if (document.selection) {
sel = document.selection.createRange();
sel.moveStart('character', char);
sel.select();
}
else {
sel = window.getSelection();
sel.collapse(el.lastChild, char);
}
(p)(スパン)などのアドバンス要素がある場合、キャレットを適切な位置に設定するのは非常に困難です。目標は(オブジェクトテキスト)を取得することです:
<div id="editable" contenteditable="true">dddddddddddddddddddddddddddd<p>dd</p>psss<p>dd</p>
<p>dd</p>
<p>text text text</p>
</div>
<p id='we'></p>
<button onclick="set_mouse()">focus</button>
<script>
function set_mouse() {
var as = document.getElementById("editable");
el = as.childNodes[1].childNodes[0];//goal is to get ('we') id to write (object Text) because it work only in object text
var range = document.createRange();
var sel = window.getSelection();
range.setStart(el, 1);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
document.getElementById("we").innerHTML = el;// see out put of we id
}
</script>
JQueryを使用したくない場合は、このアプローチを試すことができます。
public setCaretPosition() {
const editableDiv = document.getElementById('contenteditablediv');
const lastLine = this.input.nativeElement.innerHTML.replace(/.*?(<br>)/g, '');
const selection = window.getSelection();
selection.collapse(editableDiv.childNodes[editableDiv.childNodes.length - 1], lastLine.length);
}
editableDiv
は編集可能な要素です。id
を設定することを忘れないでください。次に、要素からinnerHTML
を取得し、すべてのブレーキラインを切断する必要があります。そして、次の引数で折りたたみを設定します。
シンタックスハイライター(および基本的なコードエディター)を作成しており、単一引用符文字を自動入力してキャレットを戻す方法を知る必要がありました(最近の多くのコードエディターと同様)。
このスレッドからの多くの助け、MDNドキュメント、および多くのmozコンソールウォッチのおかげで、私のソリューションのスニペットがあります。
//onKeyPress event
if (evt.key === "\"") {
let sel = window.getSelection();
let offset = sel.focusOffset;
let focus = sel.focusNode;
focus.textContent += "\""; //setting div's innerText directly creates new
//nodes, which invalidate our selections, so we modify the focusNode directly
let range = document.createRange();
range.selectNode(focus);
range.setStart(focus, offset);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
//end onKeyPress event
これはcontenteditable div要素にあります
私はこれを感謝としてここに残します。すでに受け入れられた答えがあることに気づきました。
Contenteditable要素のキャレットを特定の位置に設定するのは簡単ではないと思います。このために独自のコードを書きました。残りの文字数を計算するノードツリーをバイパスし、必要な要素にキャレットを設定します。このコードはあまりテストしませんでした。
//Set offset in current contenteditable field (for start by default or for with forEnd=true)
function setCurSelectionOffset(offset, forEnd = false) {
const sel = window.getSelection();
if (sel.rangeCount !== 1 || !document.activeElement) return;
const firstRange = sel.getRangeAt(0);
if (offset > 0) {
bypassChildNodes(document.activeElement, offset);
}else{
if (forEnd)
firstRange.setEnd(document.activeElement, 0);
else
firstRange.setStart(document.activeElement, 0);
}
//Bypass in depth
function bypassChildNodes(el, leftOffset) {
const childNodes = el.childNodes;
for (let i = 0; i < childNodes.length && leftOffset; i++) {
const childNode = childNodes[i];
if (childNode.nodeType === 3) {
const curLen = childNode.textContent.length;
if (curLen >= leftOffset) {
if (forEnd)
firstRange.setEnd(childNode, leftOffset);
else
firstRange.setStart(childNode, leftOffset);
return 0;
}else{
leftOffset -= curLen;
}
}else
if (childNode.nodeType === 1) {
leftOffset = bypassChildNodes(childNode, leftOffset);
}
}
return leftOffset;
}
}
また、現在のキャレット位置を取得するコードを作成しました(テストしませんでした)。
//Get offset in current contenteditable field (start offset by default or end offset with calcEnd=true)
function getCurSelectionOffset(calcEnd = false) {
const sel = window.getSelection();
if (sel.rangeCount !== 1 || !document.activeElement) return 0;
const firstRange = sel.getRangeAt(0),
startContainer = calcEnd ? firstRange.endContainer : firstRange.startContainer,
startOffset = calcEnd ? firstRange.endOffset : firstRange.startOffset;
let needStop = false;
return bypassChildNodes(document.activeElement);
//Bypass in depth
function bypassChildNodes(el) {
const childNodes = el.childNodes;
let ans = 0;
if (el === startContainer) {
if (startContainer.nodeType === 3) {
ans = startOffset;
}else
if (startContainer.nodeType === 1) {
for (let i = 0; i < startOffset; i++) {
const childNode = childNodes[i];
ans += childNode.nodeType === 3 ? childNode.textContent.length :
childNode.nodeType === 1 ? childNode.innerText.length :
0;
}
}
needStop = true;
}else{
for (let i = 0; i < childNodes.length && !needStop; i++) {
const childNode = childNodes[i];
ans += bypassChildNodes(childNode);
}
}
return ans;
}
}
また、range.startOffsetおよびrange.endOffsetには、テキストノードの文字オフセット(nodeType === 3)および要素ノードの子ノードオフセット(nodeType === 1)が含まれることに注意する必要があります。 range.startContainerとrange.endContainerは、ツリー内の任意のレベルの任意の要素ノードを参照できます(もちろん、テキストノードも参照できます)。