9

有时您的字符串必须适合某个像素宽度。这个函数试图有效地做到这一点。请在下面发布您的建议或重构:)

function fitStringToSize(str,len) {
    var shortStr = str;
    var f = document.createElement("span");
    f.style.display = 'hidden';
    f.style.padding = '0px';
    document.body.appendChild(f);

    // on first run, check if string fits into the length already.
    f.innerHTML = str;
    diff = f.offsetWidth - len;

    // if string is too long, shorten it by the approximate 
    // difference in characters (to make for fewer iterations). 
    while(diff > 0)
    {
        shortStr = substring(str,0,(str.length - Math.ceil(diff / 5))) + '…';
        f.innerHTML = shortStr;
        diff = f.offsetWidth - len;
    }

    while(f.lastChild) {
        f.removeChild(f.lastChild);
    }
    document.body.removeChild(f);

    // if the string was too long, put the original string 
    // in the title element of the abbr, and append an ellipsis
    if(shortStr.length < str.length)
    {
        return '<abbr title="' + str + '">' + shortStr + '</abbr>';
    }
    // if the string was short enough in the first place, just return it.
    else
    {
        return str;
    }
}

更新:@some 下面的解决方案要好得多;请使用它。

更新 2:代码现在作为要点发布;随意分叉并提交补丁:)

4

4 回答 4

22

您的代码有几个问题。

  • 为什么/ 5?字符的宽度取决于font-familyfont-size
  • 您必须在缩写标题中转义str(否则 " 将使代码无效)。
  • diff未声明并在全局范围内结束
  • substring不应该那样工作。你使用的是什么浏览器?
  • hidden不是 的有效值style.display。要隐藏它,您应该使用该值none,但浏览器不计算offsetWidth. 改为使用style.visibility="hidden"
  • 寻找合适的长度是非常低效的。
  • 必须逃走&lt;/abbr&gt;

我为您重写并添加了它,className以便您可以使用样式来设置font-familyand font-size。Fooz 先生建议您使用鼠标悬停来显示整个字符串。这不是必需的,因为现代浏览器会为您执行此操作(已使用 FF、IE、Opera 和 Chrome 进行测试)

    function fitStringToSize(str,len,className) {
    var result = str; // set the result to the whole string as default
    var span = document.createElement("span");
    span.className=className; //Allow a classname to be set to get the right font-size.
    span.style.visibility = 'hidden';
    span.style.padding = '0px';
    document.body.appendChild(span);


    // check if the string don't fit 
    span.innerHTML = result;
    if (span.offsetWidth > len) {
        var posStart = 0, posMid, posEnd = str.length;
        while (true) {
            // Calculate the middle position
            posMid = posStart + Math.ceil((posEnd - posStart) / 2);
            // Break the loop if this is the last round
            if (posMid==posEnd || posMid==posStart) break;

            span.innerHTML = str.substring(0,posMid) + '&hellip;';

            // Test if the width at the middle position is
            // too wide (set new end) or too narrow (set new start).
            if ( span.offsetWidth > len ) posEnd = posMid; else posStart=posMid;
        }
        //Escape
        var title = str.replace("\"","&#34;");
        //Escape < and >
        var body = str.substring(0,posStart).replace("<","&lt;").replace(">","&gt;");
        result = '<abbr title="' + title + '">' + body + '&hellip;<\/abbr>';
    }
    document.body.removeChild(span);
    return result;
    }

编辑:在进行更多测试时,我发现了一些错误。

  • 我使用Math.ceil而不是预期Math.floor(我将此归咎于英语不是我的母语)

  • 如果输入字符串有 html-tags,那么结果将是未定义的(在中间截断标签或留下打开的标签是不好的)

改进:

  • 转义复制到所有位置跨度的字符串。您仍然可以使用 html-entities,但不允许使用标签(<并将>显示)
  • 重写了while-statement(它有点快,但主要是为了摆脱导致额外回合的错误并摆脱break-statement)
  • 将函数重命名为fitStringToWidth

版本 2:

function fitStringToWidth(str,width,className) {
  // str    A string where html-entities are allowed but no tags.
  // width  The maximum allowed width in pixels
  // className  A CSS class name with the desired font-name and font-size. (optional)
  // ----
  // _escTag is a helper to escape 'less than' and 'greater than'
  function _escTag(s){ return s.replace("<","&lt;").replace(">","&gt;");}

  //Create a span element that will be used to get the width
  var span = document.createElement("span");
  //Allow a classname to be set to get the right font-size.
  if (className) span.className=className;
  span.style.display='inline';
  span.style.visibility = 'hidden';
  span.style.padding = '0px';
  document.body.appendChild(span);

  var result = _escTag(str); // default to the whole string
  span.innerHTML = result;
  // Check if the string will fit in the allowed width. NOTE: if the width
  // can't be determined (offsetWidth==0) the whole string will be returned.
  if (span.offsetWidth > width) {
    var posStart = 0, posMid, posEnd = str.length, posLength;
    // Calculate (posEnd - posStart) integer division by 2 and
    // assign it to posLength. Repeat until posLength is zero.
    while (posLength = (posEnd - posStart) >> 1) {
      posMid = posStart + posLength;
      //Get the string from the beginning up to posMid;
      span.innerHTML = _escTag(str.substring(0,posMid)) + '&hellip;';

      // Check if the current width is too wide (set new end)
      // or too narrow (set new start)
      if ( span.offsetWidth > width ) posEnd = posMid; else posStart=posMid;
    }

    result = '<abbr title="' +
      str.replace("\"","&quot;") + '">' +
      _escTag(str.substring(0,posStart)) +
      '&hellip;<\/abbr>';
  }
  document.body.removeChild(span);
  return result;
}
于 2008-11-12T13:55:02.330 回答
3

乍一看,它对我来说看起来不错。以下是一些小建议:

  • 使用二分查找而不是线性查找最佳大小。

  • (可选)添加鼠标悬停,以便工具提示提供完整的字符串。

于 2008-11-12T01:54:27.090 回答
2

您可以编写相同的函数但要适应 div 的宽度和高度吗?我有一个固定宽度和高度的 div,我需要从数据库中放置文本。如果文本对于 div 来说太大,我想剪掉它并在最后做广告...?可能的?谢谢

编辑:我为我的问题找到了 JS 解决方案:

<p id="truncateMe">Lorem ipsum dolor sit amet, consectetuer adipiscing
elit. Aenean consectetuer. Etiam venenatis. Sed ultricies, pede sit
amet aliquet lobortis, nisi ante sagittis sapien, in rhoncus lectus
mauris quis massa. Integer porttitor, mi sit amet viverra faucibus,
urna libero viverra nibh, sed dictum nisi mi et diam. Nulla nunc eros,
convallis sed, varius ac, commodo et, magna. Proin vel
risus. Vestibulum eu urna. Maecenas lobortis, pede ac dictum pulvinar,
nibh ante vestibulum tortor, eget fermentum urna ipsum ac neque. Nam
urna nulla, mollis blandit, pretium id, tristique vitae, neque. Etiam
id tellus. Sed pharetra enim non nisl.</p>

<script type="text/javascript">

var len = 100;
var p = document.getElementById('truncateMe');
if (p) {

  var trunc = p.innerHTML;
  if (trunc.length > len) {

    /* Truncate the content of the P, then go back to the end of the
       previous word to ensure that we don't truncate in the middle of
       a word */
    trunc = trunc.substring(0, len);
    trunc = trunc.replace(/\w+$/, '');

    /* Add an ellipses to the end and make it a link that expands
       the paragraph back to its original size */
    trunc += '<a href="#" ' +
      'onclick="this.parentNode.innerHTML=' +
      'unescape(\''+escape(p.innerHTML)+'\');return false;">' +
      '...<\/a>';
    p.innerHTML = trunc;
  }
}

</script>

出于我的目的,我从...中删除了链接,因为我的页面上有另一个包含完整文本的选项卡。

于 2009-08-09T15:24:54.057 回答
1

幸运的是,CSS3 text-overflow最终应该会解决这个问题。

如果有人使用 ASP.NET 并对服务器端解决方案感兴趣,请查看此博客文章:

http://waldev.blogspot.com/2010/09/truncate-text-string-aspnet-fit-width.html

于 2010-09-08T08:04:40.240 回答