理想情况下,您应该用硬空间替换空间服务器端,而不是客户端,因为如果您在客户端这样做,您会遇到以下问题内容会在您更新时重排,这将导致可察觉的闪光/移动。
大多数情况下,您会在 Text 节点中遇到此问题,例如:
<p>Voulez-vous coucher avec moi ce soir ? Non, merci.</p>
这是一个元素(p 元素),其中有一个 Text 节点。
可能将它放在一个文本节点中的空格和另一个文本节点中的标点符号:
<p><span>Voulez-vous coucher avec moi ce soir </span>? Non, merci.</p>
那里,“Voulez-vous coucher avec moi ce soir”和空格在一个文本节点中(在span 中),但标点在另一个文本节点中(在p 中)。我假设这种情况非常罕见,我们无需担心。
在Text节点内处理它相对容易:
fixSpaces(document.body);
function fixSpaces(node) {
switch (node.nodeType) {
case 1: // Element
for (let child = node.firstChild;
child;
child = child.nextSibling) {
fixSpaces(child);
}
break;
case 3: // Text node
node.nodeValue = node.nodeValue.replace(/ ([;?!])/g, "\u00a0$1");
break;
}
}
现场示例:
// Obviously, you wouldn't have this in a `setTimeout` in your
// real code, you'd call it directly right away
// as shown in the answer
console.log("Before...");
setTimeout(() => {
fixSpaces(document.body);
console.log("After");
}, 1000);
function fixSpaces(node) {
switch (node.nodeType) {
case 1: // Element
for (let child = node.firstChild;
child;
child = child.nextSibling) {
fixSpaces(child);
}
break;
case 3: // Text node
node.nodeValue = node.nodeValue.replace(/ ([;?!])/g, "\u00a0$1");
break;
}
}
p {
font-size: 14px;
display: inline-block;
width: 220px;
border: 1px solid #eee;
}
<p>Voulez-vous coucher avec moi ce soir ? Non, merci.</p>
您可以在body 末尾的script 标记中使用它,就在结束</body> 元素之前。
但是再次:如果您可以在服务器端执行此操作,那就更好了,以避免重排。
如果我们想尝试处理一个文本节点以空格结尾而下一个以标点符号开头的情况,如上所示:
<p><span>Voulez-vous coucher avec moi ce soir </span>? Non, merci.</p>
...一种方法是获取所有 Text 节点的数组,然后查看是否有一个以空格结尾,下一个以标点符号开头。这是不完美的,因为它不会尝试处理以空格结尾的 Text 节点位于块元素末尾的情况(例如,标点符号在视觉上总是在不同的行上),但它可能聊胜于无。唯一的缺点是你最终会得到带有额外硬空间的块元素。
fixSpaces(document.body);
function gatherText(node, array = []) {
switch (node.nodeType) {
case 1: // Element
for (let child = node.firstChild;
child;
child = child.nextSibling) {
array = gatherText(child, array);
}
break;
case 3: // Text node
array.push(node);
break;
}
return array;
}
function fixSpaces(node) {
const texts = gatherText(node);
for (let i = 0, len = texts.length; i < len; ++i) {
const text = texts[i];
const str = text.nodeValue = text.nodeValue.replace(/ ([;?|])/g, "\u00A0$1");
if (i < len - 1 && str[str.length - 1] === " " && /^[;?!]/.test(texts[i + 1].nodeValue)) {
// This node ends with a space and the next starts with punctuation,
// replace the space with a hard space
text.nodeValue = str.substring(0, str.length - 1) + "\u00A0";
}
}
}
console.log("Before...");
setTimeout(() => {
fixSpaces(document.body);
console.log("After");
}, 1000);
function gatherText(node, array = []) {
switch (node.nodeType) {
case 1: // Element
for (let child = node.firstChild;
child;
child = child.nextSibling) {
array = gatherText(child, array);
}
break;
case 3: // Text node
array.push(node);
break;
}
return array;
}
function fixSpaces(node) {
const texts = gatherText(node);
for (let i = 0, len = texts.length; i < len; ++i) {
const text = texts[i];
const str = text.nodeValue = text.nodeValue.replace(/ ([;?|])/g, "\u00A0$1");
if (i < len - 1 && str[str.length - 1] === " " && /^[;?!]/.test(texts[i + 1].nodeValue)) {
// This node ends with a space and the next starts with punctuation,
// replace the space with a hard space
text.nodeValue = str.substring(0, str.length - 1) + "\u00A0";
}
}
}
p {
font-size: 14px;
display: inline-block;
width: 220px;
border: 1px solid #eee;
}
<p><span>Voulez-vous coucher avec moi ce soir </span>? Non, merci.</p>
将您的问题映射到上述内容:
触发方式和时间
将它放在body 末尾的script 元素中会很早就触发它,但在内容在 DOM 中并准备好对其进行操作之后。
如何选择合适的标签
我们忽略标签并在文本节点级别工作。
如何只替换innerText(即不是属性内容)
通过文本节点级别的工作。属性不是文本节点,所以我们不处理它们。