Auto-scaling input[type=text] to width of value?
Asked Answered
D

18

115

Is there a way to scale the width of an <input type="text"> to the width of the actual value?

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" />
Decapod answered 11/11, 2011 at 22:31 Comment(1)
See https://mcmap.net/q/189625/-calculating-text-widthImbibe
C
95

You can do this the easy way by setting the size attribute to the length of the input contents:

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

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

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

This seems to add some padding on the right that I suspect is browser dependent. If you wanted it to be really tight to the input, you could use a technique like the one I describe in this related answer, using jQuery to calculate the pixel size of your text.

Clowers answered 11/11, 2011 at 22:54 Comment(9)
The "padding on the right" is not browser dependent, it's because the size of the element is specified in characters, but for many fonts not all characters have the same width. Fill your field with 'iiiiiiiiiii' in a variable-width font and you'll see the problem more clearly.Lacteal
@Lacteal - Point taken, but the browser-dependent part is how the "size" attribute is interpreted - I don't think the exact width is standard across browsers.Clowers
...try it with a monospace font and you'll see there's still right padding in some browsers: jsfiddle.net/nrabinowitz/NvynC/151Clowers
@Clowers See jsfiddle.net/NvynC/662 for a slight modification that adds a MAX value which is much needed in most cases.Dissert
nice code, I thinks better use Courier New font for textbox, because width of all chars in this font is equal. (jsfiddle.net/NabiKAZ/NvynC/745)Stelly
Hint: Do not forget to have width:auto; in CSS.Expressage
i updated the code, for the input to resize on keydown & keyup but for some fonts (eg Roboto) the input doesn't resize fast enough. jsfiddle.net/6mLto1w2Pilfer
What's the difference between size and width?Ideality
@Ideality see developer.mozilla.org/en-US/docs/Web/HTML/Element/… - for text input, the size attribute unit is em, while for width the attribute unit is pixels.Clowers
D
70

A SIMPLE BUT PIXEL PERFECT SOLUTION

I have seen several ways to do this but calculating the width of fonts isn't always 100% accurate, it's just an estimate.

I managed to create a pixel perfect way of adjusting the input width by having a hidden placeholder to measure from.


jQuery

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

#txt:focus-visible {
  outline: none;
}

#hide {
  display: none;
  white-space: pre;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

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

Pure JavaScript

I was unable to determine how jQuery calculates the width of hidden elements so a slight tweak to css was required to accommodate this solution.

const hide = document.getElementById('hide');
const 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;
}

#txt:focus-visible {
  outline: none;
}

#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>
Deach answered 10/8, 2016 at 7:49 Comment(5)
that works awesome! I would still prefer to do that without jQuerry but vanilla JS only! here a jsfiddle with the above code: jsfiddle.net/kjxdr50aPilfer
@NightTrain At your request. JavaScript solution added. =)Deach
when comparing the two solutions jQuerry: jsfiddle.net/kjxdr50a/42 JS: jsfiddle.net/kjxdr50a/44 the jQuerry solution performs slightly better since there are no glitches. (I fixed it by adding 2 pixels) @Obsidian are you ok when i quote this solution in one of my questions, that is very similar?Pilfer
Thanks for all your help! I finally managed to implement it into my Angular app. A demo can be found on StackBlitz. And the Question I asked regarding the responsive input: https://mcmap.net/q/189627/-expand-input-width-dynamically-to-the-length-of-string. I'll try to improve my code and update the demo..Pilfer
Great answer, i would add one thing hovewer: If your input has padding (of course ghost element hide must have the same padding) height: 0; part won't work correctly. A workaround for this issue would be to use position: fixed; top: -100vh;, you can also add opacity: 0; just to be safe.Ortegal
D
43

If for some reason the other solutions don't work for you, you could use a contenteditable-span instead of an input element.

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

Note that this is more of a hack and has the severe drawback of allowing totally unsanitized HTML input like letting users enter (and paste) linebreaks, links and other HTML.

So you probably shouldn't use this solution unless you're very carefully sanitising the input...

Update: you probably want to use DreamTeK's solution below.

District answered 25/2, 2013 at 12:7 Comment(8)
This solution defers the task to the browser! By far the best solution!Normalie
user can paste more than just line breaks in content-editable. he can paste images, iframes, just about any html.Intrauterine
Besides the drawback that you can't easily and reliably make the contenteditable read-only, you also can't reliably programmatically select all text in it (useful e.g. to let the user copy a coupon code to the clipboard easily).Chabot
Also pasting formatted text from editors like MS Word or PDF files gives unexpected results, generally using this instead of a native input element is really hard and not worth it (yet).Henchman
this is really a recipe for disasterCommonage
contenteditable is for setting up a rich-text WYSIWYG text-editor. Not for making inputs resize.Commonage
Did you know #456 :The content editable span is also called 'hacker's heaven'Fabrice
Note that in some browsers (not Firefox) contenteditable accepts the value "plaintext-only". It's also worth mentioning that, sanitisation handled, this method also allows for automatic line-wrapping and other dimension constraints that are not afforded with an <input> solution.Harmless
M
13

Edit: The plugin now works with trailing whitespace characters. Thanks for pointing it out @JavaSpyder

Since most other answers didn't match what I needed(or simply didn't work at all) I modified Adrian B's answer into a proper jQuery plugin that results in pixel perfect scaling of input without requiring you to change your css or html.

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

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

Plugin:

//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});
Malarkey answered 11/12, 2015 at 13:20 Comment(3)
Nice solution, but doesn't seem to account for trailing spaces (add a bunch of trailing spaces to the input)Gilgilba
Heads up, it doesn't work with Firefox because font is a CSS shorthand (strangely FF can't handle it). You can fix this by defining in the first function: 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(' ');. Then change this.css('font') to be _this_font.Flyover
@Flyover thanks so much for that Firefox fix. You saved me lots of time.Dutchman
T
13

I've found another solution for this problem not involving JS. In HTML I just put something like:

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

All is needed is to set visibility: hidden on ghost-input and width: 100% on the input itself. It works because input scales to the 100% of its container which width is calculated by the browser itself (based on the same text).

If you add some padding and border to the input field you have to adjust your ghost-input class accordingly (or use calc() in input class).

Tisman answered 26/1, 2020 at 18:52 Comment(4)
What display rule are you using on the outer-most div? With inline-block, the input expands to fit it's value, but won't shrink any smaller than the browser's default width for inputs.Brockway
seems like a great answer, but the code would have made it better... in a snippet. Seeing is believing.Stand
I can't edit your answer because the queue is full, but if you put display: inline-block on the outer div and size="1" inside the input tag, it will shrink the default width, at least in chrome. It's not perfect, especially if you don't remove default padding, but it's close enough for me, and a million times easier than any other answer here!!! .my-input {width: 100%;} .ghost-input {visibility: hidden;} .outer-div {display: inline-block;} <input class="my-input" value="small string" size="1" /> <div class="ghost-input">small string</div>Stand
A super solution, also put height:0 so you only get the width, get the same font family, font size and padding as the input. You can put the visibility on so you can measure both look and measure the same it works like a charm.Madlin
I
9

I have a jQuery plugin on GitHub: https://github.com/MartinF/jQuery.Autosize.Input

It mirrors the value of the input, calculates the width and uses it for setting the width of the input.

You can see an live example here: http://jsfiddle.net/mJMpw/2175/

Example of how to use it (because some code is needed when posting a jsfiddle link):

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

You just use css to set min/max-width and use a transition on the width if you want a nice effect.

You can specify the space / distance to the end as the value in json notation for the data-autosize-input attribute on the input element.

Of course you can also just initialize it using jQuery

$("selector").autosizeInput();
Ivett answered 13/6, 2013 at 23:31 Comment(0)
G
7

There are already a lot of good answers here. For fun, I implemented this solution below, based on the other answers and my own ideas.

<input class="adjust">

The input element is adjusted pixel accurate and an additional offset can be defined.

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

The adjustment gets smoothed with a CSS transition.

.adjust {
    transition: width .15s;
}

Here is the fiddle. I hope this can help others looking for a clean solution.

Gilded answered 15/3, 2014 at 11:30 Comment(0)
U
5

Instead of trying to create a div and measure its width, I think it's more reliable to measure the width directly using a canvas element which is more accurate.

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

Now you can use this to measure what the width of some input element should be at any point in time by doing this:

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

This returns a number (possibly floating point). If you want to account for padding you can try this:

  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";
Uralic answered 19/4, 2017 at 7:12 Comment(0)
G
3

You can solve this problem as here :) 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');
Gravettian answered 4/6, 2014 at 18:42 Comment(0)
C
3

Unfortunately the size attribute will not work very well. There will be extra space and too little space sometimes, depending on how the font is set up. (check out the example)

If you want this to work well, try watching for changes on the input, and resize it then. You probably want to set it to the input's scrollWidth. We would need to account for box sizing, too.

In the following example, I'm setting the size of the input to 1 to prevent it from having a scrollWidth that is greater than our initial width (set manually with 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>

I think this should work in even IE6, but don't take my word for it.

Depending on your use case, you may need to bind the adjust function to other events. E.g. changing an input's value programmatically, or changing the element's style's display property from none (where scrollWidth === 0) to block or inline-block, etc.

Commonage answered 26/9, 2016 at 22:0 Comment(0)
E
2

try canvas measureText solution

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);
});
Enshroud answered 29/11, 2017 at 12:42 Comment(1)
I REALLY wanted this to work, but it appears the method is very slow, causing a jittery UX when entering into the field ... jsfiddle.net/designosis/s6xjwmygDurgy
S
1

My jQuery plugin works for me:

Usage:

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

    });

Source code of 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']);
Sefton answered 3/3, 2015 at 11:55 Comment(2)
Why do you start out with a semicolon in jquery.auto-fit.js? Why isn't there a semicolon in line "$this.keydown(methods.fit)"?Cambric
@PeterMortensen, thanks for your careful review. I added semicolon next to the line you mentioned, it had been lost. For the starting semicolon, it was just my habit to avoid issues caused by some other files that don't end with semicolon.Sefton
S
0

Input elements do behave differently from other elements, which would do just about what you want if you give them float: left (see http://jsfiddle.net/hEvYj/5/). I do not think that is possible without calculating it in some way with JavaScript (i.e. add 5px to the width per letter in the box).

Susannsusanna answered 11/11, 2011 at 22:40 Comment(0)
U
0

User nrabinowitz' solution is working great, but I use the keypress event instead of keyup. That reduces the latency if the user types slowly.

Unconditioned answered 15/8, 2013 at 14:51 Comment(1)
using oninput (or onpropertychange for legacy I.E.) is the correct event, as they respond to things like pasting with a right click or selecting "File -> paste" from the browser's menu barCommonage
E
0

Here is my modification of nrabinowitz' solution. I didn't use the size property, because it's not perfect with proportional fonts as @Mark noted. My solution place an element after your input and gets width counted by browser (using jQuery).

Although I don't test it, I suppose it will work only if all CSS properties affecting font are inherited.

The input width changes on focusout event, which works better for me. But you can use keyup/keypress to change input's width when typing as well.

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);
Elidaelidad answered 6/3, 2014 at 14:12 Comment(0)
B
0

Using canvas we could calculate the elements width:

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

and use it on the chosen event:

function onChange(e) {
  let width = getTextWidth(this.value, $(this).css('font-size'), 
  $(this).css('font-family'));
  $(this.input).css('width', width);
}
Bantu answered 11/5, 2017 at 18:6 Comment(1)
Sorry, the example is incomplete ... how do you call onChange?Durgy
A
0

I solved width creating canvas and calculating size of it. its important that input value and canvas share same font features (family, size, weight...)

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

Addend answered 5/3, 2020 at 20:45 Comment(0)
M
0

You can pass any input element in this function to get the proper width of the element. This width will be as if the input element were a span element with all the properties of the original element. It will take into account the font-family, font-size, and all other font properties that could have affected the total width of the text as well as the horizontal border and padding of the input element. Additionally, it will return the width of the placeholder, if there isn't any value in the input element.

So, this width value will be suitable for setting the width of the input element. And in that case, you may also want to set a minimum width to the element in case it has neither a value nor a placeholder.

Also, this function will behave somewhat similar to the offsetWidth property except that this function will append px to the end of the width value, return the width value even if the input element were hidden by setting its display to none, won't round the value to an integer, and won't consider the width of vertical scrollbars if there were.

function getInputWidth(element) {
    const text = element.value || element.placeholder;
    const elementStyle = window.getComputedStyle(element);
    const fontProperty = elementStyle.font;
    const horizontalBorder = parseFloat(elementStyle.borderLeftWidth) + parseFloat(elementStyle.borderRightWidth);
    const horizontalPadding = parseFloat(elementStyle.paddingLeft) + parseFloat(elementStyle.paddingRight);

    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    context.font = fontProperty;
    const textWidth = context.measureText(text).width;

    const totalWidth = horizontalBorder + horizontalPadding + textWidth + "px";
    return totalWidth;
}
Mellen answered 10/7, 2021 at 9:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.