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

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

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

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

Seth W. Klein 13.12.2015 23:35
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
498
1
442 619
24
Перейти к ответу Данный вопрос помечен как решенный

Ответы 24

Ответ принят как подходящий

Создайте 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.info(height, width);
#Test
{
    position: absolute;
    visibility: hidden;
    height: auto;
    width: auto;
    white-space: nowrap; /* Thanks to Herb Caudill comment */
}
<div id = "Test">
    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
</div>

Единственное, что я бы добавил, это то, что это может давать неправильные размеры в зависимости от того, какие стили используются. Помните, что у вас могут быть такие стили, как p {letter-spacing: 0.1em; } что элемент DIV не будет отражать. Вы должны убедиться, что используемые стили подходят для того, где вы будете использовать текст.

Jim 23.09.2008 04:26

То же, что и комментарий Джима - дважды проверьте, чтобы к контейнеру, в данном случае div, не применялись какие-либо другие стили, примененные к нему с помощью правил выбора css, о которых вы, возможно, не знали в то время. Снимите с контейнера все подходящие стили, прежде чем применять те, которые вам нужны, перед измерением.

Jason Bunting 23.09.2008 04:33

Вы также должны ввести white-space: nowrap, если вы думаете, что текст будет превышать ширину браузера.

Herb Caudill 23.09.2008 05:41

этот ответ не сработал для меня, он сначала дал мне неправильные значения, продолжал их увеличивать, и только после пары звонков у меня были правильные. Что исправлено, так это замена visibility:hidden на верхний / левый, установленный на -1000 пикселей.

BBog 19.03.2013 13:05

+1 для абсолютного позиционирования, которое, кажется, упускается во многих сообщениях ... без него вы получаете пространство, занимаемое div из-за использования visibility:hidden вместо `display: none '(что не работает для этого )

jenson-button-event 08.07.2013 20:28

@Bog все эти взломы - вот почему я предлагаю другой, менее хакерский, подход внизу. Взгляните на это здесь.

Domi 09.01.2014 12:54

Просто любопытно, для чего нужен +1 по каждому измерению?

Kip 28.03.2014 07:18

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

martin 08.12.2014 22:26

Блестящая идея - отстой, нам нужно взломать, чтобы получить эту информацию

Daniel Kereama 25.05.2017 05:36

У меня это не работает. test.clientWidth и test.clientHeight всегда возвращают 0.

Donald Duck 06.10.2018 13:24

изменение значения fontSize, отличного от 12, не влияет на результат в моем испытании с ie11. я попробовал именно так, как в ответе.

Andre Chenier 23.05.2019 08:27

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

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

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

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

Adrian Maire 28.10.2014 19:25

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

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

У ExtJS лишняя лицензия. Либо вы платите текущим сопровождающим, компании Sencha за его использование, либо вы должны открыть исходный код всего связанного кода в своем приложении. Это шоу-пробка для большинства компаний. jQuery, с другой стороны, использует разрешающую лицензию MIT.

devdanke 18.02.2012 21:00

Разве javascript автоматически не соответствует требованиям открытого исходного кода? Вы предоставляете источник любому, кто просматривает страницу.

PatrickO 24.04.2012 20:09

Есть разница между возможностью просмотра исходного кода и открытым исходным кодом, которая определяется лицензированием.

Parker Ault 27.04.2012 01:59

Спасибо тем из нас, кто все еще использует ExtJS :-)

Monarch Wadia 06.03.2017 20:00

вы должны были позволить ExtJS покоиться с миром

canbax 14.05.2020 17:23

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

Приведенные ниже фрагменты кода «вычисляют» ширину тега 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)
        }
    }
});

Попробуйте двоичный поиск вместо того, чтобы зацикливать по одному символу за раз: см. Мой комментарий к ответу Алекса на stackoverflow.com/questions/536814/…

StanleyH 01.02.2011 13:33

@StanleyH: Хорошая идея - я выполню ваше предложение как можно скорее.

Techek 14.02.2011 16:17

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

// 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 28.11.2013 11:25

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

Andrei 22.03.2020 03:54

Не используйте это решение в его текущем состоянии. Используйте textContent вместо innerHTML для устранения уязвимости XSS.

ethnanon 07.07.2020 00:48

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

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, чтобы можно было вычислить строки, ширина которых больше ширины окна.

Спасибо, JordanC. Я бы добавил, что если вы вызываете это много раз на одной и той же странице, и производительность является проблемой, вы можете сохранить DIV и просто изменить содержимое и проверить ширину. Я просто сделал это так, чтобы, как только все было сказано и сделано, DOM будет таким же, как когда вы начали

Bob Monteverde 21.01.2012 05:53

В моей версии я использовал объект вместо аргумента шрифта, поэтому вы можете передать аргументы css в div для выделения жирным шрифтом, курсивом и т. д.

Iain 31.08.2012 06:21

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

Domi 09.01.2014 12:26

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

James 17.07.2014 13:40

Если вам нужно значение с плавающей запятой, вы можете использовать o[0].getBoundingClientRect().width, как предлагается здесь: stackoverflow.com/a/16072668/936397

Alexander Olsson 30.01.2016 16:32

Вы забыли упомянуть, что для этого требуется jQuery.

Donald Duck 07.04.2018 13:42

отличные баллы от @James, но здесь приземлились более 100 человек, которые не особо заботятся об архитектуре

vir us 13.03.2020 23:58

@virus хорошо сказал, что 100+ человек, вероятно, тоже не заботятся о производительности ... Тем не менее, это не делает то, что я сказал, менее достоверным или правдивым.

James 14.03.2020 02:26

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

Разве это не намного тяжелее (с получением 2d-контекста и т. д.), Чем создание элемента DOM и указание для него свойств css?

Pharao2k 18.04.2013 18:14

Это чистый подход, если вы уже для чего-то используете холст. Но ОЧЕНЬ МЕДЛЕННО. Один только вызов measureText занимает около 8-10 мс (при использовании Chrome).

Rui Marques 18.10.2013 23:45

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

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

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

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

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

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

element.style.position = "absolute";

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

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

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

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

В 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
    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.info(getTextWidth("hello there!", "bold 12pt arial"));  // close to 86

Эта рабочий пример сравнивает этот метод Canvas с вариантом Метод Боба Монтеверде на основе DOM, чтобы вы могли анализировать и сравнивать точность результатов.

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

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

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

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

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

Это отличный вариант, я о нем не знала. Вы когда-нибудь сравнивали производительность этого метода с производительностью измерения в DOM?

SimplGy 01.04.2014 23:07

@SimpleAsCouldBe Я только что изучил это и нашел этот тест ширины текста на jsperf.com. Он говорит мне, что метод Canvas в 6 раз быстрее, но он не кэширует DOM в другом методе, поэтому он предвзят.

Domi 03.04.2014 15:37

Я добавил кеширование в этот тест jsperf. Теперь два метода почти равны: jsperf.com/measure-text-width/4

Brandon Bloom 05.06.2014 03:27

Кстати, изменение шрифта на холсте приводит к перерасчету стилей, что может быть довольно медленным на странице с большим количеством элементов HTML.

martin 08.12.2014 23:12

@ Мартин А? При чем здесь «много элементов HTML»? Объект холста в этом примере даже не прикреплен к DOM. И даже помимо этого, изменение шрифта в контексте рисования холста даже не влияет на холст, пока вы не используете strokeText() или fillText(). Возможно, вы хотели прокомментировать другой ответ?

Ajedi32 18.09.2015 19:43

Хорошее решение. Я обернул пару других методов вокруг ответа Доми, чтобы я мог - получить (потенциально) усеченную строку с многоточием (...) в конце, если она не помещается в заданное пространство (столько, сколько строка по возможности будет использоваться) - Передайте элемент JQuery, который должен содержать (возможно, усеченную) строку, и динамически определять атрибуты шрифта, чтобы их не нужно было жестко запрограммировать, что позволяет изменять атрибуты шрифта CSS без нарушения расположение. JSFiddle Здесь: jsfiddle.net/brebey/1q94gLsu/6/embed

BRebey 18.05.2016 22:37

Размер шрифта на самом деле не является его высотой. Пример: высота Helvetica 16px всего 18 пикселей ...

RgSW 13.01.2017 16:04

Я забыл, где я нашел информацию об этом, но, согласно этому источнику, размер шрифта в пунктах должен быть максимальной высотой любого символа (или, по крайней мере, любого символа ASCII?). Возможно, ошиблись ... Я сейчас уберу этот намек. Спасибо!

Domi 13.01.2017 17:57

??? не работает «Uncaught ReferenceError: getTextWidth не определен» в строке var canvas = getTextWidth.canvas || (GetT ...

user1709076 09.08.2017 18:59

@ user1709076 Используете ли вы его с Node или какой-либо другой безголовой средой Javascript, которая на самом деле не определяет DOM? Это не сработает, если вы не установите специальную среду, например PhantomJS. Однако он должен работать во всех основных браузерах. См. здесь для таблицы, показывающей вам все поддерживаемые браузеры и версии.

Domi 09.08.2017 22:45

Есть ли способ вернуть этот ответ в баллах? или другие единицы?

Trenton 20.04.2018 18:08

@Trenton Насколько я понимаю, коэффициент преобразования pixel в point зависит от самого шрифта (подумайте: если бы это было так легко преобразовать, зачем нам было бы измерять его для начала? Разные символы уже имеют разную высоту точки!) а также носитель, который вы используете (например, экран вашего компьютера), его разрешение и даже реализацию конвейера рендеринга в браузере. У конвертера Этот есть примечание (буквально справа), которое вы можете прочитать

Domi 26.04.2018 14:35

Этот не учитывает все возможные стили CSS, которые могут применяться к строке после ее отображения на странице.

vsync 16.05.2018 14:53

@vsync Вы правы. Он не может учитывать text-transform, letter-spacing и др. (Пока), но учитывает наиболее часто встречающиеся требования.

Domi 18.05.2018 04:30

Хорошо, потому что пользователь этого не видит!

clabe45 01.06.2018 21:57

На случай, если кто-то еще пришел сюда и искал способ измерения ширины строки и способ узнать, какой самый большой размер шрифта подходит для определенной ширины, я добавил ответ (stackoverflow.com/a/50813259/384670), основанный на этом решении. с бинарным поиском.

M Katz 12.06.2018 12:06

Это будет работать в Chrome, но даст неправильный результат в Firefox.

daliusd 04.12.2018 17:16

Я написал для этого небольшой инструмент. Возможно, это кому-нибудь пригодится. Работает без jQuery.

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

Использование:

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

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

Рабочий пример: http://jsfiddle.net/PEvL8/

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

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

Событие соответствия используется для выполнения вызова функции сразу после того, как функция привязана к элементу управления.

например: $ ('input'). autofit (). trigger ("соответствовать");

Без 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);

когда вы устанавливаете число, не работает, потому что в пробеле нет необходимости, если вы его удалите, он будет работать нормально: размер шрифта + «px arial» -> fontSize + «px arial», вот и все.

Alberto Acuña 26.01.2017 11:57

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

Основываясь на Ответ Дипака Надара, я изменил параметр 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>

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

это умное решение, хорошее :) Я всегда использую один и тот же шрифт / размер, так что он мне подходит. Я обнаружил, что производительность решений DOM / Canvas представляет собой настоящую проблему, это действительно чисто, ура!

mikeapr4 31.12.2018 18:58

устраняет необходимость в холсте +1

frage 28.01.2019 02:48

Вы можете избавиться от * fontSize и просто использовать их

Matt Fletcher 12.02.2020 19:17

Какой замечательный ответ!

almosnow 20.05.2020 20:17

хорошее решение, потому что это также работает на стороне сервера.

Anurag Hazra 13.12.2020 17:07

The Element.getClientRects() method returns a collection of DOMRect objects that indicate the bounding rectangles for each CSS border box in a client. The returned value is a collection of DOMRect objects, one for each CSS border box associated with the element. Each DOMRect object contains read-only left, top, right and bottom properties describing the border box, in pixels, with the top-left relative to the top-left of the viewport.

Element.getClientRects() by Mozilla Contributors is licensed under 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>

В случае, если кто-то еще искал способ измерения ширины строки и, чтобы узнать, какой самый большой размер шрифта подходит для определенной ширины, вот функция, которая основывается на @ Решение Доми с двоичным поиском:

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

Это работает потрясающе (в сочетании с кодом из ответа Доми)

r3wt 01.05.2020 23:32

Перепиши свой ответ с нуля (спасибо за минус). Теперь функция принимает правила текста и 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>

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

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

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

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

Другие вопросы по теме