самый надежный способ получить текст из строки размером x пикселей, javascript - PullRequest
11 голосов
/ 07 февраля 2011

У меня есть строка, которая содержит много текста, text , в моем файле JavaScript.У меня также есть элемент, div # container , который стилизован (с использованием отдельного CSS) с потенциально нестандартными line-height, font-size, font-face и, возможно, другими.Имеет фиксированную высоту и ширину.

Я хотел бы получить максимальное количество текста, которое может поместиться в div # container без какого-либо переполнения строки.Каков наилучший способ сделать это?

Это должно быть в состоянии работать с текстом, отформатированным с тегами, например:

<strong>Hello person that is this is long and may take more than a</strong> 
line and so on.

В настоящее время у меня есть плагин JQuery, который работаетдля обычного текста код следующий:

// returns the part of the string that cannot fit into the object
$.fn.func = function(str) {
    var height = this.height();

    this.height("auto");
    while(true) {
        if(str == "") {
            this.height(height);
            return str; // the string is empty, we're done
        }

        var r = sfw(str); // r = [word, rest of String] (sfw is a split first word function defined elsewhere
        var w = r[0], s = r[1];

        var old_html = this.html();
        this.html(old_html + " " + w);

        if(this.height() > height)
        {
            this.html(old_html);
            this.height(height);
            return str; // overflow, return to last working version
        }

        str = s;

    }
}

ОБНОВЛЕНИЕ:

Данные выглядят так:

<ol>
  <li>
     <h2>Title</h2>
     <ol>
        <li>Character</li>
        <ol>
          <li>Line one that might go on a long time, SHOULD NOT BE BROKEN</li>
          <li>Line two can be separated from line one, but not from itself</li>
        </ol>
      </ol>
     <ol>
        <li>This can be split from other</li>
        <ol>
          <li>Line one that might go on a long time, SHOULD NOT BE BROKEN</li>
          <li>Line two can be separated from line one, but not from itself</li>
        </ol>
      </ol>
   </li>  <li>
     <h2>Title</h2>
     <ol>
        <li>Character</li>
        <ol>
          <li>Line one that might go on a long time, SHOULD NOT BE BROKEN</li>
          <li>Line two can be separated from line one, but not from itself</li>
        </ol>
      </ol>
     <ol>
        <li>This can be split from other</li>
        <ol>
          <li>Line one that might go on a long time, SHOULD NOT BE BROKEN</li>
          <li>Line two can be separated from line one, but not from itself</li>
        </ol>
      </ol>
   </li>
</ol>

Ответы [ 5 ]

5 голосов
/ 16 февраля 2011

хорошо, позвольте мне попытаться решить его;) на самом деле, думая о решении, я заметил, что недостаточно знаю о ваших требованиях, поэтому решил разработать простой код JavaScript и показать вам результат;после попытки вы можете сказать мне, что не так, чтобы я мог это исправить / изменить, договориться?

Я использовал чистый JavaScript, никакой jQuery (его можно переписать, если необходимо).Принцип похож на ваш плагин jQuery:

  1. мы берем символы один за другим (вместо слов, как это делает функция sfw; ее можно изменить)
  2. , если она является частьюиз открывающего тега браузер не показывает его, поэтому я не обработал его особым образом, просто добавил один за другим символы из имени тега и проверил высоту контейнера ... не знаю, так ли это плохо.Я имею в виду, что когда я пишу container.innerHTML = "My String has a link <a href='#'"; в браузере, я вижу "My String has a link", поэтому тег "незаконченный" не влияет на размер контейнера (по крайней мере, во всех браузерах, где я тестировал)
  3. проверка размера контейнера,и если он больше, чем мы ожидаем, тогда мы ищем предыдущую строку (фактически текущую строку без последнего символа)
  4. , теперь мы должны закрыть все открывающие теги, которые не закрыты из-завырезать

HTML-страницу для проверки:

<html>

  <head>
    <style>
    div {
      font-family: Arial;
      font-size: 20px;
      width: 200px;
      height: 25px;
      overflow: hidden;
    }
    </style>
  </head>

  <body>
     <div id="container"> <strong><i>Strong text with <a href="#">link</a> </i> and </strong> simple text </div>

     <script>
     /**
      * this function crops text inside div element, leaving DOMstructure valid (as much as possible ;).
      * also it makes visible part as "big" as possible, meaning that last visible word will be split 
      * to show its first letters if possible
      *
      * @param container {HTMLDivElement} - container which can also have html elements inside
      * @return {String} - visible part of html inside div element given
      */
     function cropInnerText( container ) {
       var fullText = container.innerHTML; // initial html text inside container 
       var realHeight = container.clientHeight; // remember initial height of the container 
       container.style.height = "auto"; // change height to "auto", now div "fits" its content 

       var i = 0;
       var croppedText = "";
       while(true) {
         // if initial container content is the same that cropped one then there is nothing left to do
         if(croppedText == fullText) { 
           container.style.height = realHeight + "px";
           return croppedText;
         }

         // actually append fullText characters one by one...    
         var nextChar = fullText.charAt( i );
         container.innerHTML = croppedText + nextChar;  

         // ... and check current height, if we still fit size needed
         // if we don't, then we found that visible part of string
         if ( container.clientHeight > realHeight ) {
           // take all opening tags in cropped text 
           var openingTags = croppedText.match( /<[^<>\/]+>/g );
           if ( openingTags != null ) {
             // take all closing tags in cropped text 
             var closingTags = croppedText.match( /<\/[^<>]+>/g ) || [];
             // for each opening tags, which are not closed, in right order...
             for ( var j = openingTags.length - closingTags.length - 1; j > -1; j-- ) {
               var openingTag; 
               if ( openingTags[j].indexOf(' ') > -1 ) {
                 // if there are attributes, then we take only tag name
                 openingTag = openingTags[j].substr(1, openingTags[j].indexOf(' ')-1 ) + '>';
               }
               else {
                 openingTag = openingTags[j].substr(1);
               }
               // ... close opening tag to have valid html
               croppedText += '</' + openingTag;
             }
           }

           // return height of container back ... 
           container.style.height = realHeight + "px";
           // ... as well as its visible content 
           container.innerHTML = croppedText;
           return croppedText;
         }

         i++;
         croppedText += nextChar;
       }

     }

     var container = document.getElementById("container");
     var str = cropInnerText( container );
     console.info( str ); // in this case it prints '<strong><i>Strong text with <a href="#">link</a></i></strong>'
   </script>

</body>

Возможные улучшения / изменения:

  1. Я не создаю никакихновые элементы DOM, поэтому я просто повторно использую текущий контейнер (чтобы быть уверенным, что я учел все стили CSS);таким образом я постоянно меняю его содержимое, но после взятия видимого текста вы можете написать fullText обратно в контейнер, если это необходимо (что я также не изменяю)
  2. Обработка исходного текста слово за словом позволит нам сделатьменьше изменений в DOM (мы будем писать слово за словом вместо символа за символом), поэтому этот путь должен быть быстрее.У вас уже есть функция sfw, так что вы можете легко ее изменить.
  3. Если у нас есть два слова "our sentence", возможно, что visible будет только первым ("our"), и «предложение» должно быть сокращено (overflow:hidden будет работать таким образом).В моем случае я буду добавлять символ за символом, поэтому мой результат может быть "our sent".Опять же, это не сложная часть алгоритма, поэтому на основе вашего кода плагина jQuery вы можете изменить мой для работы со словами.

Вопросы, замечания, найденные ошибки приветствуются;) Я проверял этов IE9, FF3.6, Chrome 9

ОБНОВЛЕНИЕ: Принимая во внимание проблему с <li>, <h1> ... Например, у меня есть контейнер с содержимым:

<div id="container"> <strong><i>Strong text with <ul><li>link</li></ul> </i> and </strong> simple text </div>

В этом случае браузер ведет себя следующим образом (строка за строкой, что находится в контейнере и что я вижу, оно показывает в соответствии с алгоритмом):

...
"<strong><i>Strong text with <" -> "<strong><i>Strong text with <"
"<strong><i>Strong text with <u" -> "<strong><i>Strong text with "
"<strong><i>Strong text with <ul" -> "<strong><i>Strong text with <ul></ul>" // well I mean it recognizes ul tag and changes size of container

, а результатом алгоритма является строка "<strong><i>Strong text with <u</i></strong>" - с "<u"что не приятно.В этом случае мне нужно обработать то, что если мы нашли нашу строку результата ("<strong><i>Strong text with <u" в соответствии с алгоритмом), нам нужно удалить последний «незакрытый» тег (в нашем случае "<u"), поэтому перед закрытием тегов нужно иметьДействительный HTML Я добавил следующее:

...
if ( container.clientHeight > realHeight ) {
  /* start of changes */
  var unclosedTags = croppedText.match(/<[\w]*/g);
  var lastUnclosedTag = unclosedTags[ unclosedTags.length - 1 ];
  if ( croppedText.lastIndexOf( lastUnclosedTag ) + lastUnclosedTag.length == croppedText.length ) {
    croppedText = croppedText.substr(0, croppedText.length - lastUnclosedTag.length );
  }
  /* end of changes */
  // take all opening tags in cropped text 
...

Возможно, немного ленивая реализация, но она может быть настроена, если она замедляется.Что здесь сделано

  1. взять все теги без > (в нашем случае возвращает ["<strong", "<i", "<u"]);
  2. взять последний ("<u")
  3. , если это конец строки croppedText, то мы удалим его

после этого, строка результатастановится "<strong><i>Strong text with </i></strong>"

UPDATE2 спасибо, например, так что я вижу, что у вас есть не только вложенные теги, но они также имеют "древовидную" структуру, на самом деле я не взялэто во внимание, но это все еще можно исправить;) Вначале я хотел написать свой соответствующий «парсер», но все время получаю пример, когда у меня не работает, поэтому я подумал, что лучше найти уже написанный парсери есть один: Чистый JavaScript HTML Parser .Существует также один shag :

Хотя эта библиотека не охватывает весь спектр возможных странностей, которые предоставляет HTML, она обрабатывает множество наиболее очевидных вещей.

но для вашего примера это работает;эта библиотека не учитывала положение открывающего тега, но

  1. мы полагаем, что оригинальная структура HTML в порядке (не нарушена);
  2. мы закрываем теги в конце результирующей «строки» (так что все в порядке)

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

<script src="http://ejohn.org/files/htmlparser.js"></script>
 <script>
 function cropInnerText( container ) {
   var fullText = container.innerHTML;
   var realHeight = container.clientHeight;
   container.style.height = "auto";

   var i = 0;
   var croppedText = "";
   while(true) {
     if(croppedText == fullText) { 
       container.style.height = realHeight + "px";
       return croppedText;
     }

     var nextChar = fullText.charAt( i );
     container.innerHTML = croppedText + nextChar;  

     if ( container.clientHeight > realHeight ) {
       // we still have to remove unended tag (like "<u" - with no closed bracket)
       var unclosedTags = croppedText.match(/<[\w]*/g);
       var lastUnclosedTag = unclosedTags[ unclosedTags.length - 1 ];
       if ( croppedText.lastIndexOf( lastUnclosedTag ) + lastUnclosedTag.length == croppedText.length ) {
         croppedText = croppedText.substr(0, croppedText.length - lastUnclosedTag.length );
       }

       // this part is now quite simple ;)
       croppedText = HTMLtoXML(croppedText);

       container.style.height = realHeight + "px";
       container.innerHTML = croppedText ;
       return croppedText;
     }

     i++;
     croppedText += nextChar;
   }

 }
 </script>
5 голосов
/ 07 февраля 2011

Чтобы получить максимально длинную первую строку:

  1. Создайте DIV с visibility:hidden; (чтобы он имел размерность), но поместите его как position:absolute;, чтобы он не нарушал ваш поток страниц
  2. установить его стиль шрифта на те же значения, что и полученный в результате DIV
  3. Установить его высоту так же, как в результате DIV, но сохранить width:auto;
  4. Добавить текст к нему
  5. Продолжайте обрезать текст до тех пор, пока ширина не упадет ниже полученной ширины DIV.

В результате вы можете ввести текст.

Настройте алгоритмесли вам нужно найти количество строк , которые помещаются в контейнер, чтобы сохранить height:auto; и установить фиксированное значение width.

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

1 голос
/ 15 февраля 2011

Чтобы решить эту проблему, вам понадобится дополнительная информация:

  1. где я должен «порезать» введенный текст
  2. , порезав его, как мне восстановить двапополам, чтобы я мог вставить каждый из них в DIV?

Что касается вопроса «где рубить», вам, вероятно, придется вводить уникальные <a name="uniq"/> якорные теги в стратегических точках ввода.строка (скажем ... перед каждым открывающим тегом на входе?).Затем вы можете проверить расположение каждого якоря и найти, где прервать ввод.

Найдя наиболее логичную точку для разрыва, вам нужно будет добавить теги в конце первой половины.чтобы закрыть его, и добавьте теги в начале следующей половины, чтобы открыть его.Поэтому, когда вы анализировали входную строку, чтобы найти открывающие теги ранее, вы сохраняли список «стека тегов», когда вводили <a/>.Поиск стека тегов, который подходит для этого примитива, а затем добавьте теги по мере необходимости.

Я могу выделить 2 ошибки с этим:

  1. вам нужно будет хранить больше информации о каждомпрервать, если входные теги имеют атрибуты
  2. , вам может потребоваться обработать некоторые теги как «неразрушимые» и разбить на более ранние <a/> вместо

В конечном счете, мне кажется, что вы 'ждем конструкцию столбца HTML5.

0 голосов
/ 17 февраля 2011

Вы можете использовать getComputedStyle , чтобы узнать ширину встроенного элемента (то есть он имеет свойство display: inline):

window.getComputedStyle(element, null).getPropertyValue("width");

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

0 голосов
/ 15 февраля 2011

Вы просто пытаетесь отформатировать первую строку в абзаце? Будет ли CSS : первая строка псевдо-селектор работать для вашего приложения?

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