Рассчитать ширину текста с помощью JavaScript

Я хотел бы использовать JavaScript для расчета ширины строки. Возможно ли это без использования моноширинного шрифта?

Если он не встроенный, моя единственная идея - создать таблицу ширины для каждого символа, но это довольно неразумно, особенно с поддержкой Unicode и разных размеров шрифта (и всех браузеров в этом отношении).

29 ответов

Решение

Создайте 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>

В 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://stackru.com/questions/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 с вариацией метода, основанного на DOM Боба Монтеверде, так что вы можете анализировать и сравнивать точность результатов.

У этого подхода есть несколько преимуществ, в том числе:

  • Более сжатый и безопасный, чем другие (основанные на DOM) методы, потому что он не меняет глобального состояния, такого как ваша DOM.
  • Дальнейшая настройка возможна путем изменения дополнительных свойств текста холста, таких как textAlign а также textBaseline,

ПРИМЕЧАНИЕ. При добавлении текста в DOM не забывайте также учитывать отступы, поля и границы.

ПРИМЕЧАНИЕ 2: В некоторых браузерах этот метод дает субпиксельную точность (результат - число с плавающей запятой), в других - нет (результат - только целое число). Вы могли бы хотеть бежать Math.floor (или же Math.ceil) на результат, чтобы избежать несоответствий. Поскольку метод на основе DOM никогда не является субпиксельным с точностью, этот метод имеет даже более высокую точность, чем другие методы здесь.

Согласно этому jsperf (спасибо авторам комментариев), метод Canvas и метод на основе DOM работают примерно одинаково, если к методу на основе DOM добавлено кэширование, а вы не используете Firefox. В Firefox по какой-то причине этот метод Canvas намного быстрее, чем метод на основе DOM (по состоянию на сентябрь 2014 года).

Вот тот, который я взбил без примера. Похоже, мы все на одной странице.

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 поэтому строки с шириной, превышающей ширину окна, могут быть рассчитаны.

Мне нравится ваша "единственная идея" просто сделать статическую карту ширины символов! Это на самом деле хорошо работает для моих целей. Иногда по соображениям производительности или из-за того, что у вас нет простого доступа к 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 (в противном случае предполагается средняя ширина символа). К счастью, ширина в основном линейно масштабируется в зависимости от размера шрифта, поэтому она отлично работает при любом размере шрифта. Ему явно не хватает понимания кернинга, лигатур или чего-то еще.

Чтобы "откалибровать", я просто визуализировал каждый символ до charCode 126 (могущественная тильда) в SVG, получил ограничивающий прямоугольник и сохранил его в этом массиве; больше кода и объяснений и демо здесь.

Это работает для меня...

// 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;
}

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

В библиотеке ExtJS javascript есть отличный класс Ext.util.TextMetrics, который "обеспечивает точные измерения пикселей для блоков текста, чтобы вы могли точно определить, насколько высокими и широкими в пикселях будет данный блок текста". Вы можете использовать его напрямую или просмотреть его исходный код, чтобы увидеть, как это делается.

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

Я написал небольшой инструмент для этого. Возможно, это кому-нибудь пригодится. Работает без 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/

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

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

Это должно работать до тех пор, пока к тегу не применяются другие стили. offsetWidth будет включать ширину любых границ, горизонтальное заполнение, ширину вертикальной полосы прокрутки и т. д.

Вы можете использовать холст, чтобы вам не приходилось иметь дело со свойствами 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;

Вы также можете сделать это с помощью 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;
}

На тот случай, если кто-то еще ищет здесь способ измерения ширины строки и способ определения размера шрифта, который будет соответствовать определенной ширине, вот функция, основанная на решении @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;
        }
    }
}

Вы можете использоватьmax-contentдля измерения ширины текста в пикселях.

Вот служебная функция, которая делает это. При желании он принимает любой узел в качестве контекста для расчета ширины, принимая во внимание любой CSS, например font-size, letter-spacing, так далее.

Если вы согласны с установкой пакета и вам нужен, возможно, более авторитетный или точный ответ, вы можете использовать opentype.js (удивлен, что никто еще не упомянул об этом):

      import { load } from "opentype.js";

const getWidth = async (text = "Hello World") => {
  const font = await load("path/to/some/font");
  const { x1, x2 } = font.getPath(text, 0, 0, 12).getBoundingBox();
  return x2 - x1;
};

Естественно, вы хотели бы только позвонить loadодин раз для каждого шрифта, поэтому вы должны вытащить эту строку на более высокий уровень в зависимости от ваших обстоятельств.

Вот песочница кода, сравнивающая этот метод OpenType с методами Canvas и DOM: https://codesandbox.io/s/measure-width-of-text-in-javascript-vctst2

На моей машине по 100 образцов каждый типичный результат таков:

  • Открытый тип: 5ms
  • Холст: 3ms
  • ДОМ: 4ms

Еще один пакет, который я нашел, это: https://github.com/sffc/word-wrappr

Приведенный ниже фрагмент кода "вычисляет" ширину тега span, добавляет к нему "...", если он слишком длинный, и уменьшает длину текста до тех пор, пока он не уместится в родительский элемент (или пока он не попытается выполнить более тысячи раз)

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)
        }
    }
});

Лучше всего определить, поместится ли текст прямо перед отображением элемента. Таким образом, вы можете использовать эту функцию, которая не требует, чтобы элемент был на экране.

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) {
    ...
}

Ширина и высота текста могут быть получены с помощью clientWidth а также clientHeight

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

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

убедитесь, что свойство стиля позиции установлено в абсолют

element.style.position = "absolute";

не обязательно находиться внутри div, может быть внутри p или span

Используйте на содержащем элементе текста, чтобы получить минимальную ширину элемента, включая скрытые части из-за переполнения. Дополнительные сведения см. на странице https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollWidth .

Если элемента нет в DOM, добавьте его в какую-нибудь скрытую область, чтобы выполнить измерение. Например:

      function measureText(text) {
  let div = document.createElement("div");
  div.innerText = text;
  div.style.whiteSpace = 'nowrap';
  body.appendChild(div);
  let width = div.scrollWidth;
  body.removeChild(div);
  return width;
}

Стиль (размер шрифта, вес и т. д.) будет унаследован элементом и, таким образом, будет учитываться в ширине. Вы также можете измерить размер более сложного контента с помощьюscrollWidthиscrollHeight.

Попробуйте этот код:

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

Основываясь на ответе Дипака Надара, я изменил параметры функций, чтобы принимать стили текста и шрифта. Вам не нужно ссылаться на элемент. Так же 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>

Element.getClientRects() метод возвращает коллекцию DOMRect объекты, которые обозначают ограничивающие прямоугольники для каждого CSS-рамки в клиенте. Возвращаемое значение является коллекцией DOMRect объекты, по одному для каждого CSS-рамки, связанной с элементом. каждый DOMRect объект содержит только для чтения left, top, right а также bottom свойства, описывающие границу, в пикселях, с верхним левым углом относительно верхнего левого угла области просмотра.

Element.getClientRects () от Mozilla Contributors лицензируется в соответствии с 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>

Я сделал крошечный модуль 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, даже отступы!

Всем, кто использует React и/или Typescript...

Попробуйте этот Кодепен!

      export default function App() {
  const spanRef = useRef<HTMLSpanElement>(null);
  const [textWidth, setTextWidth] = useState(0);

  const getTextWidthInPixels = (ref: HTMLSpanElement) =>
    ref.getBoundingClientRect().width;

  useEffect(() => {
    setTextWidth(getTextWidthInPixels(spanRef.current!));
  }, [spanRef]);

  return (
    <div className="App">
      <span
        ref={spanRef}
        contentEditable
        suppressContentEditableWarning
        onInput={() => setTextWidth(getTextWidthInPixels(spanRef.current!))}
      >
        Edit Me!!!
      </span>
      {`textWidth: ${textWidth}px`}
    </div>
  );
}

  • Это хорошая идея, чтобы обернуть наш текст встроенным элементом (например, )
  • useRefэто способ React для доступа к элементу DOM, <span>в нашем случае
  • getBoundingClientRectможет получить общую ширину любого элемента DOM.
  • contentEditableпозволяет пользователям изменять содержимое элемента... что немного небезопасно (React будет выдавать предупреждения!)
  • suppressContentEditableWarningпоможет нам предотвратить эти предупреждения

Я использую пакет текстовых метрик. Работает очень хорошо, я пробовал это решение, но по некоторым причинам оно считает неправильным.

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

textMetrics.init({
  fontSize: '14px',
  lineHeight: '20px',
  fontFamily: 'Helvetica, Arial, sans-serif',
  fontWeight: 400,
  width: 100,
});

Скрипка рабочего примера: 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");

Привет всем, я знаю, что я немного опаздываю на вечеринку, но поехали

      window.addEventListener("error",function(e){ alert(e.message); });
var canvas = new OffscreenCanvas(400, 50);
var ctx = canvas.getContext("2d");
ctx.font = "16px Ariel"; //this can be dynamic using getComputedStyle
const chars = ["a","b","c","d","e","f"," ","    "];
const charWidths = new Map();
while(chars.length > 0){
  var char = chars.shift();
  var wide = ctx.measureText(char).width;
  charWidths.set(char,wide);
}

а затем вы можете использовать его с чем-то вроде:

      var pixelWidth = charWidths.get("0");
//fyi css properties like letter-spacing need to be accounted for

Я предполагаю, что это похоже на запись Депака, но основано на работе Луи Лазариса, опубликованной в статье на странице впечатляющей сети.

(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");

Без 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);
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'));
Другие вопросы по тегам