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


我有一个包含大量文本的字符串, 文本,在我的JavaScript文件中。我也有一个元素, DIV#集装箱 使用可能非标准的样式(使用单独的CSS) line-heightfont-sizefont-face,也许还有其他人。它有一个固定的高度和宽度。

我想获得适合的最大文本量 DIV#集装箱 没有任何字符串溢出。这样做的最佳方法是什么?

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

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

4558
2018-02-07 00:35


起源

我认为你必须填充(一个看不见的副本)容器,直到它破裂 - Pekka 웃
你的意思是你不能只是设置 overflow:hidden; 在上面? - Robert Koritnik
不,我必须知道它显示了多少。 - Aaron Yodaiken
@Leigh:它类似但不重复。 - Robert Koritnik
和这个? stackoverflow.com/questions/4929107/... - JCOC611


答案:


好吧,让我试着解决它;)实际上考虑解决方案我注意到我对你的要求知之甚少,所以我决定开发简单的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",有可能只有第一个可见("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解析器。还有一个 粗毛 它:

虽然这个图书馆没有涵盖   完全可能的奇怪之处   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
2018-02-16 19:50



我不只是有链接......我有类似的东西 <li> 和 <h2> 在实际改变长度的内容中,我需要使用它。此外,jQuery或普通的javascript工作...无论如何更容易,但我仍然使用jQuery。 - Aaron Yodaiken
@aharon:我看到一个问题,我试图解决它,如果它不起作用,或者你有另一个“破坏”的例子,让我知道 - Maxym
@aharon:关于最后更改的任何反馈?仍然不起作用?或许我误解了你并向错误的方向前进? - Maxym
好吧,问题是整个事情都处于一个未封闭的标签中。我会更准确地了解数据:它看起来像是编辑过的问题。 - Aaron Yodaiken
这并不适用于此。抱歉模糊不清,非常感谢! - Aaron Yodaiken


要获得最长的第一行:

  1. 用它创建一个DIV visibility:hidden; (因此它将具有尺寸)但将其定位为 position:absolute; 所以它不会破坏你的页面流
  2. 将其类型样式设置为与结果相同的值 DIV
  3. 设置它的高度与结果相同 DIV 但保持 width:auto;
  4. 添加文字
  5. 保持切断文字直到宽度下降到最终结果 DIV的宽度。

结果是您可以放入的文本。

如果需要查找数量,请调整算法 线 适合容器保存 height:auto; 并设置固定 width

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


5
2018-02-07 00:41



如果我在文本中有标签,这种方法是不是真的有用,有没有一种很好的方法来处理它们? - Aaron Yodaiken
@aharon:这让事情变得更复杂,但仍然可以完成。可以容器 DIV 有内容与任何标签或标签集限制在一定数量?后者更容易解决。 - Robert Koritnik
不幸的是,任何数字。 - Aaron Yodaiken
@aharon:我想“筑巢?”是一个我不应该问的问题,因为你会激烈地说“是的,甚至是深度筑巢”。 ;) - Robert Koritnik
嵌套最多5个级别将是标准用例:|抱歉让事情变得困难! - Aaron Yodaiken


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

  1. 我应该在哪里“切”输入文字
  2. 切碎它,我该如何修复这两半,以便我可以把每一个都塞进DIV?

至于“在哪里砍”问题,你可能需要注入独特的 <a name="uniq"/> 在输入字符串中的关键点处锚定标记(比如......在输入中的每个开始标记之前?)。然后,您可以测试每个锚点的布局位置并找到中断输入的位置。

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

我可以发现2个陷阱:

  1. 如果输入标签具有属性,则需要保留有关每个中断的更多信息
  2. 您可能需要将某些标签视为“牢不可破”并在之前进行中断 <a/> 代替

最终,在我看来,你正在等待HTML5的列构造。


1
2018-02-15 02:58





你只是想格式化 第一行 在一个段落?请问CSS :第一行 伪选择器适用于您的应用程序?


0
2018-02-15 00:47



对不起,什么?我很困惑。 - Aaron Yodaiken
为什么 你想找到'最大量的文本,可以适应div#容器没有任何溢出'? - David Bullock
因为我想把大量的数据放到横跨页面的“页面”中。 - Aaron Yodaiken
也许这个问题会更好地称为“如何在浏览器中将HTML文本转换为多个固定大小的DIV?” - David Bullock


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

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

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


0
2018-02-17 01:56