从字符串,javascript获取x像素值文本的最可靠方法

时间:2011-02-07 00:35:30

标签: javascript html formatting

我的JavaScript文件中有一个包含大量文本 text 的字符串。我还有一个元素, div#container ,它使用可能不标准的line-heightfont-sizefont-face以及其他可能非标准<strong>Hello person that is this is long and may take more than a</strong> line and so on. 进行样式设置(使用单独的CSS)。它有一个固定的高度和宽度。

我想获得可容纳 div#container 的最大文本量,而不会从字符串中溢出。这样做的最佳方式是什么?

这需要能够处理使用标签格式化的文本,例如:

// 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;

    }
}

目前,我有一个适用于纯文本的JQuery插件,代码如下:

<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>

更新:

数据如下所示:

{{1}}

5 个答案:

答案 0 :(得分:5)

获得最长的第一行:

  1. 使用visibility:hidden;创建一个DIV(因此它将具有维度),但将其定位为position:absolute;,以便它不会破坏您的页面流量
  2. 将其类型样式设置为与生成的DIV
  3. 相同的值
  4. 将其高度设置为与生成的DIV相同,但保留width:auto;
  5. 向其添加文字
  6. 继续剪切文字,直到宽度低于DIV的宽度。
  7. 结果是您可以放入的文字。

    如果您需要查找适合容器的行数,请调整算法以保留height:auto;并设置固定width

    自动调整textareas使用相同的技术,当用户输入文本时,textareas会自动增长。

答案 1 :(得分:5)

好吧,让我试着解决它;)实际上考虑解决方案我注意到我对你的要求知之甚少,所以我决定开发简单的JavaScript代码并向你展示结果;在尝试之后你可以告诉我什么是错的,所以我可以修理/更改它,交易?

我使用纯JavaScript,没有jQuery(如果需要可以重写)。原理类似于你的jQuery插件:

  1. 我们逐个接受字符(而不是像sfw函数那样的字;它可以更改)
  2. 如果它是开始标记的一部分,浏览器不会显示它,所以我没有特别处理它,只是从标记名称和容器的检查高度逐个添加...不知道是否是那个坏。我的意思是当我在浏览器中写container.innerHTML = "My String has a link <a href='#'";时,我看到“My String has a link”,因此“未完成”标签不会影响容器的大小(至少在我测试过的所有浏览器中)
  3. 检查容器的大小,如果它比我们预期的要大,那么之前的字符串(实际上是没有最后一个字符的当前字符串)就是我们要找的东西
  4. 现在我们必须关闭所有因切割而未关闭的开始标记
  5. 测试它的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",则可见只有第一个("our"),并且应该删除“句子”(overflow:hidden将以这种方式工作) 。在我的情况下,我会按字符追加字符,因此我的结果可以是"our sent"。同样,这不是算法的复杂部分,因此根据您的jQuery插件代码,您可以更改我的单词。
    4. 问题,评论,发现的错误是受欢迎的;)我在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字符串的结尾,那么我们将其删除
      4. 完成后,结果字符串变为"<strong><i>Strong text with </i></strong>"

        UPDATE2 谢谢你的例子,所以我看到你没有嵌套标签,但它们也有“树”结构,实际上我没有考虑到它,但它仍然可以修复;)一开始我想编写适当的“解析器”,但是当我不工作的时候我总是得到一个例子,所以我认为最好找到已编写的解析器,并且有一个: Pure JavaScript HTML Parser。它还有一个 shag

          

        虽然这个库没有覆盖   完全可能的奇怪之处   HTML提供,它确实处理了很多   最明显的东西。

        但是你的例子可行;该库未考虑开放标记的位置,但

        1. 我们依赖原始的html结构很好(没有破坏);
        2. 我们在结果“string”的末尾关闭标签(所以这没关系)
        3. 我认为有了这个假设,这个库很好用。然后结果函数看起来像:

          <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>
          

答案 2 :(得分:1)

要解决此问题,您需要其他信息:

  1. 我应该在哪里“切断”输入文字
  2. 切碎了它,我如何修复这两半,以便我可以把每一个都塞进DIV?
  3. 对于'切到哪里'的问题,你可能需要在输入字符串的关键点注入唯一的<a name="uniq"/>锚标记(比如......在输入中的每个开始标记之前?)。然后,您可以测试每个锚点的布局位置,并找到打破输入的位置。

    找到最合理的断点,你需要在前半部分的末尾添加标签以关闭它,并在下半部分的前面添加标签以打开它。因此,当您解析输入字符串以便先找到开始标记时,您在注入<a/>时会保留一个“标记堆栈”列表。查找与此paritcular相关的标记堆栈,然后根据需要添加标记。

    我可以发现2个陷阱:

    1. 如果输入标记具有属性
    2. ,则需要保留有关每个中断的更多信息
    3. 您可能需要将某些代码视为“牢不可破”,并在较早的<a/>处打破
    4. 最终,在我看来,你正在等待HTML5的列构造。

答案 3 :(得分:0)

您是否只想在段落中设置第一行的格式? CSS :first-line伪选择器是否适用于您的应用程序?

答案 4 :(得分:0)

您可以使用getComputedStyle找出内联元素的宽度(即它具有display: inline属性):

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

如果事实证明此元素的宽度大于其容器的宽度,则会逐步删除一些文本,直到它适合。