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>
PS。このリソースを試しましたが、<div>では機能しないようです。おそらくtextareaのみ( カーソルをcontenteditableエンティティの最後に移動する方法 )
これは標準ベースのブラウザと互換性がありますが、おそらく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);
};
このソリューションはすべての主要なブラウザーで動作します:
saveSelection()
はdivのonmouseup
およびonkeyup
イベントに付加され、選択を変数savedRange
に保存します。
restoreSelection()
はdivのonfocus
イベントに添付され、savedRange
に保存された選択を再選択します。
これは、ユーザーがdivをクリックしたときに選択範囲を復元する場合を除き、完全に機能します(通常、カーソルはクリックした場所に移動すると予想されますが、完全性のためにコードが含まれています)
これを実現するために、onclick
およびonmousedown
イベントは、イベントをキャンセルするクロスブラウザー関数である関数cancelEvent()
によってキャンセルされます。 cancelEvent()
関数もrestoreSelection()
関数を実行します。クリックイベントがキャンセルされると、divがフォーカスを受け取らず、この関数が実行されない限り何も選択されないためです。
変数isInFocus
は、フォーカスがあるかどうかを格納し、「false」onblur
および「true」onfocus
に変更されます。これにより、divにフォーカスがない場合にのみクリックイベントをキャンセルできます(そうしないと、選択をまったく変更できません)。
Divがクリックによってフォーカスされたときに選択を変更したい場合、選択を復元しないonclick
(およびdocument.getElementById("area").focus();
などを使用してプログラムで要素にフォーカスが与えられた場合のみ) onclick
およびonmousedown
イベント。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>
更新
Rangy と呼ばれるクロスブラウザー範囲および選択ライブラリーを作成しました。これには、以下に投稿するコードの改良バージョンが組み込まれています。この特定の質問には 選択保存および復元モジュール を使用できますが、他に何もしていない場合は @ Nico Burnsの答え のようなものを使用したいと思いますがプロジェクト内の選択を使用して、ライブラリの大部分を必要としません。
前の回答
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/
フォーカスノードが選択範囲の先頭にあり、右または左のカーソルキーを押すと、選択範囲の開始位置に相対的な位置にキャレットが移動するように、ブラウザーで選択を逆方向に行うことができます。選択を復元するときにこれを複製することはできないと思うので、フォーカスノードは常に選択の最後にあります。
これについては、近いうちに完全に説明します。
関連する状況があり、特にカーソル位置をcontenteditable divのENDに設定する必要がありました。 Rangyのような本格的なライブラリを使用したくはありませんでした。多くのソリューションは非常に重量がありました。
最後に、この単純なjQuery関数を思いついて、コンテンツの編集可能なdivの最後にカラットの位置を設定しました。
$.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();
それでおしまい!
ニコバーンズの答えを取り、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();
}
});
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();
}
});
div[contenteditable] {
padding: 1em;
font-family: Arial;
outline: 1px solid rgba(0,0,0,0.5);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contentEditable="true"></div>
<div contentEditable="true"></div>
<div contentEditable="true"></div>
遊んだ後、上記のまぶたのない答えを修正し、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);
});
};
最新のブラウザでサポートされている 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();
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);