¿Entrada de escala automática [tipo = texto] al ancho del valor?

85

¿Hay alguna manera de escalar el ancho de un <input type="text">al ancho del valor real?

input {
  display: block;
  margin: 20px;
  width: auto;
}
<input type="text" value="I've had enough of these damn snakes, on this damn plane!" />

<input type="text" value="me too" />

Caminante
fuente

Respuestas:

86

Puede hacer esto de la manera más fácil configurando el sizeatributo a la longitud del contenido de entrada:

function resizeInput() {
    $(this).attr('size', $(this).val().length);
}

$('input[type="text"]')
    // event handler
    .keyup(resizeInput)
    // resize on page load
    .each(resizeInput);

Ver: http://jsfiddle.net/nrabinowitz/NvynC/

Esto parece agregar algo de relleno a la derecha que sospecho que depende del navegador. Si desea que esté realmente ajustado a la entrada, puede usar una técnica como la que describo en esta respuesta relacionada , usando jQuery para calcular el tamaño de píxel de su texto.

nrabinowitz
fuente
23
El "relleno de la derecha" no depende del navegador, es porque el tamaño del elemento se especifica en caracteres, pero para muchas fuentes no todos los caracteres tienen el mismo ancho. Complete su campo con 'iiiiiiiiiii' en una fuente de ancho variable y verá el problema con mayor claridad.
Mark
@Mark: punto tomado, pero la parte que depende del navegador es cómo se interpreta el atributo "tamaño"; no creo que el ancho exacto sea estándar en todos los navegadores.
nrabinowitz
... pruébelo con una fuente monoespaciada y verá que todavía hay relleno correcto en algunos navegadores: jsfiddle.net/nrabinowitz/NvynC/151
nrabinowitz
@nrabinowitz Consulte jsfiddle.net/NvynC/662 para ver una pequeña modificación que agrega un valor MAX que es muy necesario en la mayoría de los casos.
GJ
1
Buen código, creo que es mejor usar la Courier Newfuente para el cuadro de texto, porque el ancho de todos los caracteres en esta fuente es igual. ( jsfiddle.net/NabiKAZ/NvynC/745 )
Nabi KAZ
45

UNA SOLUCIÓN SIMPLE PERO PERFECTA DE PÍXELES

He visto varias formas de hacer esto, pero calcular el ancho de las fuentes no siempre es 100% exacto, es solo una estimación.

Me las arreglé para crear una forma perfecta de píxeles de ajustar el ancho de entrada al tener un marcador de posición oculto para medir.


jQuery (recomendado)

$(function(){
  $('#hide').text($('#txt').val());
  $('#txt').width($('#hide').width());
}).on('input', function () {
  $('#hide').text($('#txt').val());
  $('#txt').width($('#hide').width());
});
body,
#txt,
#hide{
  font:inherit;
  margin:0;
  padding:0;
}
#txt{
  border:none;
  color:#888;
  min-width:10px;
}
#hide{
  display:none;
  white-space:pre;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<p>Lorem ipsum 
  <span id="hide"></span><input id="txt" type="text" value="type here ...">
  egestas arcu.
</p>


JavaScript puro

No pude determinar cómo jQuery calcula el ancho de los elementos ocultos, por lo que se requirió un ligero ajuste en css para adaptarse a esta solución.

var hide = document.getElementById('hide');
var txt = document.getElementById('txt');
resize();
txt.addEventListener("input", resize);

function resize() {
  hide.textContent = txt.value;
  txt.style.width = hide.offsetWidth + "px";
}
body,
#txt,
#hide {
  font: inherit;
  margin: 0;
  padding: 0;
}

#txt {
  border: none;
  color: #888;
  min-width: 10px;
}

#hide {
  position: absolute;
  height: 0;
  overflow: hidden;
  white-space: pre;
}
<p>Lorem ipsum
  <span id="hide"></span><input id="txt" type="text" value="type here ..."> egestas arcu.
</p>

DreamTeK
fuente
2
eso funciona genial! ¡Todavía preferiría hacer eso sin jQuerry pero solo Vanilla JS! aquí un jsfiddle con el código anterior: jsfiddle.net/kjxdr50a
Tren nocturno
3
@NightTrain A su solicitud. Se agregó una solución de JavaScript. =)
DreamTeK
al comparar las dos soluciones jQuerry: jsfiddle.net/kjxdr50a/42 JS: jsfiddle.net/kjxdr50a/44, la solución jQuerry funciona un poco mejor ya que no hay fallas. (Lo arreglé agregando 2 píxeles) @Obsidian, ¿estás bien cuando cito esta solución en una de mis preguntas, que es muy similar?
Tren nocturno
¡Gracias por toda tu ayuda! Finalmente logré implementarlo en mi aplicación Angular. Se puede encontrar una demostración en StackBlitz . Y la pregunta que hice con respecto a la entrada receptiva: stackoverflow.com/a/49478320/9058671 . Intentaré mejorar mi código y actualizar la demostración ..
Night Train
1
Excelente respuesta, agregaría una cosa más: si su entrada tiene relleno (por supuesto, el elemento fantasma hidedebe tener el mismo relleno), la height: 0;parte no funcionará correctamente. Una solución para este problema sería usar position: fixed; top: -100vh;, también puede agregar opacity: 0;solo para estar seguro.
Kamil Bęben
35

Si por alguna razón las otras soluciones no funcionan para usted, puede usar un intervalo contenteditable en lugar de un elemento de entrada.

<span contenteditable="true">dummy text</span>

Tenga en cuenta que esto es más un truco y tiene el grave inconveniente de permitir una entrada HTML totalmente no higienizada, como permitir que los usuarios ingresen (y peguen) saltos de línea, enlaces y otro HTML.

Por lo tanto , probablemente no debería usar esta solución a menos que esté desinfectando cuidadosamente la entrada ...

Actualización : probablemente desee utilizar la solución de DreamTeK a continuación .

mb21
fuente
1
¡Esta solución transfiere la tarea al navegador! ¡De lejos la mejor solución!
user2033412
8
el usuario puede pegar algo más que saltos de línea en contenido editable. puede pegar imágenes, iframes, casi cualquier html.
CodeToad
2
Además, pegar texto formateado de editores como MS Word o archivos PDF da resultados inesperados, generalmente usar esto en lugar de un elemento de entrada nativo es realmente difícil y no vale la pena (todavía).
Senthe
7
esta es realmente una receta para el desastre
Joseph Nields
2
contenteditablees para configurar un editor de texto WYSIWYG de texto enriquecido. No para cambiar el tamaño de las entradas.
Joseph Nields
10

Editar : el complemento ahora funciona con caracteres de espacio en blanco finales. Gracias por señalarlo @JavaSpyder

Dado que la mayoría de las otras respuestas no coincidían con lo que necesitaba (o simplemente no funcionó en absoluto), modifiqué la respuesta de Adrian B en un complemento de jQuery adecuado que da como resultado una escala de entrada perfecta de píxeles sin necesidad de que cambie su css o html.

Ejemplo: https://jsfiddle.net/587aapc2/

Uso:$("input").autoresize({padding: 20, minWidth: 20, maxWidth: 300});

Enchufar:

//JQuery plugin:
$.fn.textWidth = function(_text, _font){//get width of text with font.  usage: $("div").textWidth();
        var fakeEl = $('<span>').hide().appendTo(document.body).text(_text || this.val() || this.text()).css({font: _font || this.css('font'), whiteSpace: "pre"}),
            width = fakeEl.width();
        fakeEl.remove();
        return width;
    };

$.fn.autoresize = function(options){//resizes elements based on content size.  usage: $('input').autoresize({padding:10,minWidth:0,maxWidth:100});
  options = $.extend({padding:10,minWidth:0,maxWidth:10000}, options||{});
  $(this).on('input', function() {
    $(this).css('width', Math.min(options.maxWidth,Math.max(options.minWidth,$(this).textWidth() + options.padding)));
  }).trigger('input');
  return this;
}



//have <input> resize automatically
$("input").autoresize({padding:20,minWidth:40,maxWidth:300});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input value="i magically resize">
<br/><br/>
called with:
$("input").autoresize({padding: 20, minWidth: 40, maxWidth: 300});

Marc Guiselin
fuente
1
Buena solución, pero no parece tener en cuenta los espacios finales (agregue un montón de espacios finales a la entrada)
SpyderScript
1
Atención, no funciona con Firefox porque fontes una forma abreviada de CSS (extrañamente, FF no puede manejarlo ). Puede solucionar este problema mediante la definición de la primera función: var _this_font = [this.css('font-style'), this.css('font-variant'), this.css('font-weight'), 'normal', this.css('font-size') + ' / ' + this.css('line-height'), this.css('font-family')].join(' ');. Entonces cambia this.css('font')a ser _this_font.
Garrett
@Garrett muchas gracias por la corrección de Firefox. Me ahorraste mucho tiempo.
maiko
8

Tengo un complemento jQuery en GitHub: https://github.com/MartinF/jQuery.Autosize.Input

Refleja el valor de la entrada, calcula el ancho y lo usa para configurar el ancho de la entrada.

Puedes ver un ejemplo en vivo aquí: http://jsfiddle.net/mJMpw/2175/

Ejemplo de cómo usarlo (porque se necesita algo de código al publicar un enlace jsfiddle):

<input type="text" value="" placeholder="Autosize" data-autosize-input='{ "space": 40 }' />

input[type="data-autosize-input"] {
  width: 90px;
  min-width: 90px;
  max-width: 300px;
  transition: width 0.25s;    
}

Simplemente use css para establecer el ancho mínimo / máximo y use una transición en el ancho si desea un efecto agradable.

Puede especificar el espacio / distancia hasta el final como el valor en notación json para el atributo data-autosize-input en el elemento de entrada.

Por supuesto, también puede inicializarlo usando jQuery

$("selector").autosizeInput();
MartinF
fuente
6

Ya hay muchas buenas respuestas aquí. Por diversión, implementé esta solución a continuación, basándome en las otras respuestas y en mis propias ideas.

<input class="adjust">

El elemento de entrada se ajusta con precisión de píxeles y se puede definir un desplazamiento adicional.

function adjust(elements, offset, min, max) {

    // Initialize parameters
    offset = offset || 0;
    min    = min    || 0;
    max    = max    || Infinity;
    elements.each(function() {
        var element = $(this);

        // Add element to measure pixel length of text
        var id = btoa(Math.floor(Math.random() * Math.pow(2, 64)));
        var tag = $('<span id="' + id + '">' + element.val() + '</span>').css({
            'display': 'none',
            'font-family': element.css('font-family'),
            'font-size': element.css('font-size'),
        }).appendTo('body');

        // Adjust element width on keydown
        function update() {

            // Give browser time to add current letter
            setTimeout(function() {

                // Prevent whitespace from being collapsed
                tag.html(element.val().replace(/ /g, '&nbsp'));

                // Clamp length and prevent text from scrolling
                var size = Math.max(min, Math.min(max, tag.width() + offset));
                if (size < max)
                    element.scrollLeft(0);

                // Apply width to element
                element.width(size);
            }, 0);
        };
        update();
        element.keydown(update);
    });
}

// Apply to our element
adjust($('.adjust'), 10, 100, 500);

El ajuste se suaviza con una transición CSS.

.adjust {
    transition: width .15s;
}

Aquí está el violín . Espero que esto pueda ayudar a otros que buscan una solución limpia.

danijar
fuente
4

En lugar de intentar crear un div y medir su ancho, creo que es más confiable medir el ancho directamente usando un elemento de lienzo que es más preciso.

function measureTextWidth(txt, font) {
    var element = document.createElement('canvas');
    var context = element.getContext("2d");
    context.font = font;
    return context.measureText(txt).width;
}

Ahora puede usar esto para medir cuál debería ser el ancho de algún elemento de entrada en cualquier momento haciendo esto:

// assuming inputElement is a reference to an input element (DOM, not jQuery)
var style = window.getComputedStyle(inputElement, null);
var text = inputElement.value || inputElement.placeholder;
var width = measureTextWidth(text, style.font);

Esto devuelve un número (posiblemente punto flotante). Si desea tener en cuenta el relleno, puede intentar esto:

  var desiredWidth = (parseInt(style.borderLeftWidth) +
      parseInt(style.paddingLeft) +
      Math.ceil(width) +
      1 + // extra space for cursor
      parseInt(style.paddingRight) +
      parseInt(style.borderRightWidth))
  inputElement.style.width = desiredWidth + "px";
ハ セ ン
fuente
3

Puede resolver este problema como aquí :) http://jsfiddle.net/MqM76/217/

HTML:

<input id="inpt" type="text" />
<div id="inpt-width"></div>

JS:

$.fn.textWidth = function(text, font) {
    if (!$.fn.textWidth.fakeEl) $.fn.textWidth.fakeEl =      $('<span>').hide().appendTo(document.body);
    $.fn.textWidth.fakeEl.text(text || this.val() || this.text()).css('font', font || this.css('font'));
    return $.fn.textWidth.fakeEl.width(); 
};

$('#inpt').on('input', function() {
    var padding = 10; //Works as a minimum width
    var valWidth = ($(this).textWidth() + padding) + 'px';
    $('#'+this.id+'-width').html(valWidth);
    $('#inpt').css('width', valWidth);
}).trigger('input');
Adrián B
fuente
3

Desafortunadamente, el sizeatributo no funcionará muy bien. Habrá espacio adicional y muy poco espacio a veces, dependiendo de cómo esté configurada la fuente. (mira el ejemplo)

Si desea que esto funcione bien, intente observar los cambios en la entrada y luego cambie su tamaño. Probablemente desee configurarlo en la entradascrollWidth . También deberíamos tener en cuenta el tamaño de la caja.

En el siguiente ejemplo, establezco el valor sizede la entrada en 1 para evitar que tenga un scrollWidthancho mayor que nuestro ancho inicial (establecido manualmente con CSS).

// (no-jquery document.ready)
function onReady(f) {
    "complete" === document.readyState
        ? f() : setTimeout(onReady, 10, f);
}

onReady(function() {
    [].forEach.call(
        document.querySelectorAll("input[type='text'].autoresize"),
        registerInput
    );
});
function registerInput(el) {
    el.size = 1;
    var style = el.currentStyle || window.getComputedStyle(el),
        borderBox = style.boxSizing === "border-box",
        boxSizing = borderBox
            ? parseInt(style.borderRightWidth, 10) +
                parseInt(style.borderLeftWidth, 10)
            : 0;
    if ("onpropertychange" in el) {
         // IE
         el.onpropertychange = adjust;
    } else if ("oninput" in el) {
         el.oninput = adjust;
    }
    adjust();

    function adjust() {

        // reset to smaller size (for if text deleted) 
        el.style.width = "";

        // getting the scrollWidth should trigger a reflow
        // and give you what the width would be in px if 
        // original style, less any box-sizing
        var newWidth = el.scrollWidth + boxSizing;

        // so let's set this to the new width!
        el.style.width = newWidth + "px";
    }
}
* {
  font-family: sans-serif;
}
input.autoresize {
  width: 125px;
  min-width: 125px;
  max-width: 400px;
}
input[type='text'] {
  box-sizing: border-box;
  padding: 4px 8px;
  border-radius: 4px;
  border: 1px solid #ccc;
  margin-bottom: 10px;
}
<label> 
  Resizes:
  <input class="autoresize" placeholder="this will resize" type='text'>
</label>
<br/>
<label>
  Doesn't resize:
<input placeholder="this will not" type='text'>
</label>
<br/>
<label>
  Has extra space to right:
  <input value="123456789" size="9" type="text"/>
</label>

Creo que esto debería funcionar incluso en IE6, pero no confíe en mi palabra.

Dependiendo de su caso de uso, es posible que deba vincular la función de ajuste a otros eventos. Por ejemplo, cambiar el valor de una entrada mediante programación, o cambiar la displaypropiedad de estilo del elemento de none(dónde scrollWidth === 0) a blocko inline-block, etc.

Joseph Nields
fuente
3

Encontré otra solución para este problema que no involucra a JS. En HTML acabo de poner algo como:

<div>
  <input class="input" value={someValue} />
  <div class="ghost-input">someValue</div>
</div>

Todo lo que se necesita es configurar la visibilidad: oculta en la entrada fantasma y el ancho: 100% en la entrada misma. Funciona porque la entrada se escala al 100% de su contenedor, cuyo ancho es calculado por el propio navegador (basado en el mismo texto).

Si agrega algo de relleno y borde al campo de entrada, debe ajustar su clase de entrada fantasma en consecuencia (o usar calc () en la clase de entrada).

Piotr Siatkowski
fuente
1

Mi complemento jQuery funciona para mí:

Uso:

    $('form input[type="text"]').autoFit({

    });

Código fuente de jquery.auto-fit.js:

;
(function ($) {
    var methods = {
        init: function (options) {
            var settings = $.extend(true, {}, $.fn.autoFit.defaults, options);
            var $this = $(this);

            $this.keydown(methods.fit);

            methods.fit.call(this, null);

            return $this;
        },

        fit: function (event) {
            var $this = $(this);

            var val = $this.val().replace(' ', '-');
            var fontSize = $this.css('font-size');
            var padding = $this.outerWidth() - $this.width();
            var contentWidth = $('<span style="font-size: ' + fontSize + '; padding: 0 ' + padding / 2 + 'px; display: inline-block; position: absolute; visibility: hidden;">' + val + '</span>').insertAfter($this).outerWidth();

            $this.width((contentWidth + padding) + 'px');

            return $this;
        }
    };

    $.fn.autoFit = function (options) {
        if (typeof options == 'string' && methods[options] && typeof methods[options] === 'function') {
            return methods[options].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof options === 'object' || !options) {
            // Default to 'init'
            return this.each(function (i, element) {
                methods.init.apply(this, [options]);
            });
        } else {
            $.error('Method ' + options + ' does not exist on jquery.auto-fit.');
            return null;
        }
    };

    $.fn.autoFit.defaults = {};

})(this['jQuery']);
Jeff Tian
fuente
¿Por qué empiezas con un punto y coma en jquery.auto-fit.js ? ¿Por qué no hay un punto y coma en la línea "$ this.keydown (methods.fit)"?
Peter Mortensen
1
@PeterMortensen, gracias por su cuidadosa revisión. Agregué un punto y coma junto a la línea que mencionaste, se había perdido. Para el punto y coma inicial, era mi costumbre evitar problemas causados ​​por otros archivos que no terminan en punto y coma.
Jeff Tian
0

Los elementos de entrada se comportan de manera diferente a otros elementos, que harían casi lo que usted desea si se los da float: left(consulte http://jsfiddle.net/hEvYj/5/ ). No creo que sea posible sin calcularlo de alguna manera con JavaScript (es decir, agregue 5px al ancho por letra en el cuadro).

Arve Systad
fuente
0

La solución del usuario nrabinowitz funciona muy bien, pero uso el keypressevento en lugar de keyup. Eso reduce la latencia si el usuario escribe lentamente.

Timoteo A
fuente
usar oninput(o onpropertychangepara IE heredado) es el evento correcto, ya que responden a cosas como pegar con un clic derecho o seleccionar "Archivo -> pegar" en la barra de menú del navegador
Joseph Nields
0

Aquí está mi modificación de la solución de nrabinowitz . No usé la propiedad de tamaño , porque no es perfecta con fuentes proporcionales como señaló @Mark. Mi solución coloca un elemento después de su entrada y obtiene el ancho contado por el navegador (usando jQuery).

Aunque no lo pruebo, supongo que funcionará solo si se heredan todas las propiedades CSS que afectan a la fuente.

El ancho de entrada cambia en el evento focusout , que funciona mejor para mí. Pero también puede usar tecla arriba / pulsación de tecla para cambiar el ancho de entrada al escribir.

function resizeInput() {

    //Firstly take the content or placeholder if content is missing.
    var content =
        $(this).val().length > 0 ? $(this).val() : $(this).prop("placeholder");

    //Create testing element with same content as input.
    var widthTester = $("<span>"+content+"</span>").hide();

    //Place testing element into DOM after input (so it inherits same formatting as input does).
    widthTester.insertAfter($(this));

    //Set inputs width; you may want to use outerWidth() or innerWidth()
    //depending whether you want to count padding and border or not.
    $(this).css("width",widthTester.width()+"px");

    //Remove the element from the DOM
    widthTester.remove();
 }

 $('.resizing-input').focusout(resizeInput).each(resizeInput);
fandasson
fuente
0

Usando canvas podríamos calcular el ancho de los elementos:

function getTextWidth(text, fontSize, fontName) {
  let canvas = document.createElement('canvas');
  let context = canvas.getContext('2d');
  context.font = fontSize + fontName;
  return context.measureText(text).width;
}

y utilícelo en el evento elegido:

function onChange(e) {
  let width = getTextWidth(this.value, $(this).css('font-size'), 
  $(this).css('font-family'));
  $(this.input).css('width', width);
}
Henrique Coura
fuente
0

Prueba la solución Canvas MeasureText

css:

    input{
        min-width:10px!important;
        max-width:99.99%!important;
        transition: width 0.1s;
        border-width:1px;
    }

javascript:

function getWidthOfInput(input){
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');
    var text = input.value.length ? input.value : input.placeholder;
    var style = window.getComputedStyle(input);
    ctx.lineWidth = 1;
    ctx.font = style.font;
    var text_width = ctx.measureText(text).width;
    return text_width;
}

function resizable (el, factor) {
    function resize() {
        var width = getWidthOfInput(el);
        el.style.width = width + 'px';
    }
    var e = 'keyup,keypress,focus,blur,change'.split(',');
    for (var i in e){
        el.addEventListener(e[i],resize,false);
    }
    resize();
}

$( "input" ).each( function(i){
    resizable(this);
});
Konstantin Eletskiy
fuente
0

Resolví el ancho creando un lienzo y calculando su tamaño. Es importante que el valor de entrada y el lienzo compartan las mismas características de fuente (familia, tamaño, peso ...)

import calculateTextWidth from "calculate-text-width";

/*
 requires two props "value" and "font"
  - defaultFont: normal 500 14px sans-serif 
 */
const defaultText = 'calculate my width'
const textFont = 'normal 500 14px sans-serif'
const calculatedWidth = calculateTextWidth(defaultText, textFont)
console.log(calculatedWidth) // 114.37890625

GitHub: https://github.com/ozluy/calculate-text-width CodeSandbox: https://codesandbox.io/s/calculate-text-width-okr46

ozluy
fuente