부모 컨테이너를 기준으로 범위의 시작 및 끝 오프셋 가져 오기


79

이 HTML 요소가 있다고 가정합니다.

<div id="parent">
 Hello everyone! <a>This is my home page</a>
 <p>Bye!</p>
</div>

그리고 사용자는 마우스로 "홈"을 선택합니다.

나는 #parent그의 선택 시작에 얼마나 많은 문자가 있는지 (그리고 #parent그의 선택 끝 에서 얼마나 많은 문자가 끝나는 지) 결정할 수 있기를 원합니다 . HTML 태그를 선택한 경우에도 작동합니다. (그리고 모든 브라우저에서 작동하려면 필요합니다)

range.startOffset 유망 해 보이지만 범위의 직접 컨테이너에만 상대적인 오프셋이며 컨테이너가 텍스트 노드 인 경우에만 문자 오프셋입니다.


> 이것은 그가 HTML 태그를 선택하더라도 작동합니다 <이것은 무엇을 의미합니까? 누군가 HTML 태그를 어떻게 선택합니까? 설명 해주십시오.
Satyajit 2011 년

사용자가의 모든 #parent항목을 선택하면 일부 HTML 태그 (<a> 및 <p>)가 선택됩니다
Tom Lehman

답변:


201

최신 정보

주석에서 지적했듯이 내 원래 답변 (아래)은 선택의 끝 또는 캐럿 위치 만 반환합니다. 시작 및 끝 오프셋을 반환하도록 코드를 조정하는 것은 매우 쉽습니다. 다음은 그렇게하는 예입니다.

다음은 지정된 요소 내에서 캐럿의 문자 오프셋을 가져 오는 함수입니다. 그러나 이것은 거의 확실하게 줄 바꿈과 불일치가 있고 CSS를 통해 숨겨진 텍스트를 처리하려고 시도하지 않는 순진한 구현입니다 (IE는 이러한 텍스트를 올바르게 무시하지만 다른 브라우저에서는 그렇지 않을 것입니다). 이 모든 것을 제대로 처리하는 것은 까다로울 것입니다. 이제 Rangy 라이브러리에 대해 시도했습니다 .

라이브 예 : http://jsfiddle.net/TjXEG/900/

function getCaretCharacterOffsetWithin(element) {
    var caretOffset = 0;
    var doc = element.ownerDocument || element.document;
    var win = doc.defaultView || doc.parentWindow;
    var sel;
    if (typeof win.getSelection != "undefined") {
        sel = win.getSelection();
        if (sel.rangeCount > 0) {
            var range = win.getSelection().getRangeAt(0);
            var preCaretRange = range.cloneRange();
            preCaretRange.selectNodeContents(element);
            preCaretRange.setEnd(range.endContainer, range.endOffset);
            caretOffset = preCaretRange.toString().length;
        }
    } else if ( (sel = doc.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        var preCaretTextRange = doc.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        caretOffset = preCaretTextRange.text.length;
    }
    return caretOffset;
}

2
@ 팀 다운 : 좋습니다, 감사합니다! 내 구체적인 예 (TinyMCE 사용)의 경우 실제로 더 간단한 방법을 tinyMCE.execCommand('mceInsertContent', false, newContent);찾았 습니다 . , 여기 에서 찾았 습니다 . 하지만 도움을 받으려면 +1하세요!
Travesty3

4
@RafaelDiaz : 예, 그리고 HTML 또는 CSS에서 암시하는 다른 줄 바꿈. 이상적인 솔루션이 아닙니다.
Tim Down

1
@TimDown 대신 해당 요소 html을 통해 위치를 가져 오기 위해 이것을 수정하는 방법이 있습니까? 예 : Html 문자열 : "<div class ="c1 "> Hello </ div> Hello에서 커서가"el "을 판매하면 17을 반환합니다
Fred Johnson

1
@TimDown 나는 stackoverflow.com/questions/22935320/…에 설명 된대로 오류를 방지하기 위해 win.getSelection().rangeCount > 0실행하기 전에 검사 를 추가하는 것이 좋습니다 . 내가 직접 문제에 부딪 쳤습니다. rangeCount 검사로 문제가 해결되었습니다win.getSelection().getRangeAt(0)
Gregor

2
@KimchiMan : 당신은 더 이상하지 않습니다. element.documentIE 5 및 5.5 용이었습니다.
Tim Down

25

나는 이것이 1 년 된 것을 알고 있지만,이 게시물은 캐럿 위치를 찾는 것에 대한 많은 질문에 대한 상위 검색 결과이며 이것이 유용하다는 것을 알았습니다.

콘텐츠 편집 가능한 div에서 요소를 한 위치에서 다른 위치로 끌어 놓은 후 위의 Tim의 훌륭한 스크립트를 사용하여 새 커서 위치를 찾으려고했습니다. FF 및 IE에서 완벽하게 작동했지만 Chrome에서는 드래그 동작이 드래그 시작과 끝 사이의 모든 콘텐츠를 강조 표시하여 반환 된 결과 caretOffset가 너무 크거나 작습니다 (선택 영역의 길이 기준).

첫 번째 if 문에 몇 줄을 추가하여 텍스트가 선택되었는지 확인하고 그에 따라 결과를 조정했습니다. 새로운 진술은 아래와 같습니다. OP가하려는 것이 아니기 때문에 여기에 추가하는 것이 부적절하다면 저를 용서하십시오.하지만 제가 말했듯이 Caret 위치와 관련된 정보에 대한 여러 검색이 저를이 게시물로 이끌었으므로 다른 사람을 도울 것입니다. .

줄이 추가 된 Tim의 첫 번째 if 문 (*) :

if (typeof window.getSelection != "undefined") {
  var range = window.getSelection().getRangeAt(0);
  var selected = range.toString().length; // *
  var preCaretRange = range.cloneRange();
  preCaretRange.selectNodeContents(element);
  preCaretRange.setEnd(range.endContainer, range.endOffset);

  if(selected){ // *
    caretOffset = preCaretRange.toString().length - selected; // *
  } else { // *
    caretOffset = preCaretRange.toString().length; 
  } // *
}

1
이미 나와 함께 오프셋 값이 있고 (서버 측 JSON 응답에서 얻고 있음) 단어를 강조 표시하려는 경우 단어를 강조 표시하는 방법을 아는 사람이 있습니까? 이 두 값 ( start문자 오프셋 및 stop문자 오프셋)을 간단히 연결 하고 단어를 강조 표시 할 수있는 방대한 라이브러리에서는 아무것도 찾을 수 없습니다 . 조언하십시오.

나는 이것이 4 년 된 포스트라는 것을 알고 있지만, 내가 선택 될 범위가있는 그런 시나리오에서 단어를 강조 할 수 있습니까
Varun

selected빼기 전에 확인하는 이유가 있습니까? 0이면 0 caretOffset을 빼서 변경하는 것이 아닙니다. 그렇죠?
Donnie D' Amato

1
@ DonnieD'Amato 좋은 지적입니다. 나는 몇 가지 테스트를 수행하고 선택이 없을 때 항상 == 0을 얻었으므로 (정의되지 않거나 null 또는 예기치 않은 다른 항목이 없음) 해당 검사를 건너 뛰고 항상 선택 항목을 빼는 것이 안전해야합니다.
Cody Crumrine

1
@CodyCrumrine 제쳐두고, 무언가를 빼는 것은 nullusing과 동일합니다 0(그러나 그것이 모든 자바 스크립트 엔진에서 사실인지 확실하지 않습니다). :)
aleclarson

17

며칠 동안 실험 한 후 유망 해 보이는 접근 방식을 발견했습니다. 태그를 올바르게 selectNodeContents()처리하지 않기 때문에 <br>.NET node내부 의 각 텍스트 길이를 결정하는 사용자 지정 알고리즘을 작성 했습니다 contenteditable. 예를 들어 선택 시작을 계산하기 위해 모든 이전 노드의 텍스트 길이를 합산합니다. 이렇게하면 (다중) 줄 바꿈을 처리 할 수 있습니다.


if 조건 검사에서 getTextLength 및 getNodeTextLength를 래핑해야했습니다 if (node != null). 또한 node.parentNode이후 에 null 검사를 추가했습니다if (node != parent)
RugerSR9

정말 감사합니다 완벽하게 작동합니다. document.activeElement어떤 이유로 작동하지 않았기 때문에 함께 작업하던 단일 div를 사용했습니다.
Xertz

1

이 솔루션은 부모 컨테이너로 돌아가는 이전 형제의 텍스트 콘텐츠 길이를 계산하여 작동합니다. 깊이에 관계없이 중첩 된 태그를 처리하지만 모든 가장자리 케이스를 다루지는 않지만 비슷한 요구 사항이있는 경우 시작하기에 좋고 간단한 곳입니다.

  calculateTotalOffset(node, offset) {
    let total = offset
    let curNode = node

    while (curNode.id != 'parent') {
      if(curNode.previousSibling) {
        total += curNode.previousSibling.textContent.length

        curNode = curNode.previousSibling
      } else {
        curNode = curNode.parentElement
      }
    }

   return total
 }

 // after selection

let start = calculateTotalOffset(range.startContainer, range.startOffset)
let end = calculateTotalOffset(range.endContainer, range.endOffset)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.