【问题标题】:Trimming text to a given pixel width in SVG在SVG中将文本修剪为给定的像素宽度
【发布时间】:2012-03-03 17:18:34
【问题描述】:

我在 SVG 中绘制文本标签。我有一个可用的固定宽度(比如 200 像素)。文字太长,怎么剪?

理想的解决方案还会在文本被剪切的位置添加省略号 (...)。但我也可以没有它。

【问题讨论】:

  • 您的目标是什么 SVG 引擎?土坯?网络套件?火狐? IE9?
  • 它是一个网络应用程序,所以我的目标是WebKit、Firefox、IE9。

标签: svg


【解决方案1】:

我的方法与 OpherV 类似,但我尝试使用 JQuery 进行此操作

function getWidthOfText(text, fontSize, fontFamily) {
    var span = $('<span></span>');
    span.css({
       'font-family': fontFamily,
        'font-size' : fontSize
    }).text(text);
    $('body').append(span);
    var w = span.width();
    span.remove();
    return w;
}

function getStringForSize(text, size, fontSize, fontFamily) {
    var curSize = getWidthOfText(text, fontSize, fontFamily);
    if(curSize > size)
    {
        var curText = text.substring(0,text.length-5) + '...';
        return getStringForSize(curText, size, fontSize, fontFamily);
    }
    else
    {
        return text;
    }
}
&lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"&gt;&lt;/script&gt;

现在当调用getStringForSize('asdfasdfasdfasdfasdfasdf', 110, '13px','OpenSans-Light') 时,你会得到“asdfasdfasdfasd...”

【讨论】:

    【解决方案2】:

    有几种变体使用 d3 和循环来搜索适合的较小文本。这可以在没有循环的情况下实现,并且工作得更快。 textNode - d3 节点。

    clipText(textNode, maxWidth, postfix) {
            const textWidth = textNode.getComputedTextLength();       
            if (textWidth > maxWidth) {
                let text = textNode.textContent;
                const newLength = Math.round(text.length * (1 - (textWidth - maxWidth) / textWidth));
                text = text.substring(0, newLength);
                textNode.textContent = text.trim() + postfix;
            }
        }
    

    【讨论】:

    • 这通常不起作用,因为字符可以有不同的宽度。在这种情况下,不能通过按比例缩放来获得减小的长度。不过对于等宽字体来说,这是一个不错的选择。
    • @bluenote10,你是对的,但我认为 1-2-3 符号的差异在大多数情况下并不是主要的。例如,我用它来修剪图表上过长的标题(超过 30 个符号)。我从使用循环的解决方案开始,但如果我有很多标题(我的应用程序的正常情况),它会在图表更新中增加明显的延迟。我认为在大多数情况下,在 JavaScript 应用程序中,速度比准确性更重要。
    • 对于符号是的,但是如果字符串总共包含 10 个 IIIIIIIIII 对 10 个 WWWWWWWWWW 怎么办?它仅取决于用例。如果您的设计师希望所有字符串的长度尽可能接近,您必须测量文本。另请注意,您可以在内部缓存这些字符串长度测量值,这在许多情况下也会提供出色的性能。
    • @bluenote10,你完全正确,但我认为你描述了罕见的情况,在大多数情况下,像这样的函数用于常规文本。无论如何,您描述了我的方法的缺点,如果有人尝试使用它,这将有所帮助,谢谢。
    【解决方案3】:

    linearGradient 元素可用于生成纯 SVG 解决方案。此示例淡出截断的文本(无省略号):

    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
      <defs>
        <linearGradient gradientUnits="userSpaceOnUse" x1="0" x2="200" y1="0" y2="0" id="truncateText">
          <stop offset="90%" stop-opacity="1" />
          <stop offset="100%" stop-opacity="0" />
        </linearGradient>
        <linearGradient id="truncateLegendText0" gradientTransform="translate(0)" xlink:href="#truncateText" />
        <linearGradient id="truncateLegendText1" gradientTransform="translate(200)" xlink:href="#truncateText" />
      </defs>
    
      <text fill="url(#truncateLegendText0)" font-size="50" x="0" y="50">0123456789</text>
      <text fill="url(#truncateLegendText1)" font-size="50" x="200" y="150">0123456789</text>
      
    </svg>

    (我不得不使用线性渐变来解决这个问题,因为我使用的 SVG 渲染器不支持 textPath 解决方案。)

    【讨论】:

    • 这比我使用 Angular 的任何 JS 选项都好用。 Angular 正在干扰并导致截断和全文的可怕无限循环闪烁
    【解决方案4】:

    试试这个,我在我的图表库中使用了这个功能:

    function textEllipsis(el, text, width) {
      if (typeof el.getSubStringLength !== "undefined") {
        el.textContent = text;
        var len = text.length;
        while (el.getSubStringLength(0, len--) > width) {}
        el.textContent = text.slice(0, len) + "...";
      } else if (typeof el.getComputedTextLength !== "undefined") {
        while (el.getComputedTextLength() > width) {
          text = text.slice(0,-1);
          el.textContent = text + "...";
        }
      } else {
        // the last fallback
        while (el.getBBox().width > width) {
          text = text.slice(0,-1);
          // we need to update the textContent to update the boundary width
          el.textContent = text + "...";
        }
      }
    }
    

    【讨论】:

    • 改了很多,但是原来的sn-p就是这个。
    【解决方案5】:

    @user2846569 告诉我怎么做(是的,使用 d3.js)。但是,我必须做一些小改动才能工作:

    
             function wrap( d ) {
                    var self = d3.select(this),
                        textLength = self.node().getComputedTextLength(),
                        text = self.text();
                    while ( ( textLength > self.attr('width') )&& text.length > 0) {
                        text = text.slice(0, -1);
                        self.text(text + '...');
                        textLength = self.node().getComputedTextLength();
                    }
                }
                svg.append('text')
                    .append('tspan')
                    .text(function(d) { return d; }) 
                    .attr('width', 200 )
                    .each( wrap );
    

    【讨论】:

    • @user2846569 的解决方案具有不存在的函数变量。请改用此修改后的解决方案。
    【解决方案6】:

    使用 d3 库

    溢出文本的包装函数:

        function wrap() {
            var self = d3.select(this),
                textLength = self.node().getComputedTextLength(),
                text = self.text();
            while (textLength > (width - 2 * padding) && text.length > 0) {
                text = text.slice(0, -1);
                self.text(text + '...');
                textLength = self.node().getComputedTextLength();
            }
        } 
    

    用法:

    text.append('tspan').text(function(d) { return d.name; }).each(wrap);
    

    【讨论】:

    • 问题没有提到d3。
    • ... 但它对遇到同样问题并使用 d3 的人有帮助 :)
    • 您应该将该答案放入 Gist 或其他内容中,以便下次可以更快地copy paste
    【解决方案7】:

    实施 Erik 的第三个建议我想出了这样的事情:

    //places textString in textObj, adds an ellipsis if text can't fit in width
    function placeTextWithEllipsis(textObj,textString,width){
        textObj.textContent=textString;
    
        //ellipsis is needed
        if (textObj.getSubStringLength(0,textString.length)>=width){
            for (var x=textString.length-3;x>0;x-=3){
                if (textObj.getSubStringLength(0,x)<=width){
                    textObj.textContent=textString.substring(0,x)+"...";
                    return;
                }
            }
            textObj.textContent="..."; //can't place at all
        }
    }
    

    似乎可以解决问题:)

    【讨论】:

    • 这对我有用,但你必须确保节点已经在 dom 中呈现。所以在动态添加节点时要小心。
    • @borisrorsvort 实际上我遇到了同样的问题。你有没有想出在非渲染节点上运行它的解决方案?
    • 在经典更新函数结束时,我循环所有节点,然后使用您的函数。然后当新节点进来时,我重新运行更新方法。 gist.github.com/borisrorsvort/cc6ada7e4cbfc7f959b9
    【解决方案8】:

    一种方法是使用textPath 元素,因为所有脱离路径的字符都会被自动剪掉。请参阅 SVG 测试套件中的文本路径 examples

    另一种方法是在 svg 文本元素上使用 CSS3 text-overflow,例如 here。 Opera 11 支持这一点,但您可能会发现其他浏览器目前仅在 html 元素上支持它。

    您也可以测量文本字符串并使用脚本自己插入省略号,我建议在文本元素上使用getSubStringLength 方法,增加 nchars 参数直到找到合适的长度。

    【讨论】:

    • CSS3 示例不适合我。文本溢出或文本路径似乎都没有剪裁溢出。有什么变化吗?
    • @DavidMeza 遗憾的是 'text-overflow'(仍然)在 svg 文本元素中不受支持,bugs.chromium.org/p/chromium/issues/detail?id=366550
    • @ErikDahlström 谢谢,第一种方法效果很好!我稍微摆弄了一下,因为我第一次尝试没有在 xlink:href - id 关联中使用非常独特的值。
    • 请注意,要让textPath[xlink:href] 处理带有base[href] 的文档,您需要对路径使用绝对href
    猜你喜欢
    • 1970-01-01
    • 2012-01-13
    • 2013-01-25
    • 2010-12-10
    • 2017-01-31
    • 2016-05-16
    • 1970-01-01
    • 2010-11-23
    • 1970-01-01
    相关资源
    最近更新 更多