Đặt vị trí con trỏ trên nội dung Có thể chỉnh sửa <div>


142

Tôi đang theo một giải pháp trình duyệt chéo, dứt khoát để đặt vị trí con trỏ / dấu mũ đến vị trí đã biết cuối cùng khi contentEditable = 'on' <div> lấy lại tiêu điểm. Nó xuất hiện chức năng mặc định của div có thể chỉnh sửa nội dung là di chuyển dấu mũ / con trỏ đến đầu văn bản trong div mỗi khi bạn nhấp vào nó, điều này là không mong muốn.

Tôi tin rằng tôi sẽ phải lưu trữ ở một biến vị trí con trỏ hiện tại khi chúng rời khỏi tiêu điểm của div và sau đó đặt lại vị trí này khi chúng tập trung vào bên trong một lần nữa, nhưng tôi không thể kết hợp hoặc tìm cách làm việc mẫu mã chưa.

Nếu bất cứ ai có bất kỳ suy nghĩ, đoạn mã làm việc hoặc mẫu tôi sẽ rất vui khi thấy chúng.

Tôi chưa thực sự có bất kỳ mã nào nhưng đây là những gì tôi có:

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

Tái bút Tôi đã thử tài nguyên này nhưng có vẻ như nó không hoạt động cho <div>. Có lẽ chỉ dành cho textarea ( Cách di chuyển con trỏ đến cuối thực thể có thể nội dung )


Tôi không biết contentEditableđã làm việc trong các trình duyệt không phải IE o_o
aditya

10
Có nó aditya.
GONeale

5
Tôi nghĩ rằng aditya, Safari 2+, Firefox 3+.
mí mắt

Hãy thử Đặt tabindex = "0" trên div. Điều đó sẽ làm cho nó có thể tập trung trong hầu hết các trình duyệt.
Tokimon

Câu trả lời:


58

Điều này tương thích với các trình duyệt dựa trên tiêu chuẩn, nhưng có thể sẽ thất bại trong IE. Tôi đang cung cấp nó như là một điểm khởi đầu. IE không hỗ trợ Phạm vi 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);
};

Cảm ơn, tôi đã thử giải pháp của bạn, tôi hơi vội vàng nhưng sau khi kết nối nó, nó chỉ đặt vị trí "-" ở điểm lấy nét cuối cùng (có vẻ là điểm gỡ lỗi?) Và đó là khi chúng tôi mất tập trung, nó dường như không khôi phục con trỏ / dấu mũ khi tôi nhấp lại (ít nhất là không có trong Chrome, tôi sẽ thử FF), nó chỉ đi đến cuối div. Vì vậy, tôi sẽ chấp nhận giải pháp của Nico vì tôi biết nó tương thích trong tất cả các trình duyệt và có xu hướng hoạt động tốt. Cảm ơn rất nhiều cho nỗ lực của bạn mặc dù.
GONeale

3
Bạn có biết không, quên câu trả lời cuối cùng của tôi, sau khi kiểm tra thêm cả của bạn và của bạn, của bạn không phải là những gì tôi yêu cầu trong mô tả của tôi, nhưng là những gì tôi thích và sẽ nhận ra tôi cần. Bạn đặt chính xác vị trí của con trỏ nơi bạn nhấp khi kích hoạt tiêu điểm trở lại <div>, giống như một hộp văn bản thông thường. Khôi phục trọng tâm đến điểm cuối cùng là không đủ để tạo trường nhập thân thiện với người dùng. Tôi sẽ trao cho bạn điểm.
GONeale

9
Hoạt động tuyệt vời! Đây là một jsfiddle của giải pháp trên: jsfiddle.net/s5xAr/3
vaughan

4
Cảm ơn bạn đã đăng JavaScript thực sự mặc dù OP đã bỏ qua và muốn sử dụng một khung công tác.
Giăng

cursorStart.appendChild(document.createTextNode('\u0002'));là một sự thay thế hợp lý, chúng tôi nghĩ. cho - char. Cảm ơn mã
Twobob

97

Giải pháp này hoạt động trong tất cả các trình duyệt chính:

saveSelection()được gắn vào onmouseuponkeyupcác sự kiện của div và lưu lựa chọn vào biến savedRange.

restoreSelection()được đính kèm với onfocussự kiện của div và chọn lại lựa chọn được lưu trong savedRange.

Điều này hoạt động hoàn hảo trừ khi bạn muốn khôi phục lựa chọn khi người dùng nhấp vào div aswell (điều này hơi vô tình như bình thường bạn mong muốn con trỏ đi đến nơi bạn nhấp nhưng bao gồm mã để hoàn thành)

Để đạt được điều này onclickonmousedowncác sự kiện bị hủy bởi chức năng cancelEvent()là chức năng trình duyệt chéo để hủy sự kiện. Các cancelEvent()chức năng cũng chạy restoreSelection()chức năng bởi vì khi sự kiện click bị hủy div không nhận được tập trung và do đó không có gì được chọn ở tất cả trừ khi chức năng này được chạy.

Biến isInFocuslưu trữ cho dù nó nằm trong tiêu điểm và được thay đổi thành "false" onblurvà "true" onfocus. Điều này cho phép các sự kiện nhấp chỉ bị hủy nếu div không tập trung (nếu không bạn hoàn toàn không thể thay đổi lựa chọn).

Nếu bạn muốn lựa chọn được thay đổi khi div được tập trung bởi một nhấp chuột, và không khôi phục lại lựa chọn onclick(và chỉ khi tập trung được trao cho các phần tử programtically sử dụng document.getElementById("area").focus();hoặc tương tự sau đó chỉ cần loại bỏ các onclickonmousedowncác sự kiện. Các onblursự kiện và onDivBlur()cancelEvent() chức năng cũng có thể được gỡ bỏ một cách an toàn trong những trường hợp này.

Mã này sẽ hoạt động nếu được thả trực tiếp vào phần thân của trang html nếu bạn muốn kiểm tra nhanh:

<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
Cảm ơn bạn điều này thực sự hoạt động! Đã thử nghiệm trên IE, Chrome và FF mới nhất. Xin lỗi về trả lời siêu chậm =)
GONeale

Sẽ không if (window.getSelection)...chỉ kiểm tra xem trình duyệt có hỗ trợ getSelectionhay không, có hay không có lựa chọn?
Sandy Gifford

@Sandy Có chính xác. Phần mã này đang quyết định nên sử dụng getSelectionapi tiêu chuẩn hay document.selectionapi kế thừa được sử dụng bởi các phiên bản IE cũ hơn. Cuộc getRangeAt (0)gọi sau sẽ trả về nullnếu không có lựa chọn nào được kiểm tra trong chức năng khôi phục.
Nico Burns

@NicoBurns đúng, nhưng mã trong khối điều kiện thứ hai ( else if (document.createRange)) là những gì tôi đang xem. Nó sẽ chỉ được gọi nếu window.getSelectionkhông tồn tại, nhưng vẫn sử dụngwindow.getSelection
Sandy Gifford

@NicoBurns hơn nữa, tôi không nghĩ rằng bạn sẽ tìm thấy một trình duyệt window.getSelectionnhưng không document.createRange- có nghĩa là khối thứ hai sẽ không bao giờ được sử dụng ...
Sandy Gifford

19

Cập nhật

Tôi đã viết một phạm vi trình duyệt chéo và thư viện lựa chọn có tên Rangy kết hợp phiên bản cải tiến của mã tôi đã đăng bên dưới. Bạn có thể sử dụng mô-đun lưu và khôi phục lựa chọn cho câu hỏi cụ thể này, mặc dù tôi rất muốn sử dụng một cái gì đó như câu trả lời của @Nico Burns như nếu bạn không làm gì khác với các lựa chọn trong dự án của mình và không cần hàng loạt thư viện.

Câu trả lời trước

Bạn có thể sử dụng IERange ( http://code.google.com.vn/p/ierange/ ) để chuyển đổi TextRange của IE thành một cái gì đó giống như một Phạm vi DOM và sử dụng nó cùng với điểm bắt đầu của mí mắt. Cá nhân tôi sẽ chỉ sử dụng các thuật toán từ IERange thực hiện các chuyển đổi Phạm vi <-> TextRange chứ không sử dụng toàn bộ. Và đối tượng lựa chọn của IE không có thuộc tính FocusNode và anchorNode nhưng bạn chỉ có thể sử dụng Range / TextRange thu được từ lựa chọn.

Tôi có thể đặt một cái gì đó cùng nhau để làm điều này, sẽ gửi lại ở đây nếu và khi tôi làm.

BIÊN TẬP:

Tôi đã tạo một bản demo của một kịch bản thực hiện điều này. Nó hoạt động trong tất cả mọi thứ tôi đã thử nó cho đến nay ngoại trừ một lỗi trong Opera 9, mà tôi chưa có thời gian để xem xét. Các trình duyệt mà nó hoạt động là IE 5.5, 6 và 7, Chrome 2, Firefox 2, 3 và 3.5 và Safari 4, tất cả đều có trên Windows.

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

Lưu ý rằng các lựa chọn có thể được thực hiện ngược trong các trình duyệt để nút tiêu điểm nằm ở đầu lựa chọn và nhấn phím con trỏ phải hoặc trái sẽ di chuyển dấu mũ đến vị trí so với điểm bắt đầu của lựa chọn. Tôi không nghĩ có thể sao chép điều này khi khôi phục lựa chọn, vì vậy nút tiêu điểm luôn ở cuối lựa chọn.

Tôi sẽ viết điều này lên đầy đủ tại một số điểm sớm.


15

Tôi đã có một tình huống liên quan, trong đó tôi đặc biệt cần thiết để đặt vị trí con trỏ thành KẾT THÚC của một div có thể nội dung. Tôi không muốn sử dụng một thư viện chính thức như Rangy, và nhiều giải pháp quá nặng nề.

Cuối cùng, tôi đã nghĩ ra hàm jQuery đơn giản này để đặt vị trí carat ở cuối div có thể nội dung:

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

Lý thuyết rất đơn giản: nối một nhịp vào cuối phần có thể chỉnh sửa, chọn nó và sau đó xóa khoảng đó - để lại cho chúng ta một con trỏ ở cuối div. Bạn có thể điều chỉnh giải pháp này để chèn nhịp bất cứ nơi nào bạn muốn, do đó đặt con trỏ vào một vị trí cụ thể.

Cách sử dụng rất đơn giản:

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

Đó là nó!


3
Bạn không cần phải chèn <span>, điều này sẽ vô tình phá vỡ ngăn xếp hoàn tác tích hợp của trình duyệt. Xem stackoverflow.com/a/4238971/96100
Tim Down

6

Tôi lấy câu trả lời của Nico Burns và thực hiện bằng jQuery:

  • Chung: Dành cho mọi div contentEditable="true"
  • Ngắn hơn

Bạn sẽ cần jQuery 1.6 trở lên:

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 Tôi biết đã muộn để cập nhật nó, nhưng tôi nghĩ nó hoạt động ngay bây giờ. Về cơ bản tôi đã thêm một điều kiện mới và thay đổi từ việc sử dụng id của phần tử thành chỉ mục của phần tử, sẽ luôn tồn tại :)
Gatsbimantico

4

Sau khi chơi xung quanh tôi đã sửa đổi câu trả lời mí mắt 'ở trên và biến nó thành một plugin jQuery để bạn có thể thực hiện một trong những điều sau:

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

Xin lỗi, bài viết mã dài, nhưng nó có thể giúp ai đó:

$.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

Bạn có thể tận dụng selectNodeContents được hỗ trợ bởi các trình duyệt hiện đại.

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

Có thể sửa đổi mã này để cho phép người dùng cuối vẫn có thể di chuyển dấu mũ đến bất kỳ vị trí nào họ muốn không?
Zabs

Đúng. Bạn nên sử dụng các phương thức setStart & setEnd trên đối tượng phạm vi. developer.mozilla.org/en-US/docs/Web/API/Range/setStart
zoonman

0

Trong Firefox, bạn có thể có văn bản của div trong một nút con ( 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);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.