使多个contenteditable表现得像一个文档

时间:2016-07-16 14:32:05

标签: javascript html wysiwyg contenteditable

我有一堆垂直排列的多行可信div,我想通过箭头键允许它们之间的自然导航(好像它是一个文档)。为此,在keydown事件中,我需要:

  • 了解插入符号的当前行行数,以确定是否需要向上移动(按下第一行和↑键)或down (最后一行和↓键)
  • 知道当前字符(显示字符串中的位置)以确定是否需要向上移动(按下位置== 0和←键)或向下 (position == text.length and→press)
  • 当关键被保持和未被释放时,切换元素之间的过程不应该停止(因此keydown事件,而不是keyup
  • 优选:事件应该停止传播(例如,如果我在最后一行的第一列上,我按↓键,它不应该跳到该行的最后一个字符然后向下)< / LI>
  • 最好(真的很棒):在我们跳到下一个元素之后,我们不仅仅.focus()元素,而是模仿与我们之前相同的垂直位置的点击,这样它就会感觉自然,就像文字编辑一样。

我迄今发现的所有脚本/库都没有完成我需要的所有东西或者错误。请在您的建议中包含演示,以便我可以先测试而不在我的代码中进行测试。谢谢!

更新: 视觉说明 - 请注意最后一行有超过2个div和&#39;向下箭头键&#39;只是四个触发器中的一个

enter image description here

2 个答案:

答案 0 :(得分:3)

我已经编写了一些代码,但它根本没有完成......也许你可以从那开始并尝试完成我已经完成的工作,如果你想;)我会继续工作本周为了给你提供一个解决方案...这是我迄今为止所做的:

&#13;
&#13;
var ajaxResult = [
  "Inter has ruinarum varietates a Nisibi quam tuebatur accitus Vrsicinus, cui nos obsecuturos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur. Inter has ruinarum varietates a Nisibi quam tuebatur accitus Vrsicinus, cui nos obsecuturos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur. Inter has ruinarum varietates  exitialis certamina cogebatur",
  "Inter has ruinarum varietates a Nisibi quam tuebatur accitus",
  "Inter has ruinarum varietates a Nisibi quam tuebatur accitus Vrsicinus, cui nos obsecuturos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur. Inter has ruinarum varietates a Nisibi quamos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur. Inter has ruinarum varietates  exitialis certamina cogebatur",
];
/*************************************************************
* 
*	LIST OF CONTENT EDITABLE DIVS MANAGEMENT
*
**************************************************************/
// Create the editable divs
window.onload = function(){
  var contentEditables = createContentEditables();
  document.body.appendChild(contentEditables);
}

// Remember all the content editable elements in the order they appear in the dom
var _currentEdit,
	_edits = [];
	
function createContentEditables(){
  var div;
  var result = document.createDocumentFragment();
  for (var i = 0, n = ajaxResult.length ; i < n ; i++){
    div = createContentEditable(ajaxResult[i]);
    _edits.push(div);
    result.appendChild(div);
  }

  return result;
}

function getPreviousEdit(edit){
  // Search for the edit index
  var index = _edits.indexOf(edit);

  if(index == 0)
    return;

  // Return the previous one
  return _edits[index - 1];
}

function getNextEdit(edit){
  // Search for the edit index
  var index = _edits.indexOf(edit);

  if(index == _edits.length - 1)
    return;

  // Return the previous one
  return _edits[index + 1];
}

/*************************************************************
* 
*	CONTENT EDITABLE MANAGEMENT
*
**************************************************************/
// We need to define the line height of the div to be able to retrieve the number of lines
var LINE_HEIGHT = 16;

// variables to keep trace of relevant information about the div
var _lines, _caretPosition;

/*
* Create a div with contenteditable set to true with the text
* received from the server
*/
function createContentEditable(text){
  var element =  document.createElement('div');
  element.className = 'contenteditable';
  element.innerHTML = text;
  element.style.lineHeight = LINE_HEIGHT + 'px';
  element.setAttribute('contenteditable', true);

  // Set listeners
  element.addEventListener('mouseup', onEdit_mouseup);
  element.addEventListener('keydown', onEdit_keydown);
  element.addEventListener('focus', onEdit_focus);

  return element;
}

function onEdit_keydown(domEvent){
  // Update caret position
  _caretPosition = getCaretPosition(domEvent.target);
  switch(domEvent.keyCode){
    case 37: // left arrow
      if (_caretPosition.index == 0){
        var previousEdit = getPreviousEdit(domEvent.target);
        if(previousEdit){
          console.log("go to end of previous edit");
          console.log(previousEdit);
          previousEdit.focus();
        }
      }
      break;
    case 38: // up arrow
      if (_caretPosition.line == 1){
        var previousEdit = getPreviousEdit(domEvent.target);
        if(previousEdit){
          console.log("go to previous edit keeping the caret offset");
          console.log(previousEdit);
          previousEdit.focus();
        }
      }
      break;
    case 39: // right arrow
      if (_caretPosition.index == domEvent.target.innerHTML.length){
        var nextEdit = getNextEdit(domEvent.target);
        if(nextEdit){
          console.log("go to beginning of next edit");
          console.log(nextEdit);
          nextEdit.focus();
        }
      }
      break;
    case 40: // down arrow
      if (_caretPosition.line == getLines(domEvent.target)){
        var nextEdit = getNextEdit(domEvent.target);
        if(nextEdit){
          console.log("go to next edit keeping the caret offset");
          console.log(nextEdit);
          nextEdit.focus();
        }
      }
      break;
  }
}

function onEdit_mouseup(domEvent){
  // Update caret position
  _caretPosition = getCaretPosition(domEvent.target);
}

function onEdit_focus(domEvent){
  // Add listeners
  _currentEdit = domEvent.target;
  _currentEdit.addEventListener('blur', onEdit_blur);
  window.addEventListener('resize', onWindow_resize);
}

function onEdit_blur(domEvent){
  // Remove listeners
  domEvent.target.removeEventListener('blur', onEdit_blur);
  window.removeEventListener('resize', onWindow_resize);
}

function onWindow_resize(domEvent){
  // Update caret position
  _caretPosition = getCaretPosition(_currentEdit);
}

/*************************************************************
* 
*	HELPERS
*
**************************************************************/
//http://stackoverflow.com/questions/4811822/get-a-ranges-start-and-end-offsets-relative-to-its-parent-container/4812022#4812022
//http://stackoverflow.com/questions/5528004/how-to-get-number-of-rows-in-contenteditable-area-and-current-caret-line-positio
function getCaretPosition(element){
  var caretPosition = {index: 0, line: 0};
  var doc = element.ownerDocument || element.document;
  var win = doc.defaultView || doc.parentWindow;
  var elemOffsetTop = element.offsetTop;
  var sel;
  // Get the x position of the caret
  if (typeof win.getSelection != "undefined") {
    sel = win.getSelection();
    if (sel.rangeCount > 0) {
      var range = win.getSelection().getRangeAt(0);
      // Retrieve the current line
      var rects = range.getClientRects();
      var caretOffsetTop;
      if (typeof rects[1] != "undefined"){
        caretOffsetTop = rects[1].top;
      }
      else if (typeof rects[0] != "undefined"){
        caretOffsetTop = rects[0].top;
      }
      else{
        // Create dummy element to get y position of the caret
        var dummy = document.createElement('CANVAS');
        dummy.id = 'findCaretHelper';
        range.insertNode(dummy);
        caretOffsetTop = dummy.offsetTop;
        element.removeChild(dummy);
      }

      var preCaretRange = range.cloneRange();
      preCaretRange.selectNodeContents(element);
      preCaretRange.setEnd(range.endContainer, range.endOffset);

      // Remember caret position
      caretPosition.index = preCaretRange.toString().length;
      caretPosition.line = Math.ceil((caretOffsetTop - elemOffsetTop)/LINE_HEIGHT) + 1;
    }
  } 
  // support ie
  //else if ( (sel = doc.selection) && sel.type != "Control") {
  //var textRange = sel.createRange();
  //var preCaretTextRange = doc.body.createTextRange();
  //preCaretTextRange.moveToElementText(element);
  //preCaretTextRange.setEndPoint("EndToEnd", textRange);
  //caretPosition.x = preCaretTextRange.text.length;
  //}

  return caretPosition;
}

function getLines(element){	
  return element.clientHeight/LINE_HEIGHT;;
}
&#13;
.contenteditable{
  border: solid 1px #aaa;
  margin: 10px 0;
}
&#13;
&#13;
&#13;

我设法获取有关当前行的信息,内容可编辑div中的当前字符索引以及其他一些内容......我仍然需要关注其他内容可编辑div,以便将插入符号放在正确的位置......我希望这个解决方案的开头能帮到你!

答案 1 :(得分:0)

您可以简单地创建父元素/包含元素contenteditable而不是每个段落。这会相应地自动添加/删除p标记。

https://jsfiddle.net/ewesmwmv/2/