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

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

最佳答案

好吧,让我尝试解决它;) 在实际思考解决方案时,我注意到我对您的需求了解得不够多,所以我决定开发简单的 JavaScript 代码并向您展示结果;尝试之后,您可以告诉我出了什么问题,以便我可以修复/更改它,交易?

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

  • 我们一个一个地取字符(而不是像 sfw 函数那样的单词;它可以改变)
  • 如果它是打开标签的一部分,浏览器不会显示它,所以我没有特殊处理它,只是从标签名称和检查容器的高度一个一个字符附加...不知道它是否那么糟糕。我的意思是当我写 container.innerHTML = "My String has a link <a href='#'";在浏览器中我看到“My String has a link”,所以“未完成”标签不会影响容器的大小(至少在我测试的所有浏览器中)
  • 检查容器的大小,如果它比我们预期的要大,那么前一个字符串(实际上没有最后一个字符的当前字符串)就是我们要查找的
  • 现在我们必须关闭所有因为切割而没有关闭的打开标签

  • 测试它的 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>
    

    可能的改进/变化:
  • 我没有创建任何新的 DOM 元素,所以我只是重用了当前的容器(确保我考虑了所有的 css 样式);这样我一直在改变它的内容,但是在获取可见文本后你可以写fullText如果需要(我也不会更改),请放回容器中
  • 逐字处理原始文本会让我们对DOM的改动更少(我们将逐字而不是逐字写),所以这种方式应该更快。您已经拥有 sfw功能,因此您可以轻松更改它。
  • 如果我们有两个词 "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 
    ...
    

    可能有点懒惰的实现,但如果它变慢,可以对其进行调整。这里做了什么
  • 不带 > 取走所有标签(在我们的例子中,它返回 ["<strong", "<i", "<u"] );
  • 取最后一个 ( "<u" )
  • 如果结束 croppedText字符串,然后我们将其删除

  • 这样做后,结果字符串变成 "<strong><i>Strong text with </i></strong>"
    更新2 例如谢谢你,所以我看到你不仅有嵌套标签,而且它们也有“树”结构,确实我没有考虑到它,但它仍然可以修复;) 一开始我想要写我合适的“解析器”,但是我总是在我不工作的时候得到一个例子,所以我认为最好找到已经写好的解析器,并且有一个:Pure JavaScript HTML Parser .还有一个问题:

    While this library doesn't cover the full gamut of possible weirdness that HTML provides, it does handle a lot of the most obvious stuff.



    但对于你的例子,它有效;该库没有考虑开始标签的位置,但是
  • 我们相信原始 html 结构很好(没有损坏);
  • 我们在结果“字符串”的末尾关闭标签(所以这是可以的)

  • 我认为有了这个假设,这个库就很好用了。然后结果函数看起来像:
    <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>
    

    https://stackoverflow.com/questions/4917244/

    相关文章:

    c++ - clang-format:总是打破所有参数,每行一个

    c# - 日期时间格式,如 HH :mm 24 Hours without AM/PM

    java - Eclipse 格式化程序 : can it ignore annotations?

    visual-studio - Visual Studio 格式化 - 更改方法颜色

    SQL Server 2005 For XML Explicit - 需要帮助格式化

    android - 如何格式化GPS经纬度?

    c# - 如何拆分保留整个单词的字符串?

    php - 在 PHP 中从 SQL 格式化时间戳的最简单方法是什么?

    java - 任何可用于 java 文件的命令行格式工具?

    c# - 如何用零填充二进制字符串?