寻找换行位置

47

假设我有一段随机的单行文本。像这样

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

但由于某种原因(包含元素的宽度设置、文本缩放等),在查看者的屏幕上,它显示为两行或多行。

Lorem ipsum dolor sit amet,

consectetur adipiscing elit.

或者

Lorem ipsum dolor sit

amet, consectetur

adipiscing elit.

是否有办法通过javascript找出这些换行符的位置?

$('p').text()$('p').html() 无论文本如何显示,都会返回 Lorem ipsum dolor sit amet, consectetur adipiscing elit.


1
如果$('p').html()返回的是那个,那么它不是换行符。可能是'p'元素本身或其容器之一的宽度。为什么不同时提供问题的上下文呢? - Hari Pachuveetil
使用HTML、JavaScript、jQuery或CSS是不可能的。你可以编写Java小程序或嵌入Flash,但这似乎太困难了。你为什么需要它? - Flo Edelmann
@Floyd Pink:这就是我的意思。代码中没有字面上的“\n”,文本之所以显示在多行上,是因为<p>的宽度。问题是,我能否找出最终显示文本每一行的内容? - Inaimathi
1
@elektronikLexikon:长话短说,我正在制作一个小型网络应用程序,让一些人生成一些奇特的具体文件。我想,从屏幕上提取文本布局比在生成步骤中自己排版会更容易些。 - Inaimathi
可以通过JavaScript实现,您可以使用替代的内联元素来测量宽度,并检查它们是否相同 - 但是这非常繁琐。对我来说,听起来你的目标含糊不清,你应该重新思考你想要实现什么 - 或许可以使用CSS中的word-wrap属性或类似的方法。 - balupton
显示剩余2条评论
6个回答

29

如果您想要一个极其简单且可能对您毫无用处的东西(如果您在段落中有任何HTML,则需要进行重大修改),那么请看看这个:

var para = $('p');

para.each(function(){
    var current = $(this);
    var text = current.text();
    var words = text.split(' ');

    current.text(words[0]);
    var height = current.height();

    for(var i = 1; i < words.length; i++){
        current.text(current.text() + ' ' + words[i]);

        if(current.height() > height){
            height = current.height();
            // (i-1) is the index of the word before the text wraps
            console.log(words[i-1]);
        }
    }
});

这个方法非常简单,但可能很有效。它将文本按空格分割,然后逐个单词地追加回去,并观察元素高度是否增加,以指示是否换行。

您可以在此处查看:http://www.jsfiddle.net/xRPYN/2/


jsfiddle的设置似乎停留在第一行。我的第一反应是我不能使用它,因为用户实际上可以控制容器的宽度/高度,但仔细思考后,我可以在屏幕外创建一个具有相同宽度但没有高度规格的临时p(此时我可以在每行之后清除当前以获得所需的“按行换行分隔的行列表”输出)。我会试一试。 - Inaimathi
2
太棒了...如果有人需要,这里有一个没有jQuery的版本(仅在Chrome中测试过) http://jsfiddle.net/tV29m/ - sq2
1
太好了!我已经将其改成函数形式: function isTextWrapped(div) { var wraps=0; var words = div.text().split(' '); div.text(words[0]); var height = div.height(); for(var i = 1; i < words.length; i++){ div.text(div.text() + ' ' + words[i]); if(div.height() > height){ height = div.height(); wraps++; } } return wraps; } - AwokeKnowing
我也确认在处理从右到左的文本(阿拉伯语)时不会弄乱文本。但是如果您担心,可以将原始文本存储并在最后替换它。 - AwokeKnowing

13

对于像生成pdf这样的用例。

如果发生分裂,您可以限制每行字符数,并适当调整中间单词。

为了获得更准确的每行字符数,您可以使用等宽字体,然后确定每种允许字体的每个字符的宽度。然后将字符宽度除以允许文本行宽度的大小,就可以得到该字体的允许字符数每行。

您可以使用非等宽字体,但是那么您将不得不测量每个字母的宽度 - 哎呀。您可以自动猜测宽度的方法是添加一个没有边距或填充的span,为每种字体(和大小)添加每个字符,然后测量span的宽度并使用它。

我已经编写了代码:

/**
 * jQuery getFontSizeCharObject
 * @version 1.0.0
 * @date September 18, 2010
 * @since 1.0.0, September 18, 2010
 * @package jquery-sparkle {@link http://www.balupton/projects/jquery-sparkle}
 * @author Benjamin "balupton" Lupton {@link http://www.balupton.com}
 * @copyright (c) 2010 Benjamin Arthur Lupton {@link http://www.balupton.com}
 * @license Attribution-ShareAlike 2.5 Generic {@link http://creativecommons.org/licenses/by-sa/2.5/
 */
$.getFontSizeCharObject = function(fonts,sizes,chars){
    var fonts = fonts||['Arial','Times'],
        sizes = sizes||['12px','14px'],
        chars = chars||['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','y','x','z',
                        'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','Y','X','Z',
                        '0','1','2','3','4','5','6','7','8','9','-','=',
                        '!','@','#','$','%','^','&','*','(',')','_','+',
                        '[',']','{','}','\\','|',
                        ';',"'",':','"',
                        ',','.','/','<','>','?',' '],
        font_size_char = {},
        $body = $('body'),
        $span = $('<span style="padding:0;margin:0;letter-spacing:0:word-spacing:0"/>').appendTo($body);

    $.each(fonts, function(i,font){
        $span.css('font-family', font);
        font_size_char[font] = font_size_char[font]||{};
        $.each(sizes, function(i,size){
            $span.css('font-size',size);
            font_size_char[font][size] = font_size_char[font][size]||{};
            $.each(chars,function(i,char){
                if ( char === ' ' ) {
                    $span.html('&nbsp;');
                }
                else {
                    $span.text(char);
                }
                var width = $span.width()||0;
                font_size_char[font][size][char] = width;
            });
        });
    });

    $span.remove();

    return font_size_char;
};

/**
 * jQuery adjustedText Element Function
 * @version 1.0.0
 * @date September 18, 2010
 * @since 1.0.0, September 18, 2010
 * @package jquery-sparkle {@link http://www.balupton/projects/jquery-sparkle}
 * @author Benjamin "balupton" Lupton {@link http://www.balupton.com}
 * @copyright (c) 2010 Benjamin Arthur Lupton {@link http://www.balupton.com}
 * @license Attribution-ShareAlike 2.5 Generic {@link http://creativecommons.org/licenses/by-sa/2.5/
 */
$.fn.adjustedText = function(text,maxLineWidth){
    var $this = $(this),
        font_size_char = $.getFontSizeCharObject(),
        char_width = font_size_char['Times']['14px'],
        maxLineWidth = parseInt(maxLineWidth,10),
        newlinesAt = [],
        lineWidth = 0,
        lastSpace = null;

    text = text.replace(/\s+/g, ' ');

    $.each(text,function(i,char){
        var width = char_width[char]||0;
        lineWidth += width;
        if ( /^[\-\s]$/.test(char) ) {
            lastSpace = i;
        }
        //console.log(i,char,lineWidth,width);
        if ( lineWidth >= maxLineWidth ) {
            newlinesAt.push(lastSpace||i);
            lineWidth = width;
            lastSpace = null;
        }
    });

    $.each(newlinesAt,function(i,at){
        text = text.substring(0,at+i)+"\n"+text.substring(at+i);
    });

    text = text.replace(/\ ?\n\ ?/g, "\n");

    console.log(text,newlinesAt);

    $this.text(text);

    return $this;
};

$(function(){
    var $body = $('body'),
        $textarea = $('#mytext'),
        $btn = $('#mybtn'),
        $div = $('#mydiv');

    if ( $textarea.length === 0 && $div.length === 0 ) {
        $body.empty();

        $textarea = $('<textarea id="mytext"/>').val('(When spoken repeatedly, often three times in succession: blah blah blah!) Imitative of idle, meaningless talk; used sometimes in a slightly derogatory manner to mock or downplay another\'s words, or to show disinterest in a diatribe, rant, instructions, unsolicited advice, parenting, etc. Also used when recalling and retelling another\'s words, as a substitute for the portions of the speech deemed irrelevant.').appendTo($body);
        $div = $('<div id="mydiv"/>').appendTo($body);
        $btn = $('<button id="mybtn">Update Div</button>').click(function(){
            $div.adjustedText($textarea.val(),'300px');
        }).appendTo($body);

        $div.add($textarea).css({
            'width':'300px',
            'font-family': 'Times',
            'font-size': '14px'
        });
        $div.css({
            'width':'auto',
            'white-space':'pre',
            'text-align':'left'
        });
    }

});

1
这比我预期的要多得多。点赞。不过,我认为对于我的目的来说,可能有更简单的方法(因为我只需要一个在换行点分隔的行列表,而且只要我让UI做同样的事情,我可能只需要按单词断开,而不是字母)。我会试着玩一下,并稍后发布我的代码。不过还是谢谢你指引我朝这个方向。 - Inaimathi
与功能完全无关,您需要在@package中加入.com。 - joedborg

11

这是我最终使用的代码(欢迎评论和复制,以满足你自己的恶意目的)。

首先,当用户编辑时,它会使用$(editableElement).lineText(userInput)进行分割。

jQuery.fn.lineText = function (userInput) {
   var a = userInput.replace(/\n/g, " \n<br/> ").split(" ");
   $.each(a, function(i, val) { 
      if(!val.match(/\n/) && val!="") a[i] = '<span class="word-measure">' + val + '</span>';
   });
   $(this).html(a.join(" "));
};

换行符的替换是因为编辑文本框中使用了$(editableElement).text()来填充,它会忽略<br/>标签,但这些标签仍会为排版目的更改以下行的高度。 这不是最初目标的一部分,只是比较容易实现的功能。

当我需要提取格式化的文本时,我调用$(editableElement).getLines(),其中

jQuery.fn.getLines = function (){
   var count = $(this).children(".word-measure").length;
   var lineAcc = [$(this).children(".word-measure:eq(0)").text()];
   var textAcc = [];
   for(var i=1; i<count; i++){
      var prevY = $(this).children(".word-measure:eq("+(i-1)+")").offset().top;
      if($(this).children(".word-measure:eq("+i+")").offset().top==prevY){
         lineAcc.push($(this).children(".word-measure:eq("+i+")").text());
   } else {
     textAcc.push({text: lineAcc.join(" "), top: prevY});
     lineAcc = [$(this).children(".word-measure:eq("+i+")").text()];
   }
   }
   textAcc.push({text: lineAcc.join(" "), top: $(this).children(".word-measure:last").offset().top});
   return textAcc;
};
最终的结果是哈希列表,每个哈希包含单行文本的内容和垂直偏移量。
[{"text":"Some dummy set to","top":363},
 {"text":"demonstrate...","top":382},
 {"text":"The output of this","top":420},
 {"text":"wrap-detector.","top":439}]
如果我只想要未经格式化的文本,$(editableElement).text() 仍会返回内容。
"Some dummy set to demonstrate... The output of this wrap-detector."

8
以上的解决方案在出现更复杂结构时不起作用,例如在段落中包含链接(例如,你可以在<p>中使用<b><i><a href></a>)。所以我创建了一个JavaScript库来检测换行符的位置,这个库对这些情况有效:http://github.com/xdamman/js-line-wrap-detector。希望这能有所帮助。

0

我有一个情况,需要将每一行都包裹在一个标签中。这样做是为了给文本块添加填充高亮效果。如果将背景添加到包裹文本的标签中,只会填充文本块的开头和结尾,每一行必须单独包裹。

根据上面的建议,我想出了以下解决方案:

$.fn.highlghtWrap = function () {
    this.each( function () {
      var current = $( this );
      var text = current.text();
      var words = text.split( ' ' );
      var line = '';
      var lines = [];

      current.text( words[ 0 ] );
      var height = current.height();
      line = words[ 0 ];
      for ( var i = 1; i < words.length; i++ ) {
        current.text( current.text() + ' ' + words[ i ] );

        if ( current.height() > height ) {
          lines.push( line );
          line = words[ i ];
          height = current.height();
        } else {
          line = line + ' ' + words[ i ];
        }
      }
      lines.push( line );
      current.html( '' );
      $.each( lines, function ( v, a ) {
        current.html( current.html() + '<span>' + a +
          ' </span>' );
      } );
    } );
  }

  $( '.home-top_wrapper h2' ).highlghtWrap();
  $( '.home-top_wrapper p' ).highlghtWrap();

-1
一个在内部标记和任意字体和样式存在时也能工作的概念上简单的方法是,首先进行第一次遍历,将每个单词放入其自己的元素中(可能是'SPAN',或者像'w'这样的自定义名称)。
然后,您可以使用getBoundingClientRect()迭代查找'top'属性何时发生变化:
function findBreaks() {
    var words = document.getElementsByTagName('w');
    var lastTop = 0;
    for (var i=0; i<words.length; i++) {
        var newTop = words[i].getBoundingClientRect().top;
        if (newTop == lastTop) continue;
        console.log("new line " + words[i].textContent + " at: " + newTop);
        lastTop = newTop;
    }
}

听起来很慢,但除非文档真的很大,否则你不会注意到。


这假设新元素没有任何可能会影响布局的样式。 - Sean
@Sean 从技术上讲是正确的,但是(1)这很容易检查,而且(2)您可以通过明智选择元素类型来避免它。例如,您可以轻松扫描适用的CSS,或使用一个随机编造的元素名称(通常的HTML浏览器完全可以处理)。试试看。 - TextGeek

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