HTML: 防止在一些标点符号前面有空格之前换行

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

在法语排版惯例中,很少有标点符号像 ;:? 必须在前面加上白色 space。当标点位于其 HTML 容器的边界时,这会导致不必要的换行。

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

变成

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

objective就是这样得到的:

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

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

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

有什么建议吗?

注意: 共享相同的 objective 但有一个纯粹的 CSS 约束,我这里没有。

理想情况下,您应该用硬 space 服务器端 而不是客户端来替换 space,因为如果如果您在客户端执行此操作,您会遇到更新内容时内容会回流的问题,这会导致明显的 flash/movement.

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

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

这是一个元素(p 元素),里面有一个文本节点。

可能 space 在一个文本节点中,标点符号在另一个文本节点中:

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

那里,"Voulez-vous coucher avec moi ce soir"和space在一个文本节点中(在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");
            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");
            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> 元素之前。

但是再说一次:如果你能在服务器端做这个,那就更好了,避免回流。


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

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

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

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");
        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");
        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

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

How to select the appropriate tags

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

How to replace innerText only (ie not properties content)

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