【问题标题】:HTML: Prevent line breaks before some punctuation characters preceeded by a whitespaceHTML:防止在某些标点符号前面有空格之前换行
【发布时间】:2019-10-06 12:08:08
【问题描述】:

在法语排版约定中,;:? 等少数标点字符必须以空格开头。当标点位于其 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 约束。

【问题讨论】:

    标签: javascript typography punctuation


    【解决方案1】:

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

    大多数情况下,您会在 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;
    }
    &lt;p&gt;Voulez-vous coucher avec moi ce soir ? Non, merci.&lt;/p&gt;

    您可以在body 末尾的script 标记中使用它,就在结束&lt;/body&gt; 元素之前。

    但是再次:如果您可以在服务器端执行此操作,那就更好了,以避免重排。


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

    <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;
    }
    &lt;p&gt;&lt;span&gt;Voulez-vous coucher avec moi ce soir &lt;/span&gt;? Non, merci.&lt;/p&gt;

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

    触发方式和时间

    将它放在body 末尾的script 元素中会很早就触发它,但在内容在 DOM 中并准备好对其进行操作之后。

    如何选择合适的标签

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

    如何只替换innerText(即不是属性内容)

    通过文本节点级别的工作。属性不是文本节点,所以我们不处理它们。

    【讨论】:

    • 您好 T.J.,我无法更改数据服务器端,因为它是由我无法修改的 API 发送的。我刚刚测试了您的解决方案,效果很好。我看不到任何可见的闪光/移动,可能是因为页面中这种情况很少见。非常感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-05
    • 1970-01-01
    • 2021-12-20
    • 2015-04-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多