使用JavaScript计算文字宽度


466

我想使用JavaScript计算字符串的宽度。是否可以不必使用等宽字体?

如果不是内置的,我唯一的想法就是为每个字符创建一个宽度表,但这是非常不合理的,特别是支持Unicode和不同类型的大小(以及与此相关的所有浏览器)。


10
请注意,如果您使用的是外部字体,则在加载后必须使用以下技术。如果您已缓存它们或安装了本地版本,则可能没有意识到。
塞思·克莱因

Answers:


355

创建具有以下样式的DIV。在JavaScript中,设置要测量的字体大小和属性,将字符串放入DIV中,然后读取DIV的当前宽度和高度。它将拉伸以适合内容,并且大小将在字符串呈现大小的几个像素之内。

var fontSize = 12;
var test = document.getElementById("Test");
test.style.fontSize = fontSize;
var height = (test.clientHeight + 1) + "px";
var width = (test.clientWidth + 1) + "px"

console.log(height, width);
#Test
{
    position: absolute;
    visibility: hidden;
    height: auto;
    width: auto;
    white-space: nowrap; /* Thanks to Herb Caudill comment */
}
<div id="Test">
    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
</div>


8
我唯一要补充的是,根据使用的样式,这可能会给出错误的尺寸。请记住,您可能有类似p {letter-spacing:0.1em;的样式。} DIV元素无法反映。您必须确保适当的样式适合您将在其中使用文本的地方。
吉姆

5
同上的吉姆(Ditto Jim)的评论-仔细检查以确保容器(在这种情况下为div)没有通过您当时可能不了解的CSS选择规则应用于其上的任何其他样式。在测量之前应用您关注的样式,然后从容器中剥离所有相关样式。
詹森·邦廷

44
如果您认为文本会超出浏览器宽度,则还应该输入white-space:nowrap。
Herb Caudill

5
@BBog到处都是骇客,这就是为什么我建议在楼下采取一种不那么怪异的方法。在这里看看。
米2014年

11
只是好奇+1每个维度的用途是什么?
2014年

385

HTML 5中,您可以仅使用Canvas.measureText方法此处有更多说明)。

试试这个小提琴

/**
 * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
 * 
 * @param {String} text The text to be rendered.
 * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
 * 
 * @see /programming/118241/calculate-text-width-with-javascript/21015393#21015393
 */
function getTextWidth(text, font) {
    // re-use canvas object for better performance
    var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
    var context = canvas.getContext("2d");
    context.font = font;
    var metrics = context.measureText(text);
    return metrics.width;
}

console.log(getTextWidth("hello there!", "bold 12pt arial"));  // close to 86

这个小提琴将这种Canvas方法与Bob Monteverde基于DOM的方法进行了比较,因此您可以分析和比较结果的准确性。

这种方法有几个优点,包括:

  • 比其他(基于DOM)方法更简洁,更安全,因为它不会更改全局状态(例如DOM)。
  • 通过修改更多的画布文本属性(例如textAlign和),可以进行进一步的自定义textBaseline

注意:将文本添加到DOM时,请记住还要考虑padding,margin和border

注意2:在某些浏览器上,此方法产生子像素精度(结果是浮点数),在其他浏览器上则没有(结果只是整数)。您可能要对结果运行Math.floor(或Math.ceil),以避免不一致。由于基于DOM的方法永远不会精确到子像素,因此此方法比此处的其他方法具有更高的精度。

根据这个jsperf的介绍(感谢评论中的贡献者),如果将缓存添加到基于DOM的方法中并且您没有使用Firefox ,那么Canvas方法基于DOM的方法的速度大约相等。在Firefox中,由于某种原因,此Canvas方法基于DOM的方法(截至2014年9月)要快得多。


7
这是一个很棒的选择,我不知道。有没有将其性能与DOM中的测量性能进行比较?
SimplGy 2014年

2
我将缓存添加到该jsperf基准测试中。现在,这两种方法几乎可以相提并论:jsperf.com/measure-text-width/4
Brandon Bloom

1
@Martin Huh?“许多HTML元素”与这里的内容有什么关系?此示例中的canvas对象甚至没有附加到DOM。甚至除此之外,更改画布绘图上下文中的字体甚至根本不会影响画布,直到您strokeText()fillText()。也许您想评论其他答案?
Ajedi32

1
不错的解决方案。我在Domi的答案周围还包装了其他一些方法,这样我可以-如果在给定的空间不适合它,则在结尾处得到一个(可能)截断的字符串,并带有省略号(...)(尽可能多的字符串)尽可能使用)-传递一个包含(可能是截断的)字符串的JQuery元素,并动态确定Font属性,这样就不必对其进行硬编码,从而允许CSS字体属性进行更改而不会中断布局。JSFiddle此处:jsfiddle.net/brebey/1q94gLsu/6/embed
BRebey '16

2
不错,因为用户看不到!
clabe45

110

这是我一起举过的一个例子。看起来我们都在同一页面上。

String.prototype.width = function(font) {
  var f = font || '12px arial',
      o = $('<div></div>')
            .text(this)
            .css({'position': 'absolute', 'float': 'left', 'white-space': 'nowrap', 'visibility': 'hidden', 'font': f})
            .appendTo($('body')),
      w = o.width();

  o.remove();

  return w;
}

使用它很简单: "a string".width()

**添加后white-space: nowrap,可以计算宽度大于窗口宽度的字符串。


2
感谢JordanC。我要补充的一件事是,如果您在同一页面上多次调用此命令,而性能是一个问题,则可以保留DIV,而只需更改内容并检查宽度。我只是做了这样所以一旦一切都说过和做过,DOM的将是一样的,当你开始
鲍勃蒙特韦尔德

3
我不会添加此方法,String因为它会减少程序关注点分离(分离核心代码与UI代码)。想象一下,您想将核心代码移植到具有非常不同的UI框架的平台上……
Domi 2014年

23
这是一个糟糕的设计,不仅会使模型与视图耦合,还会与jQuery直接耦合。最重要的是,这是回流的地狱,这肯定无法很好地扩展。
2014年

1
如果需要浮点值,则可以o[0].getBoundingClientRect().width按照此处的建议使用:stackoverflow.com/a/16072668/936397
Alexander Olsson,2016年

1
@virus很好地表示,有100多个人可能也不在乎性能……不过,这并没有使我所说的内容无效或不正确。
詹姆斯

30

jQuery的:

(function($) {

 $.textMetrics = function(el) {

  var h = 0, w = 0;

  var div = document.createElement('div');
  document.body.appendChild(div);
  $(div).css({
   position: 'absolute',
   left: -1000,
   top: -1000,
   display: 'none'
  });

  $(div).html($(el).html());
  var styles = ['font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing'];
  $(styles).each(function() {
   var s = this.toString();
   $(div).css(s, $(el).css(s));
  });

  h = $(div).outerHeight();
  w = $(div).outerWidth();

  $(div).remove();

  var ret = {
   height: h,
   width: w
  };

  return ret;
 }

})(jQuery);

26

这对我有用...

// Handy JavaScript to measure the size taken to render the supplied text;
// you can supply additional style information too if you have it.

function measureText(pText, pFontSize, pStyle) {
    var lDiv = document.createElement('div');

    document.body.appendChild(lDiv);

    if (pStyle != null) {
        lDiv.style = pStyle;
    }
    lDiv.style.fontSize = "" + pFontSize + "px";
    lDiv.style.position = "absolute";
    lDiv.style.left = -1000;
    lDiv.style.top = -1000;

    lDiv.innerHTML = pText;

    var lResult = {
        width: lDiv.clientWidth,
        height: lDiv.clientHeight
    };

    document.body.removeChild(lDiv);
    lDiv = null;

    return lResult;
}

嗨,您的答案确实有用,但是请使用.cssText而不是.style来支持IE 8及更低版本。.–
Dilip Rajkumar

这个答案很好,但是当我生成大量文本时,它会变得不准确一些像素。是否因为宽度不是整数?
Bobtroopo

20

ExtJS的JavaScript库有一个名为Ext.util.TextMetrics说,“为文本块精确测量像素,让您可以准确确定的高和宽,以像素为单位,文本的给定块将是”一个伟大的阶级。您可以直接使用它,也可以查看其源代码以查看其完成方式。

http://docs.sencha.com/extjs/6.5.3/modern/Ext.util.TextMetrics.html


1
ExtJS具有奇数许可证。您要么向当前的维护者,Sencha公司付款,要么使用它,或者您必须在应用程序中开源所有相关代码。对于大多数公司来说,这是一个表演障碍。另一方面,jQuery使用高度宽松的MIT许可证。
devdanke '02

1
javascript不能自动满足开源要求吗?您将源提供给查看该页面的任何人。
PatrickO 2012年

10
能够查看源代码和开源(由许可定义)之间是有区别的。
帕克奥特

感谢那些仍在使用ExtJS的人:-)
Monarch Wadia'3

1
您应该让ExtJS安息
Canbax

19

我喜欢您仅做一个静态字符宽度图的“唯一想法”!对于我的目的,它实际上工作得很好。有时,出于性能原因,或者由于您无法轻松访问DOM,您可能只想将快速,简单易用的独立计算器校准为单个字体。所以这是一个经过Helvetica校准的;传递字符串和(可选)字体大小:

function measureText(str, fontSize = 10) {
  const widths = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2796875,0.2765625,0.3546875,0.5546875,0.5546875,0.8890625,0.665625,0.190625,0.3328125,0.3328125,0.3890625,0.5828125,0.2765625,0.3328125,0.2765625,0.3015625,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.2765625,0.2765625,0.584375,0.5828125,0.584375,0.5546875,1.0140625,0.665625,0.665625,0.721875,0.721875,0.665625,0.609375,0.7765625,0.721875,0.2765625,0.5,0.665625,0.5546875,0.8328125,0.721875,0.7765625,0.665625,0.7765625,0.721875,0.665625,0.609375,0.721875,0.665625,0.94375,0.665625,0.665625,0.609375,0.2765625,0.3546875,0.2765625,0.4765625,0.5546875,0.3328125,0.5546875,0.5546875,0.5,0.5546875,0.5546875,0.2765625,0.5546875,0.5546875,0.221875,0.240625,0.5,0.221875,0.8328125,0.5546875,0.5546875,0.5546875,0.5546875,0.3328125,0.5,0.2765625,0.5546875,0.5,0.721875,0.5,0.5,0.5,0.3546875,0.259375,0.353125,0.5890625]
  const avg = 0.5279276315789471
  return str
    .split('')
    .map(c => c.charCodeAt(0) < widths.length ? widths[c.charCodeAt(0)] : avg)
    .reduce((cur, acc) => acc + cur) * fontSize
}

那个丑陋的巨大数组是由字符代码索引的ASCII字符宽度。因此,这仅支持ASCII(否则假定为平均字符宽度)。幸运的是,宽度基本上与字体大小成线性比例,因此在任何字体大小下都可以很好地工作。显然,它对字距调整或连字等均缺乏任何了解。

为了“校准”,我只是在svg上将每个字符渲染到charCode 126(强大的波浪号),并获得了边界框并将其保存到此数组中。更多代码,解释和演示在这里


1
那是一个聪明的解决方案,不错的一个:)我一直使用相同的字体/大小,因此非常适合我。我发现DOM / Canvas解决方案的性能是一个真正的问题,这真的很干净,干杯!
mikeapr4 '18

1
消除了对画布+1的需要
残废

你可以摆脱* fontSize,只是用“时间”
马特·弗莱彻

真是个了不起的答案!
almosnow


7

您可以使用画布,因此您不必处理CSS属性:

var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.font = "20pt Arial";  // This can be set programmaticly from the element's font-style if desired
var textWidth = ctx.measureText($("#myElement").text()).width;

是否比创建DOM元素并为其指定CSS属性要重得多(获得2d上下文等)?
Pharao2k

1
如果您已经在使用画布做某事,这是一种干净的方法。但是非常慢。单独调用measureText大约需要8-10毫秒(使用Chrome)。
Rui Marques 2013年

6
<span id="text">Text</span>

<script>
var textWidth = document.getElementById("text").offsetWidth;
</script>

只要<span>标记没有应用其他样式,它就应该起作用。offsetWidth将包括任何边框的宽度,水平填充,垂直滚动条宽度等。


这或多或少是解决问题的上策,但是由于您的文本可能会分成几行,因此他们在文本中添加了一些CSS样式以获得真正的完整文本宽度。
Adrian Maire 2014年

2

下面的代码片段“计算” span-tag的宽度,如果它太长,则在其后面附加“ ...”,并减小文本长度,直到适合其父级为止(或直到​​尝试了超过一千次)

的CSS

div.places {
  width : 100px;
}
div.places span {
  white-space:nowrap;
  overflow:hidden;
}

的HTML

<div class="places">
  <span>This is my house</span>
</div>
<div class="places">
  <span>And my house are your house</span>
</div>
<div class="places">
  <span>This placename is most certainly too wide to fit</span>
</div>

JavaScript(使用jQuery)

// loops elements classed "places" and checks if their child "span" is too long to fit
$(".places").each(function (index, item) {
    var obj = $(item).find("span");
    if (obj.length) {
        var placename = $(obj).text();
        if ($(obj).width() > $(item).width() && placename.trim().length > 0) {
            var limit = 0;
            do {
                limit++;
                                    placename = placename.substring(0, placename.length - 1);
                                    $(obj).text(placename + "...");
            } while ($(obj).width() > $(item).width() && limit < 1000)
        }
    }
});

2
尝试二进制搜索而不是一次循环1个字符:请参阅我对Alex对stackoverflow.com/questions/536814/…
StanleyH 2011年

@StanleyH:好主意-我会尽快实施您的建议。
Techek 2011年

2

更好的方法是在显示元素之前检测文本是否适合。因此,您可以使用此功能,而无需在屏幕上显示该元素。

function textWidth(text, fontProp) {
    var tag = document.createElement("div");
    tag.style.position = "absolute";
    tag.style.left = "-999em";
    tag.style.whiteSpace = "nowrap";
    tag.style.font = fontProp;
    tag.innerHTML = text;

    document.body.appendChild(tag);

    var result = tag.clientWidth;

    document.body.removeChild(tag);

    return result;
}

用法:

if ( textWidth("Text", "bold 13px Verdana") > elementWidth) {
    ...
}

2

如果其他任何人都在这里寻找一种测量字符串宽度的方法以及一种知道适合特定宽度的最大字体大小的方法,则此函数基于@Domi的二进制搜索解决方案:

/**
 * Find the largest font size (in pixels) that allows the string to fit in the given width.
 * 
 * @param {String} text The text to be rendered.
 * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold ?px verdana") -- note the use of ? in place of the font size.
 * @param {width} the width in pixels the string must fit in
 * @param {minFontPx} the smallest acceptable font size in pixels
 * @param {maxFontPx} the largest acceptable font size in pixels
**/
function GetTextSizeForWidth( text, font, width, minFontPx, maxFontPx )
{
    for ( ; ; )
    {
        var s = font.replace( "?", maxFontPx );
        var w = GetTextWidth( text, s );
        if ( w <= width )
        {
            return maxFontPx;
        }

        var g = ( minFontPx + maxFontPx ) / 2;

        if ( Math.round( g ) == Math.round( minFontPx ) || Math.round( g ) == Math.round( maxFontPx ) )
        {
            return g;
        }

        s = font.replace( "?", g );
        w = GetTextWidth( text, s );
        if ( w >= width )
        {
            maxFontPx = g;
        }
        else
        {
            minFontPx = g;
        }
    }
}

效果惊人(加上Domi的答案中的代码)
r3wt

1

试试这个代码:

function GetTextRectToPixels(obj)
{
var tmpRect = obj.getBoundingClientRect();
obj.style.width = "auto"; 
obj.style.height = "auto"; 
var Ret = obj.getBoundingClientRect(); 
obj.style.width = (tmpRect.right - tmpRect.left).toString() + "px";
obj.style.height = (tmpRect.bottom - tmpRect.top).toString() + "px"; 
return Ret;
}

1

文本的宽度和高度可以通过clientWidth和获得clientHeight

var element = document.getElementById ("mytext");

var width = element.clientWidth;
var height = element.clientHeight;

确保样式位置属性设置为绝对

element.style.position = "absolute";

不需要位于a内div,可以位于a p或aspan


1

基于Deepak Nadar的答案,我更改了functions参数以接受文本和字体样式。您不需要引用元素。另外,fontOptions具有默认值,因此您无需提供所有默认值。

(function($) {
  $.format = function(format) {
    return (function(format, args) {
      return format.replace(/{(\d+)}/g, function(val, pos) {
        return typeof args[pos] !== 'undefined' ? args[pos] : val;
      });
    }(format, [].slice.call(arguments, 1)));
  };
  $.measureText = function(html, fontOptions) {
    fontOptions = $.extend({
      fontSize: '1em',
      fontStyle: 'normal',
      fontWeight: 'normal',
      fontFamily: 'arial'
    }, fontOptions);
    var $el = $('<div>', {
      html: html,
      css: {
        position: 'absolute',
        left: -1000,
        top: -1000,
        display: 'none'
      }
    }).appendTo('body');
    $(fontOptions).each(function(index, option) {
      $el.css(option, fontOptions[option]);
    });
    var h = $el.outerHeight(), w = $el.outerWidth();
    $el.remove();
    return { height: h, width: w };
  };
}(jQuery));

var dimensions = $.measureText("Hello World!", { fontWeight: 'bold', fontFamily: 'arial' });

// Font Dimensions: 94px x 18px
$('body').append('<p>').text($.format('Font Dimensions: {0}px x {1}px', dimensions.width, dimensions.height));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


0

我猜这与Depak的作品类似,但是基于Louis Lazaris的工作,该作品发表在令人印象深刻的网页上

(function($){

        $.fn.autofit = function() {             

            var hiddenDiv = $(document.createElement('div')),
            content = null;

            hiddenDiv.css('display','none');

            $('body').append(hiddenDiv);

            $(this).bind('fit keyup keydown blur update focus',function () {
                content = $(this).val();

                content = content.replace(/\n/g, '<br>');
                hiddenDiv.html(content);

                $(this).css('width', hiddenDiv.width());

            });

            return this;

        };
    })(jQuery);

在将函数与控件关联之后,fit事件用于立即执行函数调用。

例如:$('input')。autofit()。trigger(“ fit”);


0

没有jQuery:

String.prototype.width = function (fontSize) {
    var el,
        f = fontSize + " px arial" || '12px arial';
    el = document.createElement('div');
    el.style.position = 'absolute';
    el.style.float = "left";
    el.style.whiteSpace = 'nowrap';
    el.style.visibility = 'hidden';
    el.style.font = f;
    el.innerHTML = this;
    el = document.body.appendChild(el);
    w = el.offsetWidth;
    el.parentNode.removeChild(el);
    return w;
}

// Usage
"MyString".width(12);

当您设置数字不起作用时,因为没有必要的空格,如果将其删除,它将可以正常工作:fontSize +“ px arial”-> fontSize +“ px arial”,仅此而已。
阿尔贝托·阿库尼亚(AlbertoAcuña)

0

小提琴的工作示例:http : //jsfiddle.net/tdpLdqpo/1/

HTML:

<h1 id="test1">
    How wide is this text?
</h1>
<div id="result1"></div>
<hr/>
<p id="test2">
    How wide is this text?
</p>
<div id="result2"></div>
<hr/>
<p id="test3">
    How wide is this text?<br/><br/>
    f sdfj f sdlfj lfj lsdk jflsjd fljsd flj sflj sldfj lsdfjlsdjkf sfjoifoewj flsdjfl jofjlgjdlsfjsdofjisdojfsdmfnnfoisjfoi  ojfo dsjfo jdsofjsodnfo sjfoj ifjjfoewj fofew jfos fojo foew jofj s f j
</p>
<div id="result3"></div>

JavaScript代码:

function getTextWidth(text, font) {
    var canvas = getTextWidth.canvas ||
        (getTextWidth.canvas = document.createElement("canvas"));
    var context = canvas.getContext("2d");
    context.font = font;
    var metrics = context.measureText(text);
    return metrics.width;
};

$("#result1")
.text("answer: " +
    getTextWidth(
             $("#test1").text(),
             $("#test1").css("font")) + " px");

$("#result2")
    .text("answer: " +
        getTextWidth(
             $("#test2").text(),
             $("#test2").css("font")) + " px");

$("#result3")
    .text("answer: " +
        getTextWidth(
             $("#test3").text(),
             $("#test3").css("font")) + " px");

0

Element.getClientRects()方法返回一个DOMRect对象集合,这些对象指示客户端中每个CSS边框的边界矩形。返回的值是DOMRect对象的集合,每个与元素相关联的CSS边框均包含一个对象。每个DOMRect对象包含只读lefttoprightbottom描述边框框,以像素为单位,与左上角相对于性能左上角的视口。

Element.getClientRects()Mozilla的贡献者是根据许可CC-BY-SA 2.5

将所有返回的矩形宽度相加得出以像素为单位的总文本宽度。

document.getElementById('in').addEventListener('input', function (event) {
    var span = document.getElementById('text-render')
    span.innerText = event.target.value
    var rects = span.getClientRects()
    var widthSum = 0
    for (var i = 0; i < rects.length; i++) {
        widthSum += rects[i].right - rects[i].left
    }
    document.getElementById('width-sum').value = widthSum
})
<p><textarea id='in'></textarea></p>
<p><span id='text-render'></span></p>
<p>Sum of all widths: <output id='width-sum'>0</output>px</p>


0

我做了一个很小的ES6模块(使用jQuery):

import $ from 'jquery';

const $span=$('<span>');
$span.css({
    position: 'absolute',
    display: 'none'
}).appendTo('body');

export default function(str, css){
    $span[0].style = ''; // resetting the styles being previously set
    $span.text(str).css(css || {});
    return $span.innerWidth();
}

易于使用:

import stringWidth from './string_width';
const w = stringWidth('1-3', {fontSize: 12, padding: 5});

您可能会注意到的很酷的事情-它可以考虑任何CSS属性,甚至包括填充!


0

您还可以使用createRange进行此操作,该方法比文本克隆技术更准确:

function getNodeTextWidth(nodeWithText) {
    var textNode = $(nodeWithText).contents().filter(function () {
        return this.nodeType == Node.TEXT_NODE;
    })[0];
    var range = document.createRange();
    range.selectNode(textNode);
    return range.getBoundingClientRect().width;
}

-1
var textWidth = (function (el) {
    el.style.position = 'absolute';
    el.style.top = '-1000px';
    document.body.appendChild(el);

    return function (text) {
        el.innerHTML = text;
        return el.clientWidth;
    };
})(document.createElement('div'));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.