JavaScript中的文本换行

41

我是JavaScript和jQuery的新手。

我在JavaScript中有一个名为str的变量,它包含了一段非常长的文本,大概是这样的:

"A quick brown fox jumps over a lazy dog". 
我想将它包装起来并赋给同一个变量str,在正确的位置插入适当的\nbr/标签。
我不想使用CSS等样式表。你能告诉我如何用JavaScript中的适当函数完成此操作吗?该函数应接收str参数并返回格式正确的文本。
例如:
str = somefunction(str, maxchar);
我尝试了很多次,但不幸的是,没有任何东西达到我想要的效果! :(
非常感谢任何帮助...

1
你如何知道哪些地方是“正确”的地方? - Pointy
1
你想在每 n 个字符后换行吗? - David Thomas
@OP 代码必须被包含在代码块中,不要删除编辑。 - SeinopSys
1
如果您限制包含文本的元素的宽度,那么换行不是会自动完成吗? - javabeangrinder
也许一个wordwrap模块会有所帮助? - Lloyd
14个回答

77

虽然这个问题很旧了,但迄今为止提供的许多解决方案比必要的更为复杂和昂贵,正如user2257198所指出的 - 这完全可以使用短小的一行正则表达式来解决。

然而,我发现他的解决方案存在一些问题,包括:在最大宽度之后而不是之前进行换行,打破明确未包含在字符类中的字符,并且没有考虑现有的换行符导致段落的开头被截断。

这促使我编写了自己的解决方案:

// Static Width (Plain Regex)
const wrap = (s) => s.replace(
    /(?![^\n]{1,32}$)([^\n]{1,32})\s/g, '$1\n'
);

// Dynamic Width (Build Regex)
const wrap = (s, w) => s.replace(
    new RegExp(`(?![^\\n]{1,${w}}$)([^\\n]{1,${w}})\\s`, 'g'), '$1\n'
);

额外特性

  • 处理任何非换行符的字符(例如,代码)。
  • 正确处理现有的换行符(例如,段落)。
  • 防止在换行符前添加空格。
  • 防止在字符串末尾添加不必要的换行符。

解释

主要思路是查找连续的字符序列,这些序列不包含换行符[^\n],长度最多为所需长度,例如32 {1,32}。通过在字符类中使用否定^,它更加宽容,避免了需要显式添加标点等内容的问题:

str.replace(/([^\n]{1,32})/g, '[$1]\n');
// Matches wrapped in [] to help visualise

"[Lorem ipsum dolor sit amet, cons]
[ectetur adipiscing elit, sed do ]
[eiusmod tempor incididunt ut lab]
[ore et dolore magna aliqua.]
"

到目前为止,这个函数只能确切地在32个字符处进行分割。它之所以有效,是因为它自己的换行插入标记了第一个序列后每个序列的起点。

要按单词进行分割,需要在贪婪量化符号 {1,32} 后加上限定符,以防止其选择在单词中间结束的序列。单词边界字符 \b 可能会导致新行开头的空格,因此必须使用空白字符 \s。它还必须放置在组外,以便消耗它,以防止增加最大宽度 1 个字符:

str.replace(/([^\n]{1,32})\s/g, '[$1]\n');
// Matches wrapped in [] to help visualise

"[Lorem ipsum dolor sit amet,]
[consectetur adipiscing elit, sed]
[do eiusmod tempor incididunt ut]
[labore et dolore magna]
aliqua."

现在它会在达到限制之前中断单词,但最后一个单词和句号没有在最后的序列中匹配,因为没有终止空格。

可以在空格中添加“或字符串结尾”选项(\s|$)以扩展匹配,但最好完全防止匹配最后一行,因为这会导致在结尾插入不必要的新行。为了实现这一点,可以在之前添加完全相同序列的负向先行断言,但使用字符串结尾字符而不是空格字符:

str.replace(/(?![^\n]{1,32}$)([^\n]{1,32})\s/g, '[$1]\n');
// Matches wrapped in [] to help visualise

"[Lorem ipsum dolor sit amet,]
[consectetur adipiscing elit, sed]
[do eiusmod tempor incididunt ut]
labore et dolore magna aliqua."

2
太好了!我一直在寻找这种功能,以在文本区域内生成伪双列格式,它完美地工作了。谢谢你。 - user1837608
我想将最后一行直到句号包括在数组中,这可能吗? - user1837608
如何在断行处添加缩进? - James Jensen
6
这很好,但如果单词超过最大长度限制,则根本无法捕获。 - Blowsie
然而,我想尝试其他的东西。我想支持连字符。这将允许我在必要时将文本拆分到我想要的位置。我的结果导致即使没有进行拆分,连字符也被保留下来,因此我添加了第二个替换来定位和删除未使用的连字符。 const wrap = function (s, w) { var wrapped = s.replace(new RegExp(`(?![^\\n]{1,${w}}$)(([^\\n]{1,${w}}-)|([^\n]{1,${w}})(?:\\s))`, 'g'), '$1\n'); return wrapped.replace(new RegExp('(?!-$)(-)', 'gm'), ''); } - naskew
显示剩余6条评论

33

这应该在最接近 maxChar 的空格处插入换行符:

str = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It w as popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";

str = wordWrap(str, 40);

function wordWrap(str, maxWidth) {
    var newLineStr = "\n"; done = false; res = '';
    while (str.length > maxWidth) {                 
        found = false;
        // Inserts new line at first whitespace of the line
        for (i = maxWidth - 1; i >= 0; i--) {
            if (testWhite(str.charAt(i))) {
                res = res + [str.slice(0, i), newLineStr].join('');
                str = str.slice(i + 1);
                found = true;
                break;
            }
        }
        // Inserts new line at maxWidth position, the word is too long to wrap
        if (!found) {
            res += [str.slice(0, maxWidth), newLineStr].join('');
            str = str.slice(maxWidth);
        }

    }

    return res + str;
}

function testWhite(x) {
    var white = new RegExp(/^\s$/);
    return white.test(x.charAt(0));
};

1
是的,它可以工作,但对于一些大写字母,它会破坏页面的对齐,你有注意到吗? - tina
1
这段代码从插入的文本中截取了一些最后的单词,有人有修改过的代码吗? - tina
这个可以工作,但会在没有空格的长单词(比如链接)中断,这可能不是期望的。 - cancerbero
代码对我来说运行良好。当你使用Angular或React时,你可能会收到警告或错误信息。要修复这个问题,只需处理代码的linting和变量声明即可。 - Dinesh Gopal Chand
仍然是一个很好的答案,我刚刚使用了它,但稍微更新了代码以保持用户输入的“回车/换行符”。如果还有其他人需要类似的东西,请查看:https://codepen.io/hozeis/pen/JjrePdE - Hozeis

13

这里有一个更短的解决方案:

var str = "This is a very long line of text that we are going to use in this example to divide it into rows of maximum 40 chars."

var result = stringDivider(str, 40, "<br/>\n");
console.log(result);

function stringDivider(str, width, spaceReplacer) {
    if (str.length>width) {
        var p=width
        for (;p>0 && str[p]!=' ';p--) {
        }
        if (p>0) {
            var left = str.substring(0, p);
            var right = str.substring(p+1);
            return left + spaceReplacer + stringDivider(right, width, spaceReplacer);
        }
    }
    return str;
}

这个函数使用递归来解决问题。


1
谢谢!我需要一个前缀和后缀,并且要处理任何空格,例如制表符,所以我更新了您的方案并将其放在此jsfiddle上:http://jsfiddle.net/rhyous/q409e7ej/1/ - Rhyous
@Rhyous,不错,但是你的最后一行没有前缀和后缀,短行也是如此。可能是设计问题,也可能不是。 :) - javabeangrinder
谢谢。我们在实现中发现了这些漏洞并解决了它们,但我还没有更新,所以感谢您的更新! - Rhyous

8

我的版本会返回一组行,而不是字符串,因为这样更灵活,可以选择要使用哪种换行符(如换行符或HTML BR)。

function wordWrapToStringList (text, maxLength) {
    var result = [], line = [];
    var length = 0;
    text.split(" ").forEach(function(word) {
        if ((length + word.length) >= maxLength) {
            result.push(line.join(" "));
            line = []; length = 0;
        }
        length += word.length + 1;
        line.push(word);
    });
    if (line.length > 0) {
        result.push(line.join(" "));
    }
    return result;
};

将行数组转换为字符串:
要将行数组转换为字符串:
wordWrapToStringList(textToWrap, 80).join('<br/>');

请注意,它只是进行单词换行,不会断开长单词,并且可能不是最快的。

如果您有一个变量(例如描述),需要将其拆分并需要每行进行包装,那么这非常适合代码模板(即自动生成类文件)- 谢谢。 - Nathan Hawks

3
许多这样的行为可以使用正则表达式作为单行实现 (使用非贪婪量词和最少匹配字符或贪婪量词和最大字符数,具体取决于您需要的行为)。下面展示了一个非贪婪全局替换在Node V8 REPL中的工作方式,因此您可以看到命令和结果。但同样的方法也适用于浏览器。
该模式搜索至少10个与定义组匹配(\w表示单词字符,\s表示空格字符)的字符,并将该模式锚定在\b单词边界上。然后使用反向引用将原始匹配替换为附加换行符的匹配(在这种情况下,可选地替换未捕获在括号内反向引用中的空格字符)。
> s = "This is a paragraph with several words in it."
'This is a paragraph with several words in it.'
> s.replace(/([\w\s]{10,}?)\s?\b/g, "$1\n")
'This is a \nparagraph \nwith several\nwords in it\n.'

在原帖作者请求的格式中,这可能看起来像...
var str = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It w as popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";

function wordWrap(text,width){
    var re = new RegExp("([\\w\\s]{" + (width - 2) + ",}?\\w)\\s?\\b", "g")
    return text.replace(re,"$1\n")
}

> wordWrap(str,40)
'Lorem Ipsum is simply dummy text of the\nprinting and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s\n, when an unknown printer took a galley of\ntype and scrambled it to make a type specimen\nbook. It has survived not only five centuries\n, but also the leap into electronic typesetting\n, remaining essentially unchanged. It w as popularised in the 1960s with the\nrelease of Letraset sheets containing Lorem\nIpsum passages, and more recently with desktop publishing\nsoftware like Aldus PageMaker including\nversions of Lorem Ipsum.'

警告:当单词长度大于10时,此功能不会进行分割。 - Cody G

2

我的变体。它保留单词的完整性,因此可能不总是符合最大字符标准。

function wrapText(text, maxChars) {
        var ret = [];
        var words = text.split(/\b/);

        var currentLine = '';
        var lastWhite = '';
        words.forEach(function(d) {
            var prev = currentLine;
            currentLine += lastWhite + d;

            var l = currentLine.length;

            if (l > maxChars) {
                ret.push(prev.trim());
                currentLine = d;
                lastWhite = '';
            } else {
                var m = currentLine.match(/(.*)(\s+)$/);
                lastWhite = (m && m.length === 3 && m[2]) || '';
                currentLine = (m && m.length === 3 && m[1]) || currentLine;
            }
        });

        if (currentLine) {
            ret.push(currentLine.trim());
        }

        return ret.join("\n");
    }

1

在使用正则表达式和其他实现寻找完美解决方案后,我决定自己编写。它并不完美,但对我的情况起了很好的作用,也许当您的所有文本都是大写时它无法正常工作。

function breakTextNicely(text, limit, breakpoints) {

      var parts = text.split(' ');
      var lines = [];
      text = parts[0];
      parts.shift();

      while (parts.length > 0) {
        var newText = `${text} ${parts[0]}`;

        if (newText.length > limit) {
          lines.push(`${text}\n`);
          breakpoints--;

          if (breakpoints === 0) {
            lines.push(parts.join(' '));
            break;
          } else {
           text = parts[0];
       }
        } else {
          text = newText;
        }
       parts.shift();
      }

      if (lines.length === 0) {
        return text;
      } else {
        return lines.join('');
      }
    }

    var mytext = 'this is my long text that you can break into multiple line sizes';
    console.log( breakTextNicely(mytext, 20, 3) );


将失败于此文本var mytext ='这是我的长多行大小asdsddghfssssssssghfghfghfghhhhhhhhhhhhhhhkasdsddghfssssssssghfghfghfghhhhhhhhhhhhhhhk它不会再从这里打破表单'; - cancerbero

1

这是javabeangrinder解决方案的扩展答案,也适用于多段落输入的文本包装:

  function wordWrap(str, width, delimiter) {
    // use this on single lines of text only

    if (str.length>width) {
      var p=width
      for (; p > 0 && str[p] != ' '; p--) {
      }
      if (p > 0) {
        var left = str.substring(0, p);
        var right = str.substring(p + 1);
        return left + delimiter + wordWrap(right, width, delimiter);
      }
    }
    return str;
  }

  function multiParagraphWordWrap(str, width, delimiter) {
    // use this on multi-paragraph lines of text

    var arr = str.split(delimiter);

    for (var i = 0; i < arr.length; i++) {
        if (arr[i].length > width)
          arr[i] = wordWrap(arr[i], width, delimiter);
    }

    return arr.join(delimiter);
  }

太棒了 - 这是唯一一个对我有效的函数。只有一个小提示 - 浏览器(带CSS)似乎也会通过“-”来分割单词。通过这个小改动,它可以表现得相同。 - LachoTomov

0
有很多方法可以做到这一点,Thomas Brierley的回答适用于大多数情况。其他答案可能会导致最后一行被省略。下面是一种流畅的解决方案,虽然不如Thomas提供的正则表达式解决方案优雅,但仍然可以以高效的方式输出简洁的结果,同时尊重段落出现并允许自定义换行符。
Flems Playground上查看演示。

代码

function wrap (input: string, { limit = 80, breaks = '\n', join = true } = {}) {
  
  const lexed: string[] = []
  const regex: RegExp = /[\t\v\r \u00a0\u2000-\u200b\u2028-\u2029\u3000]/g;
  const words: string[] = input.trim().replace(regex, ' ').split(' ').concat('\n');  
  
  for (let i = 0, s = 0, w = 0, l = words.length; i < l; i++) {

    if (words[i] === '\n' &&  i + 1 !== l) {

      lexed.push(words[i])

    } else {
      
      w += words[i].length + 1;
  
      if (w > limit || (i + 1 === l)) {
        
        lexed.push(join 
          ? words.slice(s, i).join(' ')
          : words.slice(s, i).join(' ') + breaks
        );

        s = i;
        w = 0;

      }
    }
  }
  
  return join ? lexed.join(breaks) : lexed;
  
}

使用方法

wrap('add your long string to wrap', {
  limit: 80,     // wrap limit
  breaks: '\n',  // newline characters
  join: true     // Whether or not to join or return array
})

故障

以下是主要要点:

  • 多余的空格将被统一。例如:foo bar > foo bar
  • 输入将被修剪开头和结尾
  • 换行符将被保留
  • 可选择返回字符串或字符串列表(即string[]
  • 接受自定义换行符,例如:\n<br>

0

除了@Ross Rogers和@ieeehh的回答之外。 你代码中的变量“done”被用作“found”。只是想提醒其他人不要混淆。


这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - Alexander

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接