contentEditable <div>에서 커서 위치 설정


142

contentEditable = 'on'<div>가 포커스를 다시 얻었을 때 커서 / 캐럿 위치를 마지막으로 알려진 위치로 설정하는 결정적인 크로스 브라우저 솔루션을 따릅니다. 내용 편집 가능한 div의 기본 기능은 캐럿 / 커서를 클릭 할 때마다 캐럿 / 커서를 div의 텍스트 시작 부분으로 옮기는 것이므로 바람직하지 않습니다.

그들이 div의 초점을 떠날 때 현재 커서 위치를 변수에 저장하고 다시 내부에 초점이있을 때 이것을 다시 설정해야한다고 생각하지만 함께 모을 수는 없었습니다. 아직 코드 샘플입니다.

누군가 생각, 작업 코드 스 니펫 또는 샘플이 있다면 기꺼이 볼 수 있습니다.

아직 코드가 없지만 여기에 내가 가진 것이 있습니다.

<script type="text/javascript">
// jQuery
$(document).ready(function() {
   $('#area').focus(function() { .. }  // focus I would imagine I need.
}
</script>
<div id="area" contentEditable="true"></div>

추신. 이 리소스를 시도했지만 <div>에서 작동하지 않는 것 같습니다. 아마도 텍스트 영역에만 해당 (콘텐츠 편집 가능 엔티티의 끝으로 커서를 이동하는 방법 )


contentEditableIE가 아닌 브라우저에서 작동 하는지 몰랐습니다 o_o
aditya

10
그렇습니다.
GONeale

5
aditya, Safari 2 이상, Firefox 3 이상인 것 같습니다.
eyelidlessness

div에서 tabindex = "0"을 설정하십시오. 대부분의 브라우저에서 초점을 맞출 수 있어야합니다.
Tokimon

답변:


58

이는 표준 기반 브라우저와 호환되지만 IE에서는 실패 할 수 있습니다. 나는 그것을 출발점으로 제공하고 있습니다. IE는 DOM 범위를 지원하지 않습니다.

var editable = document.getElementById('editable'),
    selection, range;

// Populates selection and range variables
var captureSelection = function(e) {
    // Don't capture selection outside editable region
    var isOrContainsAnchor = false,
        isOrContainsFocus = false,
        sel = window.getSelection(),
        parentAnchor = sel.anchorNode,
        parentFocus = sel.focusNode;

    while(parentAnchor && parentAnchor != document.documentElement) {
        if(parentAnchor == editable) {
            isOrContainsAnchor = true;
        }
        parentAnchor = parentAnchor.parentNode;
    }

    while(parentFocus && parentFocus != document.documentElement) {
        if(parentFocus == editable) {
            isOrContainsFocus = true;
        }
        parentFocus = parentFocus.parentNode;
    }

    if(!isOrContainsAnchor || !isOrContainsFocus) {
        return;
    }

    selection = window.getSelection();

    // Get range (standards)
    if(selection.getRangeAt !== undefined) {
        range = selection.getRangeAt(0);

    // Get range (Safari 2)
    } else if(
        document.createRange &&
        selection.anchorNode &&
        selection.anchorOffset &&
        selection.focusNode &&
        selection.focusOffset
    ) {
        range = document.createRange();
        range.setStart(selection.anchorNode, selection.anchorOffset);
        range.setEnd(selection.focusNode, selection.focusOffset);
    } else {
        // Failure here, not handled by the rest of the script.
        // Probably IE or some older browser
    }
};

// Recalculate selection while typing
editable.onkeyup = captureSelection;

// Recalculate selection after clicking/drag-selecting
editable.onmousedown = function(e) {
    editable.className = editable.className + ' selecting';
};
document.onmouseup = function(e) {
    if(editable.className.match(/\sselecting(\s|$)/)) {
        editable.className = editable.className.replace(/ selecting(\s|$)/, '');
        captureSelection();
    }
};

editable.onblur = function(e) {
    var cursorStart = document.createElement('span'),
        collapsed = !!range.collapsed;

    cursorStart.id = 'cursorStart';
    cursorStart.appendChild(document.createTextNode('—'));

    // Insert beginning cursor marker
    range.insertNode(cursorStart);

    // Insert end cursor marker if any text is selected
    if(!collapsed) {
        var cursorEnd = document.createElement('span');
        cursorEnd.id = 'cursorEnd';
        range.collapse();
        range.insertNode(cursorEnd);
    }
};

// Add callbacks to afterFocus to be called after cursor is replaced
// if you like, this would be useful for styling buttons and so on
var afterFocus = [];
editable.onfocus = function(e) {
    // Slight delay will avoid the initial selection
    // (at start or of contents depending on browser) being mistaken
    setTimeout(function() {
        var cursorStart = document.getElementById('cursorStart'),
            cursorEnd = document.getElementById('cursorEnd');

        // Don't do anything if user is creating a new selection
        if(editable.className.match(/\sselecting(\s|$)/)) {
            if(cursorStart) {
                cursorStart.parentNode.removeChild(cursorStart);
            }
            if(cursorEnd) {
                cursorEnd.parentNode.removeChild(cursorEnd);
            }
        } else if(cursorStart) {
            captureSelection();
            var range = document.createRange();

            if(cursorEnd) {
                range.setStartAfter(cursorStart);
                range.setEndBefore(cursorEnd);

                // Delete cursor markers
                cursorStart.parentNode.removeChild(cursorStart);
                cursorEnd.parentNode.removeChild(cursorEnd);

                // Select range
                selection.removeAllRanges();
                selection.addRange(range);
            } else {
                range.selectNode(cursorStart);

                // Select range
                selection.removeAllRanges();
                selection.addRange(range);

                // Delete cursor marker
                document.execCommand('delete', false, null);
            }
        }

        // Call callbacks here
        for(var i = 0; i < afterFocus.length; i++) {
            afterFocus[i]();
        }
        afterFocus = [];

        // Register selection again
        captureSelection();
    }, 10);
};

고마워 눈, 나는 당신의 해결책을 시험해 보았습니다. 약간 서두르지 만 배선 후에는 마지막 초점 지점 (디버그 마커 인 것처럼 보임)에 "-"위치 만 배치하고 우리가 잃을 때입니다. 포커스, 그것은 다시 클릭 할 때 커서 / 캐럿을 복원하지 않는 것 같습니다 (적어도 Chrome에서는 아니고 FF를 시도 할 것입니다) .div의 끝 부분으로 이동합니다. 그래서 모든 브라우저에서 호환되며 잘 작동하는 경향이 있으므로 Nico의 솔루션을 수락합니다. 노력해 주셔서 감사합니다.
GONeale

3
당신과 니코를 더 조사한 후, 나의 마지막 응답을 잊어 버린 것을 아십니까? 내 설명에서 요구 한 것이 아니라 내가 선호하고 실현해야 할 것입니다. 사용자는 일반 텍스트 상자처럼 <div>로 초점을 다시 활성화 할 때 클릭하는 위치의 커서 위치를 올바르게 설정 합니다. 마지막 지점으로 초점을 복원하는 것만으로는 사용자에게 친숙한 입력 필드를 만들 수 없습니다. 나는 당신에게 포인트를 수여합니다.
GONeale

9
잘 작동합니다! 여기서, 상기 용액의 jsfiddle입니다 jsfiddle.net/s5xAr/3
보한

4
OP가 제안하고 프레임 워크를 사용하려고했지만 실제 JavaScript를 게시 해 주셔서 감사합니다.
John

cursorStart.appendChild(document.createTextNode('\u0002'));우리가 생각하는 합리적인 대체품입니다. — char. 코드 주셔서 감사합니다
twobob

97

이 솔루션은 모든 주요 브라우저에서 작동합니다.

saveSelection()은 div 의 onmouseuponkeyup이벤트에 첨부되고 선택 사항을 변수에 저장합니다 savedRange.

restoreSelection()onfocusdiv 의 이벤트에 첨부되고에 저장된 선택을 다시 선택합니다 savedRange.

사용자가 div를 클릭 할 때 선택을 복원하지 않으려는 경우 완벽하게 작동합니다 (일반적으로 커서는 클릭하는 곳으로 이동하지만 코드는 완전성을 위해 포함됨)

이를 달성하기 위해 onclickonmousedown이벤트는 이벤트를 취소 cancelEvent()하는 크로스 브라우저 기능인 기능에 의해 취소됩니다. cancelEvent()기능도 실행 restoreSelection()클릭 이벤트가 취소 될 때 사업부가 포커스를받지 않고이 기능을 실행하지 않는 때문에 아무것도 전혀 선택되지 않기 때문에 기능.

변수 isInFocus는 포커스가 있는지 여부를 저장하고 "false" onblur및 "true"로 변경됩니다 onfocus. 이렇게하면 div의 초점이 맞지 않을 때만 클릭 이벤트를 취소 할 수 있습니다 (그렇지 않으면 선택을 전혀 변경할 수 없음).

당신은 선택을 복원 사업부는 클릭에 의해 초점이 맞춰지면 변화를, 그리고하지 않는 선택을하려는 경우 onclick(초점이 요소에 부여 된 경우에만 programtically 사용 document.getElementById("area").focus();또는 유사한 다음 단순히 제거 onclickonmousedown이벤트를. onblur이벤트와 onDivBlur()cancelEvent()기능을 이러한 상황에서도 안전하게 제거 할 수 있습니다.

이 코드는 빠르게 테스트하려는 경우 html 페이지 본문에 직접 놓으면 작동합니다.

<div id="area" style="width:300px;height:300px;" onblur="onDivBlur();" onmousedown="return cancelEvent(event);" onclick="return cancelEvent(event);" contentEditable="true" onmouseup="saveSelection();" onkeyup="saveSelection();" onfocus="restoreSelection();"></div>
<script type="text/javascript">
var savedRange,isInFocus;
function saveSelection()
{
    if(window.getSelection)//non IE Browsers
    {
        savedRange = window.getSelection().getRangeAt(0);
    }
    else if(document.selection)//IE
    { 
        savedRange = document.selection.createRange();  
    } 
}

function restoreSelection()
{
    isInFocus = true;
    document.getElementById("area").focus();
    if (savedRange != null) {
        if (window.getSelection)//non IE and there is already a selection
        {
            var s = window.getSelection();
            if (s.rangeCount > 0) 
                s.removeAllRanges();
            s.addRange(savedRange);
        }
        else if (document.createRange)//non IE and no selection
        {
            window.getSelection().addRange(savedRange);
        }
        else if (document.selection)//IE
        {
            savedRange.select();
        }
    }
}
//this part onwards is only needed if you want to restore selection onclick
var isInFocus = false;
function onDivBlur()
{
    isInFocus = false;
}

function cancelEvent(e)
{
    if (isInFocus == false && savedRange != null) {
        if (e && e.preventDefault) {
            //alert("FF");
            e.stopPropagation(); // DOM style (return false doesn't always work in FF)
            e.preventDefault();
        }
        else {
            window.event.cancelBubble = true;//IE stopPropagation
        }
        restoreSelection();
        return false; // false = IE style
    }
}
</script>

1
실제로 작동합니다 감사합니다! IE, Chrome 및 FF 최신에서 테스트되었습니다. 최고 지연 답변에 대해 죄송합니다 =)
GONeale

하지 않음 if (window.getSelection)...브라우저 만 지원하는 경우 테스트 getSelection선택이되지 여부?
Sandy Gifford

@Sandy 네 맞습니다. 이 코드 부분은 표준 getSelectionAPI 를 사용할지 아니면 document.selection이전 버전의 IE에서 사용 하는 레거시 API 를 사용할지를 결정합니다 . 선택이없는 경우 나중에 getRangeAt (0)호출하면 null복원 기능에서 확인됩니다.
Nico Burns

@NicoBurns는 맞지만 두 번째 조건부 블록 ( else if (document.createRange)) 의 코드는 내가보고있는 것입니다. window.getSelection존재하지 않는 경우에만 호출 되지만, 다음을 사용합니다.window.getSelection
Sandy Gifford

@NicoBurns 더 나아가, 나는 당신이 브라우저를 찾지는 않을 것이라고 생각 window.getSelection하지 않습니다 document.createRange-두 번째 블록은 절대 사용되지 않을 것입니다 ...
Sandy Gifford

19

최신 정보

아래에 게시 한 코드의 향상된 버전을 통합하는 Rangy 라는 브라우저 간 범위 및 선택 라이브러리를 작성했습니다 . 당신이 사용할 수있는 저장 선택 및 모듈을 복원 내가 같이 사용 무언가에 유혹 될 거라고하지만,이 특정 질문에 대한 @Nico 화상의 대답은 당신이 당신의 프로젝트의 선택과 다른 아무것도하지 않는 경우와의 대부분을 필요가 없습니다 도서관.

이전 답변

IERange ( http://code.google.com/p/ierange/ )를 사용 하여 IE의 TextRange를 DOM 범위와 같은 것으로 변환하고 눈꺼풀이없는 시작점과 함께 사용할 수 있습니다. 개인적으로 나는 전체를 사용하는 대신 Range <-> TextRange 변환을 수행하는 IERange의 알고리즘 만 사용하려고합니다. 그리고 IE의 선택 객체에는 focusNode 및 anchorNode 속성이 없지만 선택에서 얻은 Range / TextRange를 대신 사용할 수 있습니다.

나는 이것을하기 위해 무언가를 모을 수도 있고, 내가 할 때 여기에 다시 게시 할 것입니다.

편집하다:

이 작업을 수행하는 스크립트 데모를 만들었습니다. 그것은 아직 조사 할 시간이 없었던 Opera 9의 버그를 제외하고는 지금까지 시도한 모든 작업에서 작동합니다. 작동하는 브라우저는 Windows의 IE 5.5, 6 및 7, Chrome 2, Firefox 2, 3 및 3.5 및 Safari 4입니다.

http://www.timdown.co.uk/code/selections/

브라우저에서 포커스 노드가 선택의 시작 부분에 있고 뒤로 오른쪽 또는 왼쪽 커서 키를 누르면 선택 영역의 시작 부분을 기준으로 캐럿이 이동합니다. 선택을 복원 할 때 이것을 복제 할 수 없다고 생각하므로 포커스 노드가 항상 선택의 끝에 있습니다.

나는 이것을 어느 시점에서 곧 완전히 쓸 것이다.


15

관련 상황이 있었는데 특히 커서 위치를 contenteditable div의 END로 설정해야했습니다. Rangy와 같은 완전한 라이브러리를 사용하고 싶지 않았으며 많은 솔루션이 너무 무거웠습니다.

결국 캐럿 위치를 contenteditable div의 끝으로 설정하는 간단한 jQuery 함수를 생각해 냈습니다.

$.fn.focusEnd = function() {
    $(this).focus();
    var tmp = $('<span />').appendTo($(this)),
        node = tmp.get(0),
        range = null,
        sel = null;

    if (document.selection) {
        range = document.body.createTextRange();
        range.moveToElementText(node);
        range.select();
    } else if (window.getSelection) {
        range = document.createRange();
        range.selectNode(node);
        sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    }
    tmp.remove();
    return this;
}

이론은 간단합니다. 편집 가능한 끝에 스팬을 추가하고 선택 한 다음 스팬을 제거하십시오 .div 끝에 커서가 남습니다. 이 솔루션을 조정하여 원하는 위치에 스팬을 삽입하여 커서를 특정 지점에 놓을 수 있습니다.

사용법은 간단합니다.

$('#editable').focusEnd();

그게 다야!


3
<span>브라우저의 내장 실행 취소 스택을 실수로 중단시키는 을 삽입 할 필요는 없습니다 . 참조 stackoverflow.com/a/4238971/96100
팀 다운

6

Nico Burns의 답변을 jQuery를 사용하여 만들었습니다.

  • 일반 : 모든 div contentEditable="true"
  • 더 짧은

jQuery 1.6 이상이 필요합니다.

savedRanges = new Object();
$('div[contenteditable="true"]').focus(function(){
    var s = window.getSelection();
    var t = $('div[contenteditable="true"]').index(this);
    if (typeof(savedRanges[t]) === "undefined"){
        savedRanges[t]= new Range();
    } else if(s.rangeCount > 0) {
        s.removeAllRanges();
        s.addRange(savedRanges[t]);
    }
}).bind("mouseup keyup",function(){
    var t = $('div[contenteditable="true"]').index(this);
    savedRanges[t] = window.getSelection().getRangeAt(0);
}).on("mousedown click",function(e){
    if(!$(this).is(":focus")){
        e.stopPropagation();
        e.preventDefault();
        $(this).focus();
    }
});


@ salivan 나는 그것을 업데이트하는 것이 늦다는 것을 알고 있지만 지금은 효과가 있다고 생각합니다. 기본적으로 새로운 조건을 추가하고 element의 id 사용에서 element의 index로 변경했습니다. 항상 존재해야합니다. :
Gatsbimantico

4

놀란 후에 위의 눈꺼풀이없는 답변을 수정하고 jQuery 플러그인으로 만들면 다음 중 하나를 수행 할 수 있습니다.

var html = "The quick brown fox";
$div.html(html);

// Select at the text "quick":
$div.setContentEditableSelection(4, 5);

// Select at the beginning of the contenteditable div:
$div.setContentEditableSelection(0);

// Select at the end of the contenteditable div:
$div.setContentEditableSelection(html.length);

긴 코드 게시물을 실례하지만 다른 사람에게 도움이 될 수 있습니다.

$.fn.setContentEditableSelection = function(position, length) {
    if (typeof(length) == "undefined") {
        length = 0;
    }

    return this.each(function() {
        var $this = $(this);
        var editable = this;
        var selection;
        var range;

        var html = $this.html();
        html = html.substring(0, position) +
            '<a id="cursorStart"></a>' +
            html.substring(position, position + length) +
            '<a id="cursorEnd"></a>' +
            html.substring(position + length, html.length);
        console.log(html);
        $this.html(html);

        // Populates selection and range variables
        var captureSelection = function(e) {
            // Don't capture selection outside editable region
            var isOrContainsAnchor = false,
                isOrContainsFocus = false,
                sel = window.getSelection(),
                parentAnchor = sel.anchorNode,
                parentFocus = sel.focusNode;

            while (parentAnchor && parentAnchor != document.documentElement) {
                if (parentAnchor == editable) {
                    isOrContainsAnchor = true;
                }
                parentAnchor = parentAnchor.parentNode;
            }

            while (parentFocus && parentFocus != document.documentElement) {
                if (parentFocus == editable) {
                    isOrContainsFocus = true;
                }
                parentFocus = parentFocus.parentNode;
            }

            if (!isOrContainsAnchor || !isOrContainsFocus) {
                return;
            }

            selection = window.getSelection();

            // Get range (standards)
            if (selection.getRangeAt !== undefined) {
                range = selection.getRangeAt(0);

                // Get range (Safari 2)
            } else if (
                document.createRange &&
                selection.anchorNode &&
                selection.anchorOffset &&
                selection.focusNode &&
                selection.focusOffset
            ) {
                range = document.createRange();
                range.setStart(selection.anchorNode, selection.anchorOffset);
                range.setEnd(selection.focusNode, selection.focusOffset);
            } else {
                // Failure here, not handled by the rest of the script.
                // Probably IE or some older browser
            }
        };

        // Slight delay will avoid the initial selection
        // (at start or of contents depending on browser) being mistaken
        setTimeout(function() {
            var cursorStart = document.getElementById('cursorStart');
            var cursorEnd = document.getElementById('cursorEnd');

            // Don't do anything if user is creating a new selection
            if (editable.className.match(/\sselecting(\s|$)/)) {
                if (cursorStart) {
                    cursorStart.parentNode.removeChild(cursorStart);
                }
                if (cursorEnd) {
                    cursorEnd.parentNode.removeChild(cursorEnd);
                }
            } else if (cursorStart) {
                captureSelection();
                range = document.createRange();

                if (cursorEnd) {
                    range.setStartAfter(cursorStart);
                    range.setEndBefore(cursorEnd);

                    // Delete cursor markers
                    cursorStart.parentNode.removeChild(cursorStart);
                    cursorEnd.parentNode.removeChild(cursorEnd);

                    // Select range
                    selection.removeAllRanges();
                    selection.addRange(range);
                } else {
                    range.selectNode(cursorStart);

                    // Select range
                    selection.removeAllRanges();
                    selection.addRange(range);

                    // Delete cursor marker
                    document.execCommand('delete', false, null);
                }
            }

            // Register selection again
            captureSelection();
        }, 10);
    });
};

3

최신 브라우저에서 지원하는 selectNodeContents 를 활용할 수 있습니다 .

var el = document.getElementById('idOfYoursContentEditable');
var selection = window.getSelection();
var range = document.createRange();
selection.removeAllRanges();
range.selectNodeContents(el);
range.collapse(false);
selection.addRange(range);
el.focus();

최종 사용자가 캐럿을 원하는 위치로 계속 이동할 수 있도록이 코드를 수정할 수 있습니까?
Zabs

예. 범위 객체에서 setStart 및 setEnd 메소드를 사용해야합니다. developer.mozilla.org/en-US/docs/Web/API/Range/setStart
zoonman

0

Firefox에서는 하위 노드에 div 텍스트가있을 수 있습니다 ( o_div.childNodes[0])

var range = document.createRange();

range.setStart(o_div.childNodes[0],last_caret_pos);
range.setEnd(o_div.childNodes[0],last_caret_pos);
range.collapse(false);

var sel = window.getSelection(); 
sel.removeAllRanges();
sel.addRange(range);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.