이 간단한 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의 특정 위치에 배치하고 싶습니다. 웹 검색에서 버튼 클릭 에이 JS가 첨부되었지만 작동하지 않습니다 (FF, Chrome).
var range = document.createRange();
var myDiv = document.getElementById("editable");
range.setStart(myDiv, 5);
range.setEnd(myDiv, 5);
이와 같이 수동 캐럿 위치를 설정할 수 있습니까?
답변
대부분의 브라우저에는 Range
및 Selection
객체 가 필요 합니다. 각 선택 경계를 노드로 지정하고 해당 노드 내에서 오프셋을 지정합니다. 예를 들어, 캐럿을 두 번째 텍스트 행의 다섯 번째 문자로 설정하려면 다음을 수행하십시오.
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/
답변
내용 편집 가능한 커서 위치에서 찾을 수있는 대부분의 답변은 일반 바닐라 텍스트가있는 입력에만 적합하다는 점에서 상당히 단순합니다. 컨테이너 내에서 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에서 테스트했으며 모두 정상적으로 작동합니다.
추신. 누군가 관심이 있다면이 코드를 사용하여 현재 커서 위치를 얻습니다.
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와에서 촬영 한 이의를 – 아무것도 작동하지 않는 경우 알려주세요!
답변
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
요소에서 벗어나 모든 브레이크 라인을 절단해야합니다. 그리고 다음 인수로 축소를 설정하십시오.
답변
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);
}
답변
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
}
<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>
(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>
답변
구문 강조 표시기 (및 기본 코드 편집기)를 작성하고 있으며 작은 따옴표 문자를 자동 입력하고 캐럿을 다시 이동시키는 방법을 알아야했습니다 (요즘 많은 코드 편집기와 마찬가지로).
이 스레드, 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 요소에 있습니다
나는 여기에 이미 인정 된 답변이 있음을 깨닫고 고마워합니다.
답변
나는 이것을 간단한 텍스트 편집기로 만들었습니다.
다른 방법과의 차이점 :
- 고성능
- 모든 공간에서 작동
용법
// get current selection
const [start, end] = getSelectionOffset(container)
// change container html
container.innerHTML = newHtml
// restore selection
setSelectionOffset(container, start, end)
// use this instead innerText for get text with keep all spaces
const innerText = getInnerText(container)
const textBeforeCaret = innerText.substring(0, start)
const textAfterCaret = innerText.substring(start)
selection.ts
/** return true if node found */
function searchNode(
container: Node,
startNode: Node,
predicate: (node: Node) => boolean,
excludeSibling?: boolean,
): boolean {
if (predicate(startNode as Text)) {
return true
}
for (let i = 0, len = startNode.childNodes.length; i < len; i++) {
if (searchNode(startNode, startNode.childNodes[i], predicate, true)) {
return true
}
}
if (!excludeSibling) {
let parentNode = startNode
while (parentNode && parentNode !== container) {
let nextSibling = parentNode.nextSibling
while (nextSibling) {
if (searchNode(container, nextSibling, predicate, true)) {
return true
}
nextSibling = nextSibling.nextSibling
}
parentNode = parentNode.parentNode
}
}
return false
}
function createRange(container: Node, start: number, end: number): Range {
let startNode
searchNode(container, container, node => {
if (node.nodeType === Node.TEXT_NODE) {
const dataLength = (node as Text).data.length
if (start <= dataLength) {
startNode = node
return true
}
start -= dataLength
end -= dataLength
return false
}
})
let endNode
if (startNode) {
searchNode(container, startNode, node => {
if (node.nodeType === Node.TEXT_NODE) {
const dataLength = (node as Text).data.length
if (end <= dataLength) {
endNode = node
return true
}
end -= dataLength
return false
}
})
}
const range = document.createRange()
if (startNode) {
if (start < startNode.data.length) {
range.setStart(startNode, start)
} else {
range.setStartAfter(startNode)
}
} else {
if (start === 0) {
range.setStart(container, 0)
} else {
range.setStartAfter(container)
}
}
if (endNode) {
if (end < endNode.data.length) {
range.setEnd(endNode, end)
} else {
range.setEndAfter(endNode)
}
} else {
if (end === 0) {
range.setEnd(container, 0)
} else {
range.setEndAfter(container)
}
}
return range
}
export function setSelectionOffset(node: Node, start: number, end: number) {
const range = createRange(node, start, end)
const selection = window.getSelection()
selection.removeAllRanges()
selection.addRange(range)
}
function hasChild(container: Node, node: Node): boolean {
while (node) {
if (node === container) {
return true
}
node = node.parentNode
}
return false
}
function getAbsoluteOffset(container: Node, offset: number) {
if (container.nodeType === Node.TEXT_NODE) {
return offset
}
let absoluteOffset = 0
for (let i = 0, len = Math.min(container.childNodes.length, offset); i < len; i++) {
const childNode = container.childNodes[i]
searchNode(childNode, childNode, node => {
if (node.nodeType === Node.TEXT_NODE) {
absoluteOffset += (node as Text).data.length
}
return false
})
}
return absoluteOffset
}
export function getSelectionOffset(container: Node): [number, number] {
let start = 0
let end = 0
const selection = window.getSelection()
for (let i = 0, len = selection.rangeCount; i < len; i++) {
const range = selection.getRangeAt(i)
if (range.intersectsNode(container)) {
const startNode = range.startContainer
searchNode(container, container, node => {
if (startNode === node) {
start += getAbsoluteOffset(node, range.startOffset)
return true
}
const dataLength = node.nodeType === Node.TEXT_NODE
? (node as Text).data.length
: 0
start += dataLength
end += dataLength
return false
})
const endNode = range.endContainer
searchNode(container, startNode, node => {
if (endNode === node) {
end += getAbsoluteOffset(node, range.endOffset)
return true
}
const dataLength = node.nodeType === Node.TEXT_NODE
? (node as Text).data.length
: 0
end += dataLength
return false
})
break
}
}
return [start, end]
}
export function getInnerText(container: Node) {
const buffer = []
searchNode(container, container, node => {
if (node.nodeType === Node.TEXT_NODE) {
buffer.push((node as Text).data)
}
return false
})
return buffer.join('')
}