복붙노트

[JQUERY] 방법의 contentEditable 요소 (DIV)에서 설정 캐럿 (커서) 위치로?

JQUERY

방법의 contentEditable 요소 (DIV)에서 설정 캐럿 (커서) 위치로?

해결법


  1. 1.대부분의 브라우저에서는 범위 및 선택 개체가 필요합니다. 당신은 노드로 선택 경계의 각을 지정하고 해당 노드 내의 오프셋 (offset). 예를 들어, 텍스트의 두 번째 행의 5 번째의 캐릭터에 캐럿을 설정하려면 다음을 수행 것입니다 :

    대부분의 브라우저에서는 범위 및 선택 개체가 필요합니다. 당신은 노드로 선택 경계의 각을 지정하고 해당 노드 내의 오프셋 (offset). 예를 들어, 텍스트의 두 번째 행의 5 번째의 캐릭터에 캐럿을 설정하려면 다음을 수행 것입니다 :

    setCaret 함수 () { VAR 엘 = document.getElementById를 ( "편집") VAR 범위 document.createRange = () VAR SEL = window.getSelection () range.setStart (el.childNodes [2], 5) range.collapse (참) sel.removeAllRanges () sel.addRange (범위) }

    텍스트 텍스트 텍스트로
    텍스트 텍스트 텍스트로
    텍스트 텍스트 텍스트로
    <버튼 ID = "버튼"의 onclick = "setCaret ()"> 포커스

    IE <9는 완전히 다르게 작동합니다. 당신이이 브라우저를 지원해야하는 경우 다른 코드가 필요합니다.

    jsFiddle 예 : http://jsfiddle.net/timdown/vXnCM/


  2. 2.당신의 contentEditable 커서 위치에서 찾을 대부분의 답변은 단지 평범한 텍스트 입력에 대한 수용 점에서 상당히 단순하다. 당신은 컨테이너 내에서 HTML 요소를 사용하면 텍스트 노드에 도착 분할을 입력하고 트리 구조를 통해 자유롭게 배포했습니다.

    당신의 contentEditable 커서 위치에서 찾을 대부분의 답변은 단지 평범한 텍스트 입력에 대한 수용 점에서 상당히 단순하다. 당신은 컨테이너 내에서 HTML 요소를 사용하면 텍스트 노드에 도착 분할을 입력하고 트리 구조를 통해 자유롭게 배포했습니다.

    커서의 위치를 ​​설정하려면 I는 제공된 노드와 설정합니다 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);
            }
        }
    };
    

    (FALSE)을 range.collapse의 범위의 끝 부분에 커서를 설정한다. 나는 크롬, IE, 모질라와 오페라 그들은 모두 잘 작동의 최신 버전을 테스트했습니다.

    추신. 사람이 관심이 있다면이 코드를 사용하여 현재 커서 위치를 얻을 :

    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;
    };
    

    이 코드는 설정 기능의 반대를 않습니다 -. 그것은 containerId의 ID로 부모 노드 안타 때까지 () focusNode 및 focusOffset 및 카운트 거꾸로 모든 텍스트 문자가 발생, 현재 window.getSelection을 가져옵니다. suplied 노드가 제공된 parentId의 자녀가 실제로 것을 실행하기 전에 isChildOf 기능은 확인합니다.

    이 코드는 변경없이 바로 작동해야하지만 난 그냥이의 몇을 해킹 그래서 개발 한 플러그인 JQuery와에서 촬영 한 - 아무것도 작동하지 않는 경우 알려주세요!


  3. 3.당신이 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을 얻을 모든 브레이크 선을 절단 할 필요가있다. 그리고 바로 옆에 인수 붕괴를 설정합니다.


  4. 4.

      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);
      }
    

  5. 5.set_mouse 함수 () { = document.getElementById를 ( "편집")와 같은 VAR; EL = as.childNodes [1] .childNodes [0]; // 목표는 쓰기가 개체의 텍스트에서만 작동하기 때문에 (개체 텍스트)에 ( '우리') ID를 얻을 수 있습니다 VAR 범위를 document.createRange () =; VAR SEL = window.getSelection (); range.setStart EL (1); range.collapse (TRUE); sel.removeAllRanges (); sel.addRange (범위); . document.getElementById를 ( "우리") innerHTML을 = 엘; // 우리 ID의 배웅 넣어 }
    dddddddddddddddddddddddddddd

    DD 개인 가입자 단말기

    DD

    DD

    텍스트 본문 텍스트

    <버튼의 onclick = "set_mouse ()"> 포커스

    set_mouse 함수 () { = document.getElementById를 ( "편집")와 같은 VAR; EL = as.childNodes [1] .childNodes [0]; // 목표는 쓰기가 개체의 텍스트에서만 작동하기 때문에 (개체 텍스트)에 ( '우리') ID를 얻을 수 있습니다 VAR 범위를 document.createRange () =; VAR SEL = window.getSelection (); range.setStart EL (1); range.collapse (TRUE); sel.removeAllRanges (); sel.addRange (범위); . document.getElementById를 ( "우리") innerHTML을 = 엘; // 우리 ID의 배웅 넣어 }

    dddddddddddddddddddddddddddd

    DD 개인 가입자 단말기

    DD

    DD

    텍스트 본문 텍스트

    <버튼의 onclick = "set_mouse ()"> 포커스

    당신이 (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>
    

  6. 6.나는 구문 형광펜 (및 기본 코드 편집기)를 쓰고 있어요, 나는에 작은 따옴표 문자를 자동 입력하고 (현재 코드 편집기의 많은 같은) 캐럿 다시 이동 방법을 알 필요가 있었다.

    나는 구문 형광펜 (및 기본 코드 편집기)를 쓰고 있어요, 나는에 작은 따옴표 문자를 자동 입력하고 (현재 코드 편집기의 많은 같은) 캐럿 다시 이동 방법을 알 필요가 있었다.

    Heres는 내 솔루션의 조각,이 스레드는 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 요소에

    나는 허용 대답은 이미 거기 실현하는 덕분으로 여기를 떠나.


  7. 7.내 간단한 텍스트 편집기이했다.

    내 간단한 텍스트 편집기이했다.

    다른 방법의 차이점 :

    용법

    // 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('')
    }
    

  8. 8.나는 리암의 대답 @ 리팩토링. 나는 그 기능이 요소 대신의 #ID, 일부 다른 작은 비틀기를받을 만든 정적 메서드와 클래스에 넣어.

    나는 리암의 대답 @ 리팩토링. 나는 그 기능이 요소 대신의 #ID, 일부 다른 작은 비틀기를받을 만든 정적 메서드와 클래스에 넣어.

    이 코드는 <= "true"를 사업부의 contentEditable>로 제작 될 수있는 서식있는 텍스트 상자에 커서를 고정에 특히 좋다. 나는 아래의 코드에 도착하기 전에 며칠 동안이 붙어 있었다.

    편집 : 그의 대답이 대답은 입력 타격과 관련된 버그가 있습니다. 문자로 계산하지 않습니다 입력 때문에, 커서의 위치를 ​​입력 타격 후 엉망이됩니다. 내가 코드를 수정할 수 있어요, 난 내 대답을 업데이트합니다.

    EDIT2 : 저장 자신 두통을 많이하고 있는지 확인하여 은 표시합니다 인라인 블록을. Enter 키를 누르면 대신로

    퍼팅 크롬과 관련이 수정 몇 가지 버그.

    let richText = document.getElementById('rich-text');
    let offset = Cursor.getCurrentCursorPosition(richText);
    // do stuff to the innerHTML, such as adding/removing <span> tags
    Cursor.setCurrentCursorPosition(offset, richText);
    richText.focus();
    
    // Credit to Liam (Stack Overflow)
    // https://stackoverflow.com/a/41034697/3480193
    class Cursor {
        static getCurrentCursorPosition(parentElement) {
            var selection = window.getSelection(),
                charCount = -1,
                node;
            
            if (selection.focusNode) {
                if (Cursor._isChildOf(selection.focusNode, parentElement)) {
                    node = selection.focusNode; 
                    charCount = selection.focusOffset;
                    
                    while (node) {
                        if (node === parentElement) {
                            break;
                        }
    
                        if (node.previousSibling) {
                            node = node.previousSibling;
                            charCount += node.textContent.length;
                        } else {
                            node = node.parentNode;
                            if (node === null) {
                                break;
                            }
                        }
                    }
                }
            }
            
            return charCount;
        }
        
        static setCurrentCursorPosition(chars, element) {
            if (chars >= 0) {
                var selection = window.getSelection();
                
                let range = Cursor._createRange(element, { count: chars });
    
                if (range) {
                    range.collapse(false);
                    selection.removeAllRanges();
                    selection.addRange(range);
                }
            }
        }
        
        static _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 = Cursor._createRange(node.childNodes[lp], chars, range);
    
                        if (chars.count === 0) {
                        break;
                        }
                    }
                }
            } 
    
            return range;
        }
        
        static _isChildOf(node, parentElement) {
            while (node !== null) {
                if (node === parentElement) {
                    return true;
                }
                node = node.parentNode;
            }
    
            return false;
        }
    }
    

  9. 9.나는의 contentEditable 요소에 어떤 위치로 설정 캐럿에 간단하지 생각합니다. 나는이에 대한 내 자신의 코드를 썼습니다. 그것은 많은 문자가 왼쪽 세트가 필요한 요소에 캐럿 방법 calcing 노드 트리를 무시합니다. 나는 많은이 코드를 테스트하지 않았다.

    나는의 contentEditable 요소에 어떤 위치로 설정 캐럿에 간단하지 생각합니다. 나는이에 대한 내 자신의 코드를 썼습니다. 그것은 많은 문자가 왼쪽 세트가 필요한 요소에 캐럿 방법 calcing 노드 트리를 무시합니다. 나는 많은이 코드를 테스트하지 않았다.

    //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;
        }
    }
    

    또한 요소 노드의 오프셋 텍스트 노드 (nodeType에 === 3)와 자식 노드 (nodeType에 === 1)에 대한 문자 오프셋 포함 range.startOffset 및 range.endOffset 알고 있어야합니다. range.startContainer 및 range.endContainer 트리 (물론 그들은 또한 텍스트 노드를 참조 할 수 있습니다)에서 모든 수준의 요소 노드에 참조 할 수 있습니다.


  10. 10.마지막으로 알려진 "좋은"텍스트 행의 팀 아래의 답변에 따라,하지만 확인합니다. 그것은 맨 끝에 커서를 배치합니다.

    마지막으로 알려진 "좋은"텍스트 행의 팀 아래의 답변에 따라,하지만 확인합니다. 그것은 맨 끝에 커서를 배치합니다.

    또한, 나는 또한 재귀 적 / 반복적으로 DOM에서 절대 마지막으로 "좋은"텍스트 노드를 찾기 위해 각 연속 마지막 자식의 마지막 자식을 확인할 수 있습니다.

    onClickHandler 함수 () { setCaret (document.getElementById를 ( "편집")); } 기능 setCaret (엘) { 하자 document.createRange 범위 = (), SEL = window.getSelection () lastKnownIndex = -1; 대해 (ⅰ = 0하자 나는 0 }

    텍스트 텍스트 텍스트로
    텍스트 텍스트 텍스트로
    텍스트 텍스트 텍스트로
    <버튼 ID = "버튼"의 onclick = "onClickHandler ()"> 포커스

  11. from https://stackoverflow.com/questions/6249095/how-to-set-caretcursor-position-in-contenteditable-element-div by cc-by-sa and MIT license