javascript - HTML : Prevent line breaks before some punctuation characters preceeded by a whitespace

标签 javascript typography punctuation

在法语排版约定中,一些标点符号(例如 ;:?)前面必须有空格。当标点位于 HTML 容器的边框时,这会导致不必要的换行。

Voulez-vous coucher avec moi ce soir ? Non, merci.

变成了

Voulez-vous coucher avec moi ce soir 
? Non, merci.

目标是这样实现:

Voulez-vous coucher avec moi ce 
soir ? Oui, bien sûr.

我没有找到任何方法来避免使用 CSS 的这些字符之前的换行符。此外:

  • 由于现有数据和人体工学限制,无法在每次发生之前手动添加 
  • 使用 CSS whitespace: nowrap; 不是一个选项,因为它必须在所有其他情况下发生。

我正在考虑使用运行 str.replace("?", " ?"); 的全局 javascript 函数; 但我无法弄清楚一些事情:

  • 如何以及何时触发
  • 如何选择合适的标签
  • 如何仅替换innerText(即不替换属性内容)

有什么建议吗?

注意: Whitespace before some punctuation characters in French: is there a CSS way to avoid lines breaking?共享相同的目标,但具有我这里没有的纯 CSS 约束。

最佳答案

理想情况下,您应该在服务器端用硬空间替换该空间,而不是在客户端,因为如果您在客户端执行此操作,则会遇到以下问题:当您更新内容时,内容将重排,这将导致可察觉的闪烁/移动。

大多数情况下,您会在文本节点中遇到此问题,例如:

<p>Voulez-vous coucher avec moi ce soir ? Non, merci.</p>

这是一个内部有一个 Text 节点的元素(p 元素)。

可以将其放置在一个文本节点中的空格和另一个文本节点中的标点符号中:

<p><span>Voulez-vous coucher avec moi ce soir </span>? Non, merci.</p>

其中,“Voulez-vous coucher avec moi ce soir”和空格位于一个文本节点中(在 span 内),但标点符号位于另一个文本节点中(在 p 中)。我假设这种情况很罕见,我们不需要担心。

在文本节点中处理它相对容易:

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>

你可以把它放在 script 中标签位于 body 末尾,就在收盘前</body>元素。

但再说一遍:如果您可以在服务器端执行此操作,那就更好了,以避免回流。


如果我们想尝试处理一个文本节点以空格结尾而下一个文本节点以标点符号开头的情况,如我上面所示:

<p><span>Voulez-vous coucher avec moi ce soir </span>? Non, merci.</p>

...一种方法是获取所有文本节点的数组,然后查看是否有一个以空格结尾,下一个以标点符号开头。这是不完美的,因为它不会尝试处理以空格结尾的文本节点位于 block 元素末尾的情况(例如,标点符号在视觉上总是位于不同的行上),但它可能总比没有好。唯一的缺点是你最终可能会得到 block 元素,并在末尾有一个额外的硬空间。

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>


将您的问题映射到上述内容:

How and when triggering it

将其放入 script body 末尾的元素很早就触发它,但在内容进入 DOM 后,我们就可以对其进行操作了。

How to select the appropriate tags

我们忽略标签并在文本节点级别工作。

How to replace innerText only (ie not properties content)

通过在文本节点级别上工作。属性不是文本节点,因此我们不处理它们。

关于javascript - HTML : Prevent line breaks before some punctuation characters preceeded by a whitespace,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58257080/

相关文章:

javascript - 使用 js/jquery 修改 url 中的参数并重新加载页面

javascript - 如何从数组中删除空对象?

javascript - 在 Laravel 4 中使用 jquery Ajax

jquery - 一个字母单词后的不间断空格

java - 去除字符串顶部和末尾所有标点符号的正则表达式

python - 字符串列表的标点符号计数字典

javascript - Node.js中如何实现两个数组之间的增删改查?

ios - iOS 7 时钟应用中计时器使用的字体

html - 这个网站将所有标题词都包含在spans中,并添加了一个span作为空格,但为什么呢?

r - 如何删除标点符号排除否定?