用户可以按下回车键并将该内容向下移动到
下一个现有段落,同时仍保留现有的
动态自动格式化
如果我理解正确的话,你想要的是一旦文本被分成段落,然后用户在其中一个中添加一些文本并按 Enter,那么剩余的文本应该流动在接下来的段落中,将溢出的文本与之前一样分配。
类似地,当用户在段落的开头按下 BackSpace 时,文本会再次回到上一段,溢出的文本会像之前一样均匀地分布到其他段落中。
作为一种算法,你需要的是这样的:
- 将初始文本分成相等的块并分配到段落中,根据需要动态创建
ps。
- 在那些
p 元素上收听 keyup 事件
- 如果按下的键是Enter,
- 3.1 从按下 Enter 的位置提取剩余文本
- 3.2 从接下来的所有段落中提取文本,在前面提取的溢出文本前面
- 3.3 删除接下来的所有段落并像我们在步骤 1 中所做的那样分发组合文本
- 如果按下的键是BackSpace,
- 4.1 检查是否在段首,是否有前段
- 4.2 提取段落文本并附加到下一个所有段落的文本中
- 4.3 删除接下来的所有段落,包括当前段落,并将提取的文本附加到前一段。
- 4.4 分发组合文本,就像我们在步骤 1 中所做的那样
有了这个粗略的算法,你就可以开始编码了,看起来像这样:
注 1:这都是 JavaScript,没有 jQuery。
注 2:这过于简化了,您需要进一步优化并解决所有边缘情况。
缓存所需元素并绑定事件处理程序:
var btn = document.getElementById('go'),
textarea = document.getElementById('textarea1'),
content = document.getElementById('content');
btn.addEventListener('click', initialDistribute);
content.addEventListener('keyup', handleKey);
从textarea 分发初始文本,删除现有段落(如果有):
function initialDistribute() {
var text = textarea.value;
while (content.hasChildNodes()) { content.removeChild(content.lastChild); }
rearrange(text);
}
通过动态创建所需数量的段落来重新排列/分配文本的逻辑:
function rearrange(text) {
var chunks = text.match(/.{1,100}/g) || [];
chunks.forEach(function(str, idx) {
para = document.createElement('P');
para.setAttribute('contenteditable', true);
para.textContent = str;
content.appendChild(para);
});
}
注意 3:我在这个例子中使用了 100 个字符来分割文本。此外,这不会处理空格,并且会在两者之间拆分单词。您将需要在您的代码中执行此操作。 (#见下面的编辑)
用于捕获 Enter (keycode 13) 和 BackSpace (keycode 8) 键的事件处理程序。另外,查看元素是否为p 元素:
function handleKey(e) {
var para = e.target, position,
key, fragment, overflow, remainingText;
key = e.which || e.keyCode || 0;
if (para.tagName != 'P') { return; }
if (key != 13 && key != 8) { return; }
...
获取光标位置,判断段落开头是否按下了BackSpace:
position = window.getSelection().getRangeAt(0).startOffset;
如果按下 Enter,则在当前段落的最后一个子段落之后提取文本(当按下 Enter 时,contenteditable 将生成 div),删除该节点,在此之后的所有段落的剩余文本前面加上剩余的段落。
if (key == 13) {
fragment = para.lastChild; overflow = fragment.textContent;
fragment.parentNode.removeChild(fragment);
remainingText = overflow + removeSiblings(para, false);
rearrange(remainingText);
}
如果按下了BackSpace,检查是否有前一段以及光标是否在开头。如果是,则从所有后续段落(包括当前段落)中提取剩余文本,同时删除它们:
if (key == 8 && para.previousElementSibling && position == 0) {
fragment = para.previousElementSibling;
remainingText = removeSiblings(fragment, true);
rearrange(remainingText);
}
从后续段落中提取文本并将其删除的逻辑:
function removeSiblings(elem, includeCurrent) {
var text = '', next;
if (includeCurrent && !elem.previousElementSibling) {
parent = elem.parentNode; text = parent.textContent;
while (parent.hasChildNodes()) { parent.removeChild(parent.lastChild); }
} else {
elem = includeCurrent ? elem.previousElementSibling : elem;
while (next = elem.nextSibling) {
text += next.textContent; elem.parentNode.removeChild(next);
}
}
return text;
}
总而言之,这是一个有效的 sn-p:
片段:
var btn = document.getElementById('go'),
textarea = document.getElementById('textarea1'),
content = document.getElementById('content'),
chunkSize = 100;
btn.addEventListener('click', initialDistribute);
content.addEventListener('keyup', handleKey);
function initialDistribute() {
var text = textarea.value;
while (content.hasChildNodes()) {
content.removeChild(content.lastChild);
}
rearrange(text);
}
function rearrange(text) {
var chunks = splitText(text, false);
chunks.forEach(function(str, idx) {
para = document.createElement('P');
para.setAttribute('contenteditable', true);
para.textContent = str;
content.appendChild(para);
});
}
function handleKey(e) {
var para = e.target, position,
key, fragment, overflow, remainingText;
key = e.which || e.keyCode || 0;
if (para.tagName != 'P') { return; }
if (key != 13 && key != 8) { return; }
position = window.getSelection().getRangeAt(0).startOffset;
if (key == 13) {
fragment = para.lastChild;
overflow = fragment.textContent;
fragment.parentNode.removeChild(fragment);
remainingText = overflow + removeSiblings(para, false);
rearrange(remainingText);
}
if (key == 8 && para.previousElementSibling && position == 0) {
fragment = para.previousElementSibling;
remainingText = removeSiblings(fragment, true);
rearrange(remainingText);
}
}
function removeSiblings(elem, includeCurrent) {
var text = '', next;
if (includeCurrent && !elem.previousElementSibling) {
parent = elem.parentNode;
text = parent.textContent;
while (parent.hasChildNodes()) {
parent.removeChild(parent.lastChild);
}
} else {
elem = includeCurrent ? elem.previousElementSibling : elem;
while (next = elem.nextSibling) {
text += next.textContent;
elem.parentNode.removeChild(next);
}
}
return text;
}
function splitText(text, useRegex) {
var chunks = [], i, textSize, boundary = 0;
if (useRegex) {
var regex = new RegExp('.{1,' + chunkSize + '}\\b', 'g');
chunks = text.match(regex) || [];
} else {
for (i = 0, textSize = text.length; i < textSize; i = boundary) {
boundary = i + chunkSize;
if (boundary <= textSize && text.charAt(boundary) == ' ') {
chunks.push(text.substring(i, boundary));
} else {
while (boundary <= textSize && text.charAt(boundary) != ' ') { boundary++; }
chunks.push(text.substring(i, boundary));
}
}
}
return chunks;
}
* { box-sizing: border-box; padding: 0; margin: 0; }
body { font-family: monospace; font-size: 1em; }
h3 { margin: 1.2em 0; }
div { margin: 1.2em; }
textarea { width: 100%; }
button { padding: 0.5em; }
p { padding: 1.2em 0.5em; margin: 1.4em 0; border: 1px dashed #aaa; }
<div>
<h3>Paste text in the field below to divide text into
paragraphs..</h3>
<textarea placeholder="Type text here, then press the button below." id="textarea1" rows="5" ></textarea><br/><br/>
<button id="go">Divide Text into Paragraphs</button>
</div>
<hr>
<div>
<h3>Divided Text Will Appear Below:</h3>
<div id="content"></div>
</div>
还有一个小提琴供你玩:
小提琴:https://jsfiddle.net/abhitalks/jwnnn5oy/
编辑 1:
修复了在单词边界处中断的正则表达式。还添加了相同的非正则表达式程序代码(在 Op 的原始代码行),以演示 Op 如何将他的其他代码段考虑在内并将其集成到其中。
注4:关于Op关于使用jQuery的评论,与手头的问题无关。 jQuery 只不过是 JavaScript,对于他们来说,将 sn-p 片段合并到更大的代码库中应该是微不足道的。
变更集:新增功能splitText。
编辑 2:
根据您的评论,如果您希望在用户键入时自动进行重新分配...那么您将需要计算该段落中文本的长度并查看是否超出了您的块大小。如果是这样,则从该段落开始重新分发。对退格执行相反的操作。
我最初发布了解决方案,以满足您的要求,即当用户在任何文本之间按 Enter 键以中断并将其分发到后续段落时。我不建议在用户键入时自动执行此操作,因为更改对用户来说太刺耳了。
片段 2:
var btn = document.getElementById('go'),
textarea = document.getElementById('textarea1'),
content = document.getElementById('content'),
chunkSize = 100;
btn.addEventListener('click', initialDistribute);
content.addEventListener('keyup', handleKey);
function initialDistribute() {
var text = textarea.value;
while (content.hasChildNodes()) {
content.removeChild(content.lastChild);
}
rearrange(text);
}
function rearrange(text) {
var chunks = splitText(text, false);
chunks.forEach(function(str, idx) {
para = document.createElement('P');
para.setAttribute('contenteditable', true);
para.textContent = str;
content.appendChild(para);
});
}
function handleKey(e) {
var para = e.target, position,
key, fragment, overflow, remainingText;
key = e.which || e.keyCode || 0;
if (para.tagName != 'P') { return; }
if (key != 13 && key != 8) { redistributeAuto(para); return; }
position = window.getSelection().getRangeAt(0).startOffset;
if (key == 13) {
fragment = para.lastChild;
overflow = fragment.textContent;
fragment.parentNode.removeChild(fragment);
remainingText = overflow + removeSiblings(para, false);
rearrange(remainingText);
}
if (key == 8 && para.previousElementSibling && position == 0) {
fragment = para.previousElementSibling;
remainingText = removeSiblings(fragment, true);
rearrange(remainingText);
}
}
function redistributeAuto(para) {
var text = para.textContent, fullText;
if (text.length > chunkSize) {
fullText = removeSiblings(para, true);
}
rearrange(fullText);
}
function removeSiblings(elem, includeCurrent) {
var text = '', next;
if (includeCurrent && !elem.previousElementSibling) {
parent = elem.parentNode;
text = parent.textContent;
while (parent.hasChildNodes()) {
parent.removeChild(parent.lastChild);
}
} else {
elem = includeCurrent ? elem.previousElementSibling : elem;
while (next = elem.nextSibling) {
text += next.textContent;
elem.parentNode.removeChild(next);
}
}
return text;
}
function splitText(text, useRegex) {
var chunks = [], i, textSize, boundary = 0;
if (useRegex) {
var regex = new RegExp('.{1,' + chunkSize + '}\\b', 'g');
chunks = text.match(regex) || [];
} else {
for (i = 0, textSize = text.length; i < textSize; i = boundary) {
boundary = i + chunkSize;
if (boundary <= textSize && text.charAt(boundary) == ' ') {
chunks.push(text.substring(i, boundary));
} else {
while (boundary <= textSize && text.charAt(boundary) != ' ') { boundary++; }
chunks.push(text.substring(i, boundary));
}
}
}
return chunks;
}
* { box-sizing: border-box; padding: 0; margin: 0; }
body { font-family: monospace; font-size: 1em; }
h3 { margin: 1.2em 0; }
div { margin: 1.2em; }
textarea { width: 100%; }
button { padding: 0.5em; }
p { padding: 1.2em 0.5em; margin: 1.4em 0; border: 1px dashed #aaa; }
<div>
<h3>Paste text in the field below to divide text into
paragraphs..</h3>
<textarea placeholder="Type text here, then press the button below." id="textarea1" rows="5" ></textarea><br/><br/>
<button id="go">Divide Text into Paragraphs</button>
</div>
<hr>
<div>
<h3>Divided Text Will Appear Below:</h3>
<div id="content"></div>
</div>
小提琴 2:https://jsfiddle.net/abhitalks/hvhr4ds8/
注意 5:在小提琴中,首先按 Enter 键以中断一些文本,这样您就可以在键入时看到重新分配是如何发生的.另外请注意,由于不分词的逻辑,需要多出几个字符才能重新分配。
变更集:新增功能redistributeAuto。