545

我想使用 JavaScript 来计算字符串的宽度。这是否可能无需使用等宽字体?

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

4

25 回答 25

505

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 https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
  */
function getTextWidth(text, font) {
  // re-use canvas object for better performance
  const canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
  const context = canvas.getContext("2d");
  context.font = font;
  const metrics = context.measureText(text);
  return metrics.width;
}

function getCssStyle(element, prop) {
    return window.getComputedStyle(element, null).getPropertyValue(prop);
}

function getCanvasFontSize(el = document.body) {
  const fontWeight = getCssStyle(el, 'font-weight') || 'normal';
  const fontSize = getCssStyle(el, 'font-size') || '16px';
  const fontFamily = getCssStyle(el, 'font-family') || 'Times New Roman';
  
  return `${fontWeight} ${fontSize} ${fontFamily}`;
}

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

如果要使用某些特定元素的字体大小myEl,可以使用getCanvasFontSize实用函数:

const fontSize = getTextWidth(text, getCanvasFontSize(myEl));
// do something with fontSize here...

说明:该getCanvasFontSize函数采用某些元素的(默认情况下:body's)字体并将其转换为与Context.font 属性兼容的格式。当然,任何元素在使用之前都必须先添加到 DOM 中,否则它会为您提供虚假值。

el.remove()); // hackfix:不要这样做!返回 el; } console.log(getTextWidth(“你好!”,getCanvasFontSize(createEl('span')))); console.log(getTextWidth("你好!", getCanvasFontSize(createEl('h1')))); ``` -->

更多笔记

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

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

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

注意 2:在某些浏览器上,此方法会产生亚像素精度(结果是浮点数),而在其他浏览器上则不会(结果只是 int)。您可能希望在结果上运行Math.floor(或Math.ceil),以避免不一致。由于基于 DOM 的方法从来都不是亚像素精度的,因此这种方法比这里的其他方法具有更高的精度。

根据这个 jsperf(感谢评论中的贡献者),如果在基于 DOM 的方法中添加了缓存并且您没有使用 Firefox,那么Canvas方法基于DOM 的方法几乎同样快。在 Firefox 中,出于某种原因,这种Canvas 方法比基于 DOM 的方法快得多(截至 2014 年 9 月)。

表现

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

于 2014-01-09T08:46:45.823 回答
400

创建一个具有以下样式的 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>

于 2008-09-22T23:40:58.250 回答
113

这是我在没有例子的情况下拼凑在一起的。看起来我们都在同一个页面上。

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以便可以计算宽度大于窗口宽度的字符串。

于 2011-02-18T23:39:25.843 回答
32

这对我有用...

// 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.textContent = pText;

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

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

    return lResult;
}
于 2010-10-27T11:01:47.570 回答
31

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);
于 2010-01-22T14:05:50.880 回答
28

我喜欢你做一个静态字符宽度图的“唯一想法”!它实际上很适合我的目的。有时,出于性能原因或因为您无法轻松访问 DOM,您可能只想要一个快速的 hacky 独立计算器,校准为单一字体。所以这里有一个校准到 Helvetica 的;传递一个字符串和一个字体大小:

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

function measureText(str, fontSize) {
  return Array.from(str).reduce(
    (acc, cur) => acc + (widths[cur.charCodeAt(0)] ?? avg), 0
  ) * fontSize
}

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

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

于 2018-01-09T16:30:40.710 回答
20

ExtJS javascript 库有一个很棒的类,叫做 Ext.util.TextMetrics,它“为文本块提供精确的像素测量,以便您可以准确地确定给定文本块的高度和宽度(以像素为单位)”。您可以直接使用它,也可以查看其源代码以了解它是如何完成的。

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

于 2008-09-23T17:12:27.570 回答
11

我为此写了一个小工具。也许它对某人有用。它可以在没有 jQuery的情况下工作。

https://github.com/schickling/calculate-size

用法:

var size = calculateSize("Hello world!", {
   font: 'Arial',
   fontSize: '12px'
});

console.log(size.width); // 65
console.log(size.height); // 14

小提琴:http: //jsfiddle.net/PEvL8/

于 2014-02-22T21:58:08.730 回答
9
<span id="text">Text</span>

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

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

于 2008-09-22T23:46:48.237 回答
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;
于 2013-01-12T08:17:54.387 回答
4

万一其他人来到这里寻找一种测量字符串宽度的方法一种知道适合特定宽度的最大字体大小的方法,这里有一个基于@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 {Number} width - The width in pixels the string must fit in
 * @param {Number} minFontPx - The smallest acceptable font size in pixels
 * @param {Number} 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;
    }
  }
}
于 2018-06-12T09:04:21.490 回答
3

您也可以使用 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;
}
于 2020-01-08T13:42:34.623 回答
2

下面的代码片段,“计算”跨度标签的宽度,如果它太长,则附加“...”并减少文本长度,直到它适合其父级(或直到它尝试超过一千次)

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)
        }
    }
});
于 2010-06-29T11:15:08.033 回答
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) {
    ...
}
于 2013-09-13T15:27:19.637 回答
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;
}
于 2013-01-19T05:14:02.727 回答
1

文本的宽度和高度可以通过clientWidthclientHeight

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

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

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

element.style.position = "absolute";

不需要在 a 内div,可以在 ap或 a内span

于 2013-06-14T22:27:37.487 回答
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>

于 2016-04-04T12:44:11.420 回答
1

从头开始重写我的答案(感谢那个减号)。现在函数接受要应用的文本和 css 规则(并且不再使用 jQuery)。所以它也会尊重填充。结果值被四舍五入(你可以在那里看到 Math.round,如果你想要更精确的值,请删除)

function getSpan(){
    const span = document.createElement('span')
    span.style.position = 'fixed';
    span.style.visibility = 'hidden';
    document.body.appendChild(span);
    return span;
}

function textWidth(str, css) {
    const span = getSpan();
    Object.assign(span.style, css || {});
    span.innerText = str;
    const w = Math.round(span.getBoundingClientRect().width);
    
    span.remove();
    
    return w;
}


const testStyles = [
  {fontSize: '10px'},
  {fontSize: '12px'},
  {fontSize: '60px'},
  {fontSize: '120px'},
  {fontSize: '120px', padding: '10px'},
  {fontSize: '120px', fontFamily: 'arial'},
  {fontSize: '120px', fontFamily: 'tahoma'},
  {fontSize: '120px', fontFamily: 'tahoma', padding: '5px'},
];

const ul = document.getElementById('output');
testStyles.forEach(style => {
  const li = document.createElement('li');
  li.innerText = `${JSON.stringify(style)} > ${textWidth('abc', style)}`;
  ul.appendChild(li);
});
<ul id="output"></ul>

于 2018-12-28T11:42:35.347 回答
1

可以max-content用来测量文本的像素宽度。

这是一个实用功能。它可以选择将任何节点作为上下文来计算宽度,同时考虑任何 CSS,如font-size,letter-spacing等。

function measureTextPxWidth(
  text,
  template = document.createElement("span")
) {
  const measurer = template.cloneNode();
  measurer.style.setProperty("all", "revert", "important");
  measurer.style.setProperty("position", "position", "important");
  measurer.style.setProperty("visibility", "hidden", "important");
  measurer.style.setProperty("width", "max-content", "important");
  measurer.innerText = text;

  document.body.appendChild(measurer);
  const { width } = measurer.getBoundingClientRect();
  document.body.removeChild(measurer);
  return width;
}

document.querySelector('.spanTextWidth').innerText = 
  `${measureTextPxWidth('one two three')}px`
  
document.querySelector('.h1TextWidth').innerText = 
  `${measureTextPxWidth('one two three', document.querySelector('h1'))}px`
h1 {
  letter-spacing: 3px;
}
<span>one two three</span>
<div class="spanTextWidth"></div>
  
<h1>one two three</h1>
<div class="h1TextWidth"></div>

于 2021-12-01T02:36:36.210 回答
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");

于 2014-05-14T21:44:55.110 回答
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);
于 2015-01-28T18:06:32.913 回答
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");
于 2015-06-19T18:35:36.607 回答
0

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

Mozilla ContributorsElement.getClientRects()在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>

于 2018-06-02T21:55:09.597 回答
0

我正在使用text-metrics包。效果很好,我尝试了这个解决方案,但由于某些原因,它算错了。

textMetrics.init(document.querySelector('h1'), { fontSize: '20px' });

textMetrics.init({
  fontSize: '14px',
  lineHeight: '20px',
  fontFamily: 'Helvetica, Arial, sans-serif',
  fontWeight: 400,
  width: 100,
});
于 2020-08-07T22:18:06.457 回答
-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'));
于 2013-11-15T05:46:50.800 回答