it-roy-ru.com

Нахождение оберток

Предположим, у меня есть какой-то случайный блок текста в одной строке. Вот так

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Но по какой-либо причине (настройки ширины для содержащего элемента, использование text-zoom и т.д.) На экране зрителя он отображается в виде двух или более строк.

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. независимо от того, как отображается текст.

39
Inaimathi

Вот то, что я в конечном итоге использовал (не стесняйтесь критиковать и копировать для ваших собственных гнусных целей).

Прежде всего, когда редактирование приходит от пользователя, оно разбивается на $(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."
11
Inaimathi

Что ж, если вы хотите что-то смехотворно простое и, вероятно, слишком бесполезное для вас (это потребует серьезной модификации, если у вас есть какой-либо 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/

26
Yi Jiang

Для варианта использования, такого как генерация PDF.

Вы можете ограничить количество символов в строке, если происходит разделение среднего слова, отрегулируйте соответствующим образом.

Чтобы получить более точные символы в строке, вы можете использовать моноширинные шрифты, а затем определить ширину каждого символа для каждого разрешенного шрифта. Затем разделите ширину символов на размер допустимой ширины строки текста, и у вас будет разрешенные символы на строку для этого шрифта.

Вы можете использовать не моноширинные шрифты, но тогда вам придется измерять ширину каждой буквы - тьфу. Способ, позволяющий автоматизировать определение ширины, заключается в том, чтобы иметь диапазон без полей или отступов, добавить каждый символ для каждого шрифта (и размера), затем измерить ширину диапазона и использовать его.

Я сделал код:

/**
 * 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'
        });
    }

});
11
balupton

Приведенные выше решения не работают, если у вас есть более сложная структура, такая как ссылка в абзаце (например, у вас может быть <b><i><a href></a> внутри <p>).

Поэтому я создал библиотеку javascript для определения места переноса строк, которое работает в таких случаях: http://github.com/xdamman/js-line-wrap-detector

Надеюсь, это поможет.

7
xdamman

У меня есть ситуация, когда мне нужно обернуть каждую строку в промежутке. Я делаю это так, чтобы я мог добавить эффект подсветки в текстовом блоке. Добавление фона к тегу span, заключающему в себя текст, будет дополнять только начало и конец текстового блока, каждая строка должна быть обернута индивидуально.

Это то, что я придумал, основываясь на предложениях выше:

$.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();
0
user3761817

Концептуально простой способ, который также работает, когда есть внутренняя разметка и произвольные шрифты и стили, состоит в том, чтобы сделать первый проход, который просто помещает каждое Слово в его собственный элемент (возможно, «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;
    }
}

Звучит медленно, но если документы не очень большие, вы не заметите.

0
TextGeek