ContentEditable <div> üzerinde imleç konumunu ayarla


146

Bir contentEditable = 'on' <div> odağı yeniden kazandığında, imleç / şapka konumunu bilinen son konuma ayarlamak için kesin, çapraz tarayıcı çözümünün peşindeyim. Görünüşe göre, içerik düzenlenebilir div işlevinin varsayılan işlevi, imleci / imleci, üzerine her tıkladığınızda div içindeki metnin başlangıcına taşımaktır, bu istenmeyen bir durumdur.

Div'in odak noktasından ayrılırken mevcut imleç konumunu bir değişkende saklamam gerektiğine inanıyorum ve sonra tekrar içeriye odaklandıklarında bunu yeniden ayarlamalıyım, ancak bir araya getiremedim veya bir çalışma henüz kod örneği.

Herhangi bir düşüncesi, çalışan kod parçacıkları veya örnekleri olan varsa, onları görmekten mutluluk duyarım.

Henüz gerçekten kodum yok ama işte elimde olan:

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

PS. Bu kaynağı denedim ama görünen o ki <div> için çalışmıyor. Belki sadece metin alanı için ( İmleci memnun edici varlığın sonuna nasıl taşırsınız )


contentEditableIE olmayan tarayıcılarda çalıştığını bilmiyordum o_o
aditya

10
Evet aditya yapıyor.
GONeale

5
aditya, Safari 2+, Firefox 3+ sanırım.
göz kapaksızlık

Div üzerinde tabindex = "0" ayarını deneyin. Bu, çoğu tarayıcıda odaklanabilir hale getirmelidir.
Tokimon

Yanıtlar:


58

Bu, standartlara dayalı tarayıcılarla uyumludur, ancak muhtemelen IE'de başarısız olacaktır. Bunu bir başlangıç ​​noktası olarak sağlıyorum. IE, DOM Aralığını desteklemez.

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

Teşekkürler göz, çözümünü denedim, biraz acelem vardı ama kablolamadan sonra, sadece "-" konumunu son odak noktasına yerleştiriyor (bu bir hata ayıklama işaretçisi gibi görünüyor?) Ve işte o zaman kaybettik odak, geri tıkladığımda imleci / imleci geri yüklüyor gibi görünmüyor (en azından Chrome'da değil, FF'yi deneyeceğim), sadece div'in sonuna gidiyor. Bu yüzden Nico'nun çözümünü kabul edeceğim çünkü tüm tarayıcılarda uyumlu olduğunu ve iyi çalışma eğiliminde olduğunu biliyorum. Yine de çabanız için çok teşekkürler.
GONeale

3
Ne olduğunu biliyor musun, son cevabımı unut, hem senin hem de Nico'yu daha ayrıntılı inceledikten sonra, senin açıklamamda istediğim şey değil, tercih ettiğim ve ihtiyacım olduğunu fark ettim. Sizinki , odağı yeniden etkinleştirirken tıkladığınız yerin konumunu normal bir metin kutusu gibi <div> 'e doğru şekilde ayarlar . Odağı son noktaya geri getirmek, kullanıcı dostu bir giriş alanı oluşturmak için yeterli değildir. Sana puanları vereceğim.
GONeale

9
Harika çalışıyor! İşte yukarıdaki çözümün bir jsfiddle: jsfiddle.net/s5xAr/3
vaughan

4
OP yetersiz kalmasına ve bir çerçeve kullanmak istemesine rağmen gerçek JavaScript yayınladığınız için teşekkür ederiz.
John

cursorStart.appendChild(document.createTextNode('\u0002'));makul bir yedek olduğunu düşünüyoruz. için - char. Kod için teşekkürler
twobob

97

Bu çözüm tüm büyük tarayıcılarda çalışır:

saveSelection()div'in onmouseupve onkeyupolaylarına eklenir ve seçimi değişkene kaydeder savedRange.

restoreSelection()onfocusdiv olayına eklenir ve kaydedilen seçimi yeniden seçer savedRange.

Bu, kullanıcı div aswell'i tıkladığında seçimin geri yüklenmesini istemediğiniz sürece mükemmel şekilde çalışır (bu, normalde imlecin tıkladığınız yere gitmesini beklediğiniz gibi biraz mantıksızdır, ancak eksiksizlik için kod dahil edilmiştir)

Bunu başarmak için onclickve onmousedownolayları cancelEvent(), olayı iptal etmek için çapraz tarayıcı işlevi olan işlev tarafından iptal edilir . cancelEvent()Fonksiyon aynı zamanda çalışan restoreSelection()tıklama etkinlik iptal edilir olarak div odağı almaz ve bu fonksiyonlar çalıştırılır sürece bu nedenle hiçbir şey hiç seçilir çünkü fonksiyonu.

Değişken isInFocus, odakta olup olmadığını saklar ve "yanlış" onblurve "doğru" olarak değiştirilir onfocus. Bu, tıklama etkinliklerinin yalnızca div odakta değilse iptal edilmesini sağlar (aksi takdirde seçimi hiç değiştiremezsiniz).

Seçimi geri div bir tıklama ile odaklandığında değişiklik olmaması ve seçimdeki isterseniz onclick(ve odak elemanına verilen olduğunda programtically kullanarak document.getElementById("area").focus();veya benzeri daha sonra basitçe kaldırmak onclickve onmousedownolayları. onblurOlayını ve onDivBlur()ve cancelEvent()işlevleri bu durumlarda da güvenle kaldırılabilir.

Bu kod, hızlı bir şekilde test etmek istiyorsanız, doğrudan bir html sayfasının gövdesine bırakıldığında çalışmalıdır:

<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
Teşekkür ederim bu gerçekten işe yarıyor! En son IE, Chrome ve FF'de test edilmiştir. Süper gecikmiş yanıt için özür dilerim =)
GONeale

Bir seçim olup olmadığını değil, if (window.getSelection)...sadece tarayıcının destekleyip desteklemediğini test getSelectionetmeyecek mi?
Sandy Gifford

@Sandy Evet aynen. Kodun bu kısmı, IE'nin eski sürümleri tarafından kullanılan standart getSelectionapi'nin mi yoksa eski document.selectionapi'nin mi kullanılacağına karar verir . Geri yükleme işlevinde kontrol edilen seçim yoksa sonraki getRangeAt (0)arama geri dönecektir null.
Nico Burns

1
@NicoBurns doğru, ancak ikinci koşullu bloktaki kod ( else if (document.createRange)) baktığım şey. Yalnızca window.getSelectionyoksa çağrılacak , ancak kullanılıyorwindow.getSelection
Sandy Gifford

1
@NicoBurns ayrıca, bir tarayıcı bulacağınızı window.getSelectionancak bulamayacağınızı sanmıyorum document.createRange- yani ikinci blok asla kullanılmayacak ...
Sandy Gifford

19

Güncelleme

Aşağıda yayınladığım kodun geliştirilmiş bir sürümünü içeren Rangy adında bir çapraz tarayıcı aralığı ve seçim kitaplığı yazdım . Sen kullanabilirsiniz tasarrufu seçimi ve modül geri ben gibi kullanım şey için cazip olacaktır ancak bu özel soru için @Nico Burns'ün cevap projenizde seçimlerle başka bir şey yapmıyoruz ancak ve a toplu gerekmez kütüphane.

Önceki cevap

IE'nin TextRange'ini DOM Aralığı gibi bir şeye dönüştürmek için IERange'ı ( http://code.google.com/p/ierange/ ) kullanabilir ve bunu göz kapağının başlangıç ​​noktası gibi bir şeyle birlikte kullanabilirsiniz. Şahsen ben her şeyi kullanmak yerine Range <-> TextRange dönüşümlerini yapan IERange algoritmalarını kullanırdım. Ve IE'nin seçim nesnesi focusNode ve anchorNode özelliklerine sahip değildir, ancak bunun yerine sadece seçimden elde edilen Range / TextRange'i kullanabilmelisiniz.

Bunu yapmak için bir şeyler bir araya getirebilirim, ne zaman ve ne zaman yaparsam buraya geri göndereceğim

DÜZENLE:

Bunu yapan bir komut dosyası demosu oluşturdum. Henüz araştırmak için zamanım olmayan Opera 9'daki bir hata dışında şimdiye kadar denediğim her şeyde çalışıyor. Çalıştığı tarayıcılar, tümü Windows üzerinde IE 5.5, 6 ve 7, Chrome 2, Firefox 2, 3 ve 3.5 ve Safari 4'tür.

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

Tarayıcılarda seçimlerin geriye doğru yapılabileceğini ve böylece odak düğümünün seçimin başlangıcında olduğunu ve sağ veya sol imleç tuşuna basmanın imleci seçimin başlangıcına göre bir konuma taşıyacağını unutmayın. Bir seçimi geri yüklerken bunu tekrarlamanın mümkün olduğunu sanmıyorum, bu nedenle odak düğümü her zaman seçimin sonunda.

Bunu yakında bir noktada tam olarak yazacağım.


15

Bununla ilgili bir durum yaşadım, özellikle imleç konumunu tatmin edici bir div'in SONuna ayarlamam gerekiyordu. Rangy gibi tam teşekküllü bir kitaplık kullanmak istemedim ve birçok çözüm çok ağırdı.

Sonunda, karat konumunu tatmin edici bir div'in sonuna ayarlamak için bu basit jQuery işlevini buldum:

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

Teori basittir: Düzenlenebilir dosyanın sonuna bir aralık ekleyin, onu seçin ve ardından aralığı kaldırın - bizi div'in sonunda bir imleçle bırakarak. Bu çözümü, aralığı istediğiniz yere yerleştirecek ve böylece imleci belirli bir noktaya getirecek şekilde uyarlayabilirsiniz.

Kullanımı basittir:

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

Bu kadar!


3
<span>Tarayıcının yerleşik geri alma yığınını tesadüfen bozacak şekilde eklemenize gerek yoktur . Bkz stackoverflow.com/a/4238971/96100
Tim Aşağı

6

Nico Burns'ün cevabını aldım ve jQuery kullanarak yaptım:

  • Genel: Her biri için div contentEditable="true"
  • Daha kısa

JQuery 1.6 veya üstüne ihtiyacınız olacak:

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 Güncellemenin geç olduğunu biliyorum ama şimdi işe yaradığını düşünüyorum. Temel olarak, yeni bir koşul ekledim ve öğenin kimliğini kullanmaktan öğenin dizinine
geçtim, ki bu

4

Etrafta oynadıktan sonra, yukarıdaki eyelidlessness'ın cevabını değiştirdim ve bunu bir jQuery eklentisi yaptım, böylece şunlardan birini yapabilirsiniz:

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

Uzun kod gönderisini affedin, ancak birine yardımcı olabilir:

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

Modern tarayıcılar tarafından desteklenen selectNodeContents'ten yararlanabilirsiniz .

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

Son kullanıcının imleci istediği herhangi bir konuma taşımasına izin vermek için bu kodu değiştirmek mümkün müdür?
Zabs

Evet. Range nesnesinde setStart & setEnd yöntemlerini kullanmalısınız. developer.mozilla.org/en-US/docs/Web/API/Range/setStart
zoonman

0

Firefox'ta div metnine bir alt düğümde ( o_div.childNodes[0]) sahip olabilirsiniz

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);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.