Calcular el ancho del texto con JavaScript

Resuelto Jacob asked hace 16 años • 29 respuestas

Me gustaría usar JavaScript para calcular el ancho de una cadena. ¿Es esto posible sin tener que utilizar un tipo de letra monoespaciado?

Si no está integrado, mi única idea es crear una tabla de anchos para cada carácter, pero esto es bastante irrazonable, especialmente si admite Unicode y diferentes tamaños de letra (y todos los navegadores).

Jacob avatar Sep 23 '08 06:09 Jacob
Aceptado

En HTML 5 , puedes usar el método Canvas.measureText (más explicación aquí ).

Prueba este violín :

/**
  * 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 getCanvasFont(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

Si desea utilizar el tamaño de fuente de algún elemento específico myEl, puede utilizar la getCanvasFontfunción de utilidad:

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

Explicación: La getCanvasFontSizefunción toma la fuente de algún elemento (por defecto: la body's) y la convierte a un formato compatible con la propiedad Context.font . Por supuesto, cualquier elemento debe agregarse primero al DOM antes de su uso; de lo contrario, obtendrá valores falsos.

Más notas

Este enfoque tiene varias ventajas, entre ellas:

  • Más conciso y seguro que otros métodos (basados ​​en DOM) porque no cambia el estado global, como su DOM.
  • Es posible una mayor personalización modificando más propiedades del texto del lienzo , como textAligny textBaseline.

NOTA: Cuando agregue el texto a su DOM, recuerde tener en cuenta también el relleno, el margen y el borde .

NOTA 2: En algunos navegadores, este método produce una precisión de subpíxeles (el resultado es un número de punto flotante), en otros no (el resultado es solo un int). Es posible que desee ejecutar Math.floor(o Math.ceil) en el resultado para evitar inconsistencias. Dado que el método basado en DOM nunca tiene una precisión de subpíxeles, este método tiene una precisión incluso mayor que los otros métodos aquí.

Según este jsperf (gracias a los contribuyentes en los comentarios), el método Canvas y el método basado en DOM son aproximadamente igualmente rápidos, si se agrega almacenamiento en caché al método basado en DOM y no está utilizando Firefox. En Firefox, por alguna razón, este método Canvas es mucho más rápido que el método basado en DOM (a partir de septiembre de 2014).

Actuación

Este violín compara este método Canvas con una variación del método basado en DOM de Bob Monteverde , para que pueda analizar y comparar la precisión de los resultados.

Domi avatar Jan 09 '2014 08:01 Domi

Cree un DIV con los siguientes estilos. En su JavaScript, establezca el tamaño de fuente y los atributos que está intentando medir, coloque su cadena en el DIV y luego lea el ancho y alto actuales del DIV. Se estirará para ajustarse al contenido y el tamaño estará dentro de unos pocos píxeles del tamaño de la cadena representada.

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>
Expandir fragmento

CMPalmer avatar Sep 22 '2008 23:09 CMPalmer

Aquí hay uno que preparé sin ejemplo. Parece que todos estamos en la misma página.

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

Usarlo es simple:"a string".width()

**Se agregó white-space: nowrappara que se puedan calcular cadenas con un ancho mayor que el ancho de la ventana.

Bob Monteverde avatar Feb 18 '2011 23:02 Bob Monteverde

¡Me gusta tu "única idea" de hacer simplemente un mapa de ancho de caracteres estático! De hecho, funciona bien para mis propósitos. A veces, por razones de rendimiento o porque no tienes fácil acceso a un DOM, es posible que solo quieras una calculadora independiente y rápida calibrada para una sola fuente. Aquí hay uno calibrado para Helvetica; pasar una cadena y un tamaño de fuente:

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
}

Esa fea matriz gigante son anchos de caracteres ASCII indexados por código de caracteres. Entonces esto solo admite ASCII (de lo contrario, asume un ancho de carácter promedio). Afortunadamente, el ancho básicamente escala linealmente con el tamaño de fuente, por lo que funciona bastante bien con cualquier tamaño de fuente. Es notablemente carente de conocimiento sobre el interletraje, las ligaduras o lo que sea.

Para "calibrar", simplemente representé cada carácter hasta charCode 126 (la poderosa tilde) en un svg, obtuve el cuadro delimitador y lo guardé en esta matriz; Más código, explicación y demostración aquí .

Toph avatar Jan 09 '2018 16:01 Toph