【问题标题】:Justification in contentEditable div which each letter have own element每个字母都有自己的元素的 contentEditable div 中的理由
【发布时间】:2017-09-27 06:18:40
【问题描述】:

我有 contentEditable div 每个字母都有自己的元素,例如:

<div contenteditable="true">
    <p id="p1">
        <span id="s1">S</span>
        <span id="s2">o</span>
        <span id="s3">m</span>
        <span id="s4">e</span>
        <span id="s5">&nbsp;</span>
        <span id="s6">t</span>
        <span id="s7">e</span>
        <span id="s8">x</span>
        <span id="s9">t</span>
    </p>
</div>

我正在尝试使用text-align: justify 来证明一些较长的文本是合理的,但这不起作用。这很奇怪,因为 text-align: centertext-align: right 有效。

之后,我尝试使用将margin-right 添加到每个空格的脚本来执行此操作,但是当我将新文本写入段落时它会崩溃。

我怎样才能使用 JavaScript 和/或 JQuery 做到这一点(并在每个元素中保存 id 和其他属性)?

【问题讨论】:

  • 你想达到什么目的?字母应该填满整个宽度吗?
  • Fiddle 会很有帮助...请提供一个以便我们提供帮助
  • 我猜你希望文本表现得像普通文本,即使你已经完成了所有的跨度。因此,当您使用长文本进行证明时,您希望句子在单词之间中断而不是像我现在那样将单词分开?
  • 这个问题需要澄清。因为很难知道问题或目标是什么。
  • 为什么 OP 设置了赏金然后忽略了每个响应,有许多好的答案,最重要的是我们甚至没有所有必需的信息

标签: javascript jquery html css


【解决方案1】:

这是一个 JS + jQuery 方法的示例,用于模拟 contenteditable 元素内的 spans 中包裹的字符的对齐方式。这只是概念的快速证明,仅在 Chrome 中进行了测试,而不是彻底。此示例中未添加删除/撤消/复制/粘贴等功能。

var selectors = {
  'wrapper': '#editable',
  'paragraphs': '#editable > p',
  'spans': '#editable > p > span'
}

function get_cursor_position(element) {
  var caretOffset = 0;
  var doc = element.ownerDocument || element.document;
  var win = doc.defaultView || doc.parentWindow;
  var sel;
  if (typeof win.getSelection != "undefined") {
    sel = win.getSelection();
    if (sel.rangeCount > 0) {
      var range = win.getSelection().getRangeAt(0);
      var preCaretRange = range.cloneRange();
      preCaretRange.selectNodeContents(element);
      preCaretRange.setEnd(range.endContainer, range.endOffset);
      caretOffset = preCaretRange.toString().length;
    }
  } else if ((sel = doc.selection) && sel.type != "Control") {
    var textRange = sel.createRange();
    var preCaretTextRange = doc.body.createTextRange();
    preCaretTextRange.moveToElementText(element);
    preCaretTextRange.setEndPoint("EndToEnd", textRange);
    caretOffset = preCaretTextRange.text.length;
  }
  return caretOffset;
}


function set_cursor_position(pos) {
  if (document.selection) {
    sel = document.selection.createRange();
    sel.moveStart('character', pos);
    sel.select();
  } else {
    sel = window.getSelection();
    sel.collapse($(selectors['spans'])[pos].firstChild, 0);
  }
}

function set_cursor_position_to_element(el) {
  if (document.selection) {
    sel = document.selection.createRange();
    sel.moveStart('character', pos);
    sel.select();
  } else {
    sel = window.getSelection();
    sel.collapse(el, 0);
  }
}


function justify(wrapper_selector, children_selector) {
  var line_width = 0,
    first_line_char = 0,
    wrapper = $(wrapper_selector),
    wrapper_width = wrapper.width(),
    children = $(children_selector),
    position_of_last_space_found,
    filled_line_width_at_last_space_found;

  // refresh
  children.removeAttr("padding-right").removeClass("spaced first-line-char last-line-space");

  for (var space_positions = [], l = children.length, child_i = 0; child_i < l; child_i++) {
    child_e = children.eq(child_i);
    line_width += $(child_e).width();
    first_line_char += 1;
    if (/\s/g.test($(child_e).text())) {
      space_positions.push(child_i);
      position_of_last_space_found = child_i;
      filled_line_width_at_last_space_found = line_width - child_e.width();
    }
    if (line_width >= wrapper_width) {
      remaining_space = wrapper_width - filled_line_width_at_last_space_found;
      line_chars_extra_margin = remaining_space / (space_positions.length - 1);
      for (margin_i = 0; margin_i < space_positions.length; margin_i++) {
        children.eq(space_positions[margin_i]).addClass("spaced").css("padding-right", Math.floor(line_chars_extra_margin * 10) / 10);
      }
      children.eq(position_of_last_space_found + 1).addClass("first-line-char");
      children.eq(position_of_last_space_found).addClass("last-line-space");
      line_width = 0;
      child_i = position_of_last_space_found;
      first_line_char = 0;
      space_positions = [];
    }
  }
}

function insert_char(html) {
  var sel, range;
  if (window.getSelection) {
    sel = window.getSelection();
    if (sel.getRangeAt && sel.rangeCount) {
      range = sel.getRangeAt(0);
      range.deleteContents();
      var el = document.createElement("div");
      el.innerHTML = html;
      var frag = document.createDocumentFragment(),
        node, lastNode;
      while ((node = el.firstChild)) {
        lastNode = frag.appendChild(node);
      }
      var firstNode = frag.firstChild;
      pos = $(range.commonAncestorContainer).parent('span').index() + range.startOffset;
      $(selectors['spans']).eq(pos).before(frag);
      set_cursor_position(pos + 1, $(selectors['paragraphs']));
    }
  }
}

function get_span_at(x, y) {
  var $elements = $(selectors['spans']).map(function() {
    var $this = $(this);
    var offset = $this.offset();
    var l = offset.left;
    var t = offset.top;
    var h = $this.outerHeight(true);
    var w = $this.outerWidth(true);

    var maxx = l + w;
    var maxy = t + h;

    return (y <= maxy && y >= t) && (x <= maxx && x >= l) ? $this : null;
  });

  return $elements;
}

function init_demo() {
  var next_pos;

  // Copy the text from div.reference inside div.editable 
  // (only for the purpose of this example)
  var characters = $('div.reference p').text().trim().replace(/ /g, '\u00a0');
  for (var x = 0; x < characters.length; x++) {
    var c = characters.charAt(x);
    // wrap each character in a span
    $(selectors['paragraphs']).append("<span>" + c + "</span");
  }

  // initial justification
  justify(selectors['wrapper'], selectors['spans']);

  // re-justify on window resize
  $(window).resize(function() {
    clearTimeout(window.resizedFinished);
    window.resizedFinished = setTimeout(function() {
      justify(selectors['wrapper'], selectors['spans']);
    }, 20);
  });

  // Improve navigation with arrow keys
  $(selectors['wrapper']).on('keydown', function(e) {
    switch (e.which) {
      case 37: // left
        next_pos = get_cursor_position($(selectors['spans'])[0]) - 1;
        set_cursor_position(next_pos, $(selectors['paragraphs']));
        break;

      case 38: // up
        curr_pos = get_cursor_position($(selectors['spans'])[0]);
        curr_span = $(selectors['spans']).eq(curr_pos);
        curr_span_y = curr_span.position().top;
        curr_span_x = curr_span.position().left;
        next_span_y = curr_span_y - 1;
        next_span_x = curr_span_x + 1;
        next_span = get_span_at(curr_span_x, next_span_y);
        if (next_span[0]) {
          set_cursor_position_to_element(next_span[0][0]);
        }
        break;

      case 39: // right
        next_pos = get_cursor_position($(selectors['spans'])[0]);
        set_cursor_position(next_pos, $(selectors['paragraphs']));
        break;

      case 40: // down
        curr_pos = get_cursor_position($(selectors['spans'])[0]);
        curr_span = $(selectors['spans']).eq(curr_pos);
        curr_span_y = curr_span.position().top;
        curr_span_x = curr_span.position().left;
        curr_span_h = curr_span.outerHeight(true);
        next_span_y = curr_span_y + curr_span_h + 1;
        next_span_x = curr_span_x + 1;
        next_span = get_span_at(curr_span_x, next_span_y);
        if (next_span[0]) {
          set_cursor_position_to_element(next_span[0][0]);
        }
        break;
    }
  });

  // re-justify on character insertion
  $(selectors['wrapper']).on('keypress', function(e) {
    new_char = String.fromCharCode(e.which).replace(/ /g, '\u00a0');
    // Wrap new characters in spans
    new_el = '<span>' + new_char + '</span>';
    insert_char(new_el);
    justify(selectors['wrapper'], selectors['spans']);
    e.preventDefault();
  });

}

init_demo();
div.col {
  width: 50%;
  overflow: hidden;
  font-size: 1.2em;
  float: left;
  box-sizing: border-box;
  padding: 20px;
}

p {
  overflow: hidden;
}

div.reference p {
  text-align: justify;
}

div span {
  display: block;
  float: left;
}

.first-line-char {
  content: ' ';
  display: block;
  clear: left;
}

.last-line-space {
  display: none;
}
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<div class="col col1">
  <h2>reference</h2>
  <h4>This is just a reference. Edit the right column.</h4>
  <div class="reference">
    <p>
      Lorem ipsum dolor sit amet, consectetur adipisicing elit. Excepturi provident, nemo incidunt voluptate officia, ipsa nulla itaque laudantium aperiam cupiditate vero, nesciunt consequuntur, facilis aliquam enim quis ad. Fugiat, magni.
    </p>
  </div>
</div>
<div class="col col2">
  <h2>editable</h2>
  <h4>Click the paragraph text below to start to edit.</h4>
  <div contenteditable="true" class="editable" id="editable">
    <p>
      <!-- Text will be copied from the reference div -->
    </p>
  </div>
</div>

Demo.

【讨论】:

  • 这在使用箭头键、退格、删除或复制粘贴时非常有问题。缺少该功能会导致该解决方案无法使用。
  • OP 正在努力证明每个字符都包含在一个跨度中的文本的合理性。我试图通过遵循他建议的类似方法来解决这个问题(在每个空格中添加边距)。此外,我试图涵盖帖子中暴露的问题:“当我将新文本写入段落时,它会崩溃”。希望我的简约示例涵盖了将 Range 界面与 HTML 元素相结合的基本原则,并允许 OP 通过解决他的问题来制定自己的方式,如果这是他需要的话,甚至可能添加删除、撤消和格式化功能。
【解决方案2】:

对于这个问题,我认为最好采用 flexbox 方法。将id 添加到您的&lt;div&gt; 或为其设置样式以使其具有您想要的宽度,例如:

<div contenteditable="true" id="container">
    <p id="p1">
        <span id="s1">S</span>
        <span id="s2">o</span>
        <span id="s3">m</span>
        <span id="s4">e</span>
        <span id="s5">&nbsp;</span>
        <span id="s6">t</span>
        <span id="s7">e</span>
        <span id="s8">x</span>
        <span id="s9">t</span>
    </p>
</div>

然后在你的 css 中,添加以下内容:

#container {
    width: 400px;
}

#p1 {
    width: 100%;
    display: flex;
    justify-content: space-between;
}

【讨论】:

  • 目前看来是最好的建议。但是换行符会出现问题,对吗?他是否不希望单词之间有正常的换行符(这可以证明我的对齐方式)
  • 我认为换行可以简单地通过每行提供一个&lt;p&gt; 来实现。我认为这是对预期行为的公平妥协。
  • 是的,我不认为你可以用他想要的跨度设置来完全证明长多行文本。我认为更容易思考为什么需要所有跨度以及是否可以通过其他方式解决跨度问题。
  • 嗯,这取决于他的意图,也许这与谷歌文档的内容无关。
【解决方案3】:

您可以在末尾添加一个假词,使其成为width:100%display: inline-block。像这样:(根据您的要求更改父级的宽度)

.justified {
  width: 300px;
  text-align: justify;
}

.justified .fake-word {
  width: 100%;
  display: inline-block;
  height: 0.1em;
}
<div contenteditable="true">
  <p id="p1" class="justified">
    <span id="s1">S</span>
    <span id="s2">o</span>
    <span id="s3">m</span>
    <span id="s4">e</span>
    <span id="s5">&nbsp;</span>
    <span id="s6">t</span>
    <span id="s7">e</span>
    <span id="s8">x</span>
    <span id="s9">t</span>
    <span class="fake-word"></span>
  </p>
</div>

编辑 1:

如果您不希望字符之间有空格,您可以删除所有 span 标签:

<div contenteditable="true">
      <p id="p1" class="justified">
         Some Text
        <span class="fake-word"></span>
      </p>
    </div>

编辑 2: 此外,您不必添加 .fake-word,还有另一种使用伪类的方法:

 .justified:after {
      content: "";
      display: inline-block;
      width: 100%;
 }

【讨论】:

  • 好的,谢谢。但在 Mozilla Firefox 中,每个字母都是分开的。我只想分隔空格
  • 如果您不希望字符之间有空格,可以删除所有跨度标签。
  • 如果您不想删除所有 span 标签,请帮助我们了解您使用它们的目的,我们也许可以解决这部分问题。
  • 当您有 span 标签时,它们会尝试在整行中占据相等的空间,从而使字符保持距离。如果文本在宽度上占据了整个空间,那么 text-align justify 将起作用,这就是为什么我们添加了占据整个空间的假行。
  • Justify 仅在没有所有跨度的情况下才有效,否则多行句子将在字符之间而不是单词之间中断,因此没有假词无助于解决问题。
【解决方案4】:

苛刻的答案是,如果您想要正常的正当化行为但保持所有跨度,那么实现您正在寻找的东西并不容易。

但如果您想尝试这样的事情,您需要制作自己的 justify 脚本。为此,您需要遍历所有跨度,并且当您输入新行时(检查跨度从顶部的偏移量),您需要返回最后一个空格,然后尝试证明之前迭代的所有字符的合理性。这可以通过将它们封装在一个新的 span 或 div 中并在其中证明(封装 div 需要为 100% 宽度)来完成。仅使用 css 或通过更改空间跨度以仅扩展跨度。

SO question回答如何检查span的偏移量:How to select nth line of text (CSS/JS)

很抱歉,如果不添加更多跨度/div 或删除跨度,则无法正确证明我所看到的内容。

【讨论】:

    【解决方案5】:

    你有什么正在工作,但由于几个原因,它没有与你拥有的文本一起显示。

    第一个原因是文本太短。您需要足够的内容来使文本换行以显示理由。

    第二个原因是通过将 span 标签放在新行中是在每个标签之间添加一个空格。 试着把它们放在同一行

    像这样

    <div contenteditable="true">
        <p id="p1">
          <span id="s1">S</span><span id="s2">o</span><span id="s3">m</span><span id="s4">e</span><span id="s5">&nbsp;</span><span id="s6">t</span><span id="s7">e</span><span id="s8">x</span><span id="s9">t</span>
        </p>
    </div>
    

    这是jsfiddle 的示例工作

    【讨论】:

    • 您的示例与 html 中的空格结合在一起,为内容提供了包装位置。为了使这个解决方案可行,他必须找到好的断点并在他的 html 中添加空格。为此,我们需要有关他的代码的更多信息。
    • 如果你说的是  这只是复制粘贴时的错误。它适用于常规空间。我更新了jsfiddle。至于找到好的断点,我不确定你的意思。 div 会自动分词。
    • 我的意思是你在第一小提琴中的 span 标签之外的空格。但是可以替换  正常的“”也可以正常工作。但如果没有任何正常的“”,它的任何地方都不会起作用。
    • 好吧text: justify 需要空格才能产生任何效果。因此,如果您的文本中没有空格,那么拥有它并没有任何用处。当然,单词之间也会有空格。是否有跨度或只是纯文本都没有关系。 contenteditable 会在你写的时候自动添加这个空格。
    【解决方案6】:

    您可以通过使用display: inline-block;span 标记中的单词包裹起来来实现此目的。 例如:https://codepen.io/MaxViewUp/pen/XeMJmX?editors=1100

    <div id="d1" contenteditable="true">
     <p id="p1">
      <span class="word">
        <span id="s1">S</span>
        <span id="s2">o</span>
        <span id="s3">m</span>
        <span id="s4">e</span>
      </span>
      <span id="s5">&nbsp;</span>
      <span class="word">
        <span id="s1">S</span>
        <span id="s2">o</span>
        <span id="s3">m</span>
        <span id="s4">e</span>
      </span>
     </p>
    </div>
    

    【讨论】:

      【解决方案7】:

      使用 text-align: justify with display:flex(有时是强制性的) 这应该可以工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-10
        相关资源
        最近更新 更多