草庐IT

javascript - 在 contenteditable 插入符位置占 `<br>` s

coder 2024-05-12 原文

为了获取和设置 contenteditable 元素中的插入符位置,我尝试了来自 this answer 的代码, 但当您移动到不同的文本节点时,开始和结束位置会重置。

<div contenteditable>012345<br><br><br>9012345</div>

因此,我修改了 this answer 中的代码(来自@TimDown)但它仍然不完全正确计算换行符......在this demo ,当我点击 4 之后然后按三下右箭头,我会看到开始/结束报告为 5 , 6 , 然后 8 .或者,使用鼠标从 4 中选择在第一行并继续向右选择(参见 gif)

这是代码( demo ;尽管看起来像,但 使用 jQuery)

function getCaret(el) {
  let start, end;
  const range = document.getSelection().getRangeAt(0),
    preSelectionRange = range.cloneRange(),
    postSelectionRange = range.cloneRange();
  preSelectionRange.selectNodeContents(el);
  preSelectionRange.setEnd(range.startContainer, range.startOffset);
  postSelectionRange.selectNodeContents(el);
  postSelectionRange.setEnd(range.endContainer, range.endOffset);
  start = preSelectionRange.toString().length;
  end = start + range.toString().length;
  // count <br>'s and adjust start & end
  if (start > 0) {
    var node,
      i = el.children.length;
    while (i--) {
      node = el.children[i];
      if (node.nodeType === 1 && node.nodeName === 'BR') {
        start += preSelectionRange.intersectsNode(el.children[i]) ? 1 : 0;
        end += postSelectionRange.intersectsNode(el.children[i]) ? 1 : 0;
      }
    }
  }
  return {start, end};
}

setCaret功能修改似乎工作正常(在这个基本的内容可编辑示例中)。

function setCaret(el, start, end) {
  var node, i, nextCharIndex, sel,
    charIndex = 0,
    nodeStack = [el],
    foundStart = false,
    stop = false,
    range = document.createRange();
  range.setStart(el, 0);
  range.collapse(true);
  while (!stop && (node = nodeStack.pop())) {
    // BR's aren't counted, so we need to increase the index when one
    // is encountered 
    if (node.nodeType === 1 && node.nodeName === 'BR') {
      charIndex++;
    } else if (node.nodeType === 3) {
      nextCharIndex = charIndex + node.length;
      if (!foundStart && start >= charIndex && start <= nextCharIndex) {
        range.setStart(node, start - charIndex);
        foundStart = true;
      }
      if (foundStart && end >= charIndex && end <= nextCharIndex) {
        range.setEnd(node, end - charIndex);
        stop = true;
      }
      charIndex = nextCharIndex;
    } else {
      i = node.childNodes.length;
      while (i--) {
        nodeStack.push(node.childNodes[i]);
      }
    }
  }
  sel = document.getSelection();
  sel.removeAllRanges();
  sel.addRange(range);
}

我可以在以下问题上寻求一些建议/帮助:

  • 如何正确计算 <br>是吗?
  • 你怎么算一个<br>在开头(在此 HTML 示例中)?

    <div contenteditable><br>12345<br><br><br>9012345</div>
    
  • 包括<br>包裹在 <div> 中(在这个 HTML 示例中)- 我最终会谈到这一点,但我不想继续沿着这条路走下去,然后发现有更简单的方法。

    <div contenteditable><div><br></div>12345<div><br></div><div><br></div><div><br></div>9012345</div>
    
  • 我尝试用 rangy 替换上面的代码,但它似乎没有获取或设置范围的内置方法。

最佳答案

我修改了您的演示,将位置序列化为容器/偏移量对,而不仅仅是一个位置。容器被序列化为一个简单的索引数组,进入每个节点的 childNodes 集合,从一个引用节点开始(当然,在本例中是 contenteditable 元素)。

我不完全清楚你打算用它做什么,但由于它反射(reflect)了选择模型,它应该会给你带来更少的痛苦。

const $el = $('ce'),
  $startContainer = $('start-container'),
  $startOffset = $('start-offset'),
  $endContainer = $('end-container'),
  $endOffset = $('end-offset');
  
function pathFromNode(node, reference) {
  function traverse(node, acc) {
    if (node === reference) {
      return acc;
    } else {
      const parent = node.parentNode;
      const index = [...parent.childNodes].indexOf(node);
      return traverse(parent, [index, ...acc]);
    }
  }
  return traverse(node, []);
}

function nodeFromPath(path, reference) {
  if (path.length === 0) {
    return reference;
  } else {
    const [index, ...rest] = path;
    const next = reference.childNodes[index];
    return nodeFromPath(rest, next);
  }
}

function getCaret(el) {
  const range = document.getSelection().getRangeAt(0);
  return {
    start: {
      container: pathFromNode(range.startContainer, el),
      offset: range.startOffset
    },
    end: {
      container: pathFromNode(range.endContainer, el),
      offset: range.endOffset
    }
  };
}

function setCaret(el, start, end) {
  const range = document.createRange();
  range.setStart(nodeFromPath(start.container, el), start.offset);
  range.setEnd(nodeFromPath(end.container, el), end.offset);
  sel = document.getSelection();
  sel.removeAllRanges();
  sel.addRange(range);
}

function update() {
  const pos = getCaret($el);
  $startContainer.value = JSON.stringify(pos.start.container);
  $startOffset.value = pos.start.offset;
  $endContainer.value = JSON.stringify(pos.end.container);
  $endOffset.value = pos.end.offset;
}

$el.addEventListener('keyup', update);
$el.addEventListener('click', update);

$('set').addEventListener('click', () => {
  const start = {
    container: JSON.parse($startContainer.value),
    offset: $startOffset.value
  };
  const end = {
    container: JSON.parse($endContainer.value),
    offset: $endOffset.value
  };
  setCaret($el, start, end);
});

function $(sel) {
  return document.getElementById(sel);
}
input {
  width: 40px;
}

[contenteditable] {
  white-space: pre;
}
(updates on click &amp; keyup)<br/>
<label>Start: <input id="start-container" type="text"/><input id="start-offset" type="number"/></label><br/>
<label>End: <input id="end-container" type="text"/><input id="end-offset" type="number"/></label><br/>
<button id="set">Set</button>
<p></p>
<!-- inline BR's behave differently from <br> on their own separate line
<div id="ce" contenteditable>012345<br><br><br>9012345</div>
-->

<!-- get/set caret needs to work with these examples as well
* <br> at beginning
  <div id="ce" contenteditable><br>12345<br><br><br>9012345</div>
* <br>'s wrapped in a <div>
-->
  <div id="ce" contenteditable><div><br></div>12345<div><br></div><div><br></div><div><br></div>9012345</div>

关于javascript - 在 contenteditable 插入符位置占 `<br>` s,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46434775/

有关javascript - 在 contenteditable 插入符位置占 `<br>` s的更多相关文章

  1. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  2. ruby-on-rails - rspec should have_select ('cars' , :options => ['volvo' , 'saab' ] 不工作 - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion在首页我有:汽车:VolvoSaabMercedesAudistatic_pages_spec.rb中的测试代码:it"shouldhavetherightselect"dovisithome_pathit{shouldhave_select('cars',:options=>['volvo','saab','mercedes','audi'])}end响应是rspec./spec/request

  3. ruby-on-rails - Nokogiri:使用 XPath 搜索 <div> - 2

    我使用Nokogiri(Rubygem)css搜索寻找某些在我的html里面。看起来Nokogiri的css搜索不喜欢正则表达式。我想切换到Nokogiri的xpath搜索,因为这似乎支持搜索字符串中的正则表达式。如何在xpath搜索中实现下面提到的(伪)css搜索?require'rubygems'require'nokogiri'value=Nokogiri::HTML.parse(ABBlaCD3"HTML_END#my_blockisgivenmy_bl="1"#my_eqcorrespondstothisregexmy_eq="\/[0-9]+\/"#FIXMEThefoll

  4. ruby - 正则表达式在哪个位置失败? - 2

    我需要一个非常简单的字符串验证器来显示第一个符号与所需格式不对应的位置。我想使用正则表达式,但在这种情况下,我必须找到与表达式相对应的字符串停止的位置,但我找不到可以做到这一点的方法。(这一定是一种相当简单的方法……也许没有?)例如,如果我有正则表达式:/^Q+E+R+$/带字符串:"QQQQEEE2ER"期望的结果应该是7 最佳答案 一个想法:你可以做的是标记你的模式并用可选的嵌套捕获组编写它:^(Q+(E+(R+($)?)?)?)?然后你只需要计算你获得的捕获组的数量就可以知道正则表达式引擎在模式中停止的位置,你可以确定匹配结束

  5. ruby-on-rails - 没有参数的 `<<`(小于两倍)是什么意思? - 2

    我在一个我想在formtasticGem中覆盖的方法中找到了这个。该方法如下所示:defto_htmlinput_wrappingdohidden_field_html是什么意思?在第三行做什么?我知道它对数组有什么作用,但在这里我不知道。 最佳答案 你可以这样读:hidden_field_htmllabel_with_nested_checkbox是连接到hidden_​​field_html末尾的参数-为了“清晰”,他们将其分成两行 关于ruby-on-rails-没有参数的`

  6. ruby-on-rails - 找不到 gem railties (>= 0.a) (Gem::GemNotFoundException) - 2

    我已经看到了一些其他的问题,尝试了他们的建议,但没有一个对我有用。我已经使用Rails大约一年了,刚刚开始一个新的Rails项目,突然遇到了问题。我卸载并尝试重新安装所有Ruby和Rails。Ruby很好,但Rails不行。当我输入railss时,我得到了can'tfindgemrailties。我当前的Ruby版本是ruby2.2.2p95(2015-04-13修订版50295)[x86_64-darwin15],尽管我一直在尝试通过rbenv设置ruby​​2.3.0。如果我尝试rails-v查看我正在运行的版本,我会得到同样的错误。我使用的是MacOSXElCapitan版本10

  7. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

    我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

  8. ruby-on-rails - 连接字符串时如何在 <%=%> block 内输出 html_safe? - 2

    考虑一下:现在这些情况:#output:http://domain.com/?foo=1&bar=2#output:http://domain.com/?foo=1&bar=2#output:http://domain.com/?foo=1&bar=2#output:http://domain.com/?foo=1&bar=2我需要用其他字符串输出URL。我如何保证&符号不会被转义?由于我无法控制的原因,我无法发送&。求助!把我的头发拉到这里:\编辑:为了澄清,我实际上有一个像这样的数组:@images=[{:id=>"fooid",:url=>"http://

  9. ruby - 如何在 Ruby 字符串中插入项目符号字符? - 2

    我正在尝试创建一个带有项目符号字符的Ruby1.9.3字符串。str="•"+"helloworld"但是,当我输入它时,我收到有关非ASCII字符的语法错误。我该怎么做? 最佳答案 你可以把Unicode字符放在那里。str="\u2022"+"helloworld" 关于ruby-如何在Ruby字符串中插入项目符号字符?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1195

  10. Ruby -> 写入二维数组 - 2

    我正在处理http://prepwork.appacademy.io/mini-curriculum/array/中概述的数组问题我正在尝试创建函数my_transpose,它接受一个矩阵并返回其转置。我对写入二维数组感到很困惑!这是一个代码片段,突出了我的困惑。rows=[[0,1,2],[3,4,5],[6,7,8]]columns=Array.new(3,Array.new(3))putscolumns.to_s#Outputisa3x3arrayfilledwithnilcolumns[0][0]=0putscolumns.to_s#Outputis[[0,nil,nil],[

随机推荐