Reputation: 2971
<input type="text" value="1" style="min-width:1px;" />
This is my code and it is not working. Is there any other way in HTML, JavaScript, PHP or CSS to set minimum width?
I want a text input field with a dynamically changing width, so that the input field fluids around its contents. Every input has a built-in padding of 2em
, that is the problem and second problem is that min-width
ain't working on input at all.
If I set width more than it is needed than the whole program is messy, I need the width of 1px, more only if it's needed.
Upvotes: 294
Views: 571531
Reputation: 5
There is no real way to set the element width to exactly that of the input. Every hack involving another element will result in an incorrect size being calculated as there is a mismatch between element styles and how they are rendered with hidden spacing. The way this is usually done is to destroy the input and put another element (div) in its place upon losing focus. It's then recreated when clicking on the element with the element destroyed. This way it takes up the minimum space at rest which I assume is usually what matters. You can also just hide the elements:
HTML
<div style="display:flex; flex-direction:row;">
<input style="display:none; width:100px; border:1px solid; padding:1px; outline:none;" placeholder="Placeholder" onfocusout="input(this)" />
<div style="max-width:100px; border:1px solid; padding:1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" onclick="div(this)">Placeholder</div>
</div>
JS
function input(e) {
e.style.display = "none";
e.nextElementSibling.innerText = e.value || e.placeholder;
e.nextElementSibling.style.display = "initial";
}
function div(e) {
e.style.display = "none";
e.previousElementSibling.style.display = "initial";
e.previousElementSibling.focus();
}
.innerText
ensures that HTML entities are rendered correctly and not seen as encoding. I didn't use any ID attributes. Using the "sibling" methods makes the functions generic so they can be used with any number of elements as is likely to be the case. Change the styling as needed to make them match including max-width
and overflow
.
Another version is to put an image next to the "input" at rest only indicating it can be edited:
HTML
<div style="display:flex; flex-direction:row;">
<input style="display:none; width:100px; border:1px solid; padding:1px; outline:none;" placeholder="Placeholder" onfocusout="input(this)" />
<div style="display:flex; flex-direction:row;" onclick="div(this)">
<div style="max-width:100px; border:1px solid; padding:1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">Placeholder</div>
<div style="width:16px;height:16px;"><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 121.48 122.88" style="enable-background:new 0 0 121.48 122.88" xml:space="preserve"><path d="M96.84,2.22l22.42,22.42c2.96,2.96,2.96,7.8,0,10.76l-12.4,12.4L73.68,14.62l12.4-12.4 C89.04-0.74,93.88-0.74,96.84,2.22L96.84,2.22z M70.18,52.19L70.18,52.19l0,0.01c0.92,0.92,1.38,2.14,1.38,3.34 c0,1.2-0.46,2.41-1.38,3.34v0.01l-0.01,0.01L40.09,88.99l0,0h-0.01c-0.26,0.26-0.55,0.48-0.84,0.67h-0.01 c-0.3,0.19-0.61,0.34-0.93,0.45c-1.66,0.58-3.59,0.2-4.91-1.12h-0.01l0,0v-0.01c-0.26-0.26-0.48-0.55-0.67-0.84v-0.01 c-0.19-0.3-0.34-0.61-0.45-0.93c-0.58-1.66-0.2-3.59,1.11-4.91v-0.01l30.09-30.09l0,0h0.01c0.92-0.92,2.14-1.38,3.34-1.38 c1.2,0,2.41,0.46,3.34,1.38L70.18,52.19L70.18,52.19L70.18,52.19z M45.48,109.11c-8.98,2.78-17.95,5.55-26.93,8.33 C-2.55,123.97-2.46,128.32,3.3,108l9.07-32v0l-0.03-0.03L67.4,20.9l33.18,33.18l-55.07,55.07L45.48,109.11L45.48,109.11z M18.03,81.66l21.79,21.79c-5.9,1.82-11.8,3.64-17.69,5.45c-13.86,4.27-13.8,7.13-10.03-6.22L18.03,81.66L18.03,81.66z" /></svg></div>
</div>
</div>
JS
function input(e) {
e.style.display = "none";
e.nextElementSibling.firstElementChild.innerText = e.value || e.placeholder;
e.nextElementSibling.style.display = "flex";
}
function div(e) {
e.style.display = "none";
e.previousElementSibling.style.display = "initial";
e.previousElementSibling.focus();
}
Upvotes: 0
Reputation: 41
I've been looking for a simpler way to make the input use only the width of its content instead of using 100% of the parent's width.
I found the new CSS property called field-sizing, which has default value 'fixed' and can be set to 'content'.
It may require a max-width in case you want to also have ellipsis on the value, placed next to another fixed element (like a counter badge icon). Previously, with the default behavior of inputs, the space between the counter badge and the input value could be very big if the value of the input would be just a few characters, but with this property... it would make the badge come closer.
Further research can be done here: polypane blog and here css drafts for CSS Basic User Interface Module Level 4.
Upvotes: 4
Reputation: 123428
It's worth noting that a nice-looking resize can be done when the font is monospaced, so we can perfectly resize the input
element using the ch
unit.
Also in this approach we can update the width of the field by just updating a CSS variable (custom property) on input
event and we should also take care of already pre-filled input on DOMContentLoaded
event
Markup
<input type="text" value="space mono font" class="selfadapt" />
CSS
:root { --size: 0; }
.selfadapt {
padding: 5px;
min-width: 10ch;
font-family: "space mono";
font-size: 1.5rem;
width: calc(var(--size) * 1ch);
}
As a root variable we set --size: 0
: this variable will contain the length of the input and it will be multiplied by 1ch
inside the calc()
expression. By default we could also set a min-width, e.g. 10ch
The Javascript part reads the length of the value inserted and updates the variable --size
:
JS
let input = document.querySelector('.selfadapt');
let root = document.documentElement.style;
/* on input event auto resize the field */
input.addEventListener('input', function() {
root.setProperty('--size', this.value.length );
});
/* resize the field if it is pre-populated */
document.addEventListener("DOMContentLoaded", function() {
root.setProperty('--size', input.value.length);
});
of course this still works even if you don't use monospaced font, but in that case you will need to change the calc()
formula by multiplying the --size
variable by another value (which it's strictly dependent on the font-family
and font-size
) different than 1ch
.
Upvotes: 6
Reputation: 4758
Here is a solution without monospaced font needed, with only a very small piece code of javascript, does not need to calculate computed styles, and even supports IME, supports RTL text.
// copy the text from input to the span
document.addEventListener('input', event => {
const target = event.target;
if (!(target instanceof HTMLElement)) return;
if (!target.matches('.resize-input')) return;
target.previousElementSibling.textContent = target.value;
});
Array.from(document.querySelectorAll('.resize-input')).forEach(input => {
input.previousElementSibling.textContent = input.value;
});
.resize-container {
display: inline-block;
position: relative;
}
.resize-input,
.resize-text {
margin: 0;
padding: 2px 10px;
border: 1px solid #ccc;
border-radius: 3px;
height: 36px;
font: 20px/20px sans-serif;
}
.resize-text {
padding-right: 20px;
display: inline-block;
visibility: hidden;
white-space: pre;
}
.resize-input {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
<div class="resize-container">
<span class="resize-text" aria-hidden="true"></span>
<input class="resize-input" value="some text" autofocus />
</div>
Here are steps
<input>
into a container
. And add an extra <span>
next to it.input
event. If you are using Vanilla JavaScript, you can use event delegate like what this post do. If you are using frameworks like React or Vue, you can simply bind the span with same value of the input have.Vue note: you should listen to @input
instead of use v-model
so it can adjust size correctly during IME composition. Live Demo
Upvotes: 72
Reputation: 21763
To calculate the width of the current input, you'll have to embed it in a temporary span
element, attach that thing to the DOM, get the computed width (in pixels) using the scrollWidth
property and remove the span
again. Of course you'll have to ensure that the same font family, font size, etc., is used in the input
as well as in the span
element. Therefore I assigned the same class to them.
I attached the function to the keyup
event, as on keypress
the input character is not yet added to the input value
, so that will result in the wrong width. Unfortunately, I don't know how to get rid of the scrolling of the input field (when adding characters to the end of the field); it scrolls, because the character is added and shown before adjustWidthOfInput()
is called. And, as said, I can't do this the other way round because then you'll have the value of the input field before the pressed character is inserted. I'll try to solve this issue later.
BTW, I only tested this in Firefox (3.6.8), but you'll get the point, I hope.
var inputEl = document.getElementById("theInput");
function getWidthOfInput() {
var tmp = document.createElement("span");
tmp.className = "input-element tmp-element";
tmp.innerHTML = inputEl.value.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
document.body.appendChild(tmp);
var theWidth = tmp.getBoundingClientRect().width;
document.body.removeChild(tmp);
return theWidth;
}
function adjustWidthOfInput() {
inputEl.style.width = getWidthOfInput() + "px";
}
adjustWidthOfInput();
inputEl.onkeyup = adjustWidthOfInput;
body {
background: #666;
}
.input-element {
border: 0;
padding: 2px;
background: #fff;
font: 12pt sans-serif;
}
.tmp-element {
visibility: hidden;
white-space: pre;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Get/set width of <input></title>
</head>
<body>
<input id="theInput" type="text" class="input-element" value="1">
</body>
</html>
Upvotes: 80
Reputation: 253
You can just set size
attribute.
If you're using one of reactive frameworks, the following will be enough:
<input size="{{ yourValue.length }}" [value]="yourValue" />
but if you use pure js, you should set event handlers, like:
<input oninput="this.setAttribute('size', this.value.length)" />
Upvotes: 7
Reputation: 151
This is a generalization of Matthew Brent's excellent canvas-based solution. It works with select
elements and generic div
's in addition to input
elements. It also accounts for letter spacing.
It is written in Typescript but could be trivially adapted to JS.
/** Cached canvas used by adjustWidthToFitTextLength(). */
let textMeasurementCanvas: HTMLCanvasElement | undefined;
/**
* Sets the width of the given element to the width of its text, plus any extra
* padding specified.
*
* @param element HTML element to measure and set.
* @param extraWidthPx optional extra width to add to the text width, in pixels.
*/
export function adjustWidthToFitTextLength(
element: HTMLElement,
extraWidthPx = 0
): void {
// Credit to https://stackoverflow.com/a/50360743/20152698 for this canvas-
// based approach to calculating text width.
if (!textMeasurementCanvas) {
textMeasurementCanvas = document.createElement("canvas");
}
const context = textMeasurementCanvas.getContext("2d");
if (!context) {
log.error("adjustWidthToFitTextLength() failed to get a canvas context.");
return;
}
const style = window.getComputedStyle(element);
context.font = style.font;
let text: string;
if (element instanceof HTMLInputElement) {
text = element.value;
} else if (element instanceof HTMLSelectElement) {
text = element.options[element.selectedIndex].text;
} else {
text = element.innerText;
}
const textMetrics = context.measureText(text);
// CanvasRenderingContext2D.letterSpacing is not supported on Safari, so we
// calculate the change in width due to letter spacing.
let totalLetterSpacingPx = 0;
if (style.letterSpacing && text.length > 1) {
let letterSpacingPx = 0;
const match = style.letterSpacing.match(/^(?<val>[0-9.]+)(?<unit>px|ex)$/);
if (match?.groups?.unit === "px") {
letterSpacingPx = Number(match.groups?.val);
} else if (match?.groups?.unit === "ex") {
const exWidth = context.measureText("x").width;
letterSpacingPx = Number(match.groups?.val) * exWidth;
} else {
log.warn(`Could not parse letter-spacing: "${style.letterSpacing}"`);
}
totalLetterSpacingPx = letterSpacingPx * (text.length - 1);
}
element.style.width = `${
textMetrics.width + totalLetterSpacingPx + extraWidthPx
}px`;
}
Upvotes: 0
Reputation: 2759
In modern browser versions, CSS unit ch
is also available. To my understanding, it is font-independent unit, where 1ch
equals to width of character 0
(zero) in any given font.
Thus, something as simple as following could be used as resize function, by binding to the input
event:
var input = document.querySelector('input'); // get the input element
input.addEventListener('input', resizeInput); // bind the "resizeInput" callback on "input" event
resizeInput.call(input); // immediately call the function
function resizeInput() {
this.style.width = this.value.length + "ch";
}
input{ font-size:1.3em; padding:.5em; }
<label>Text
<input>
</label>
That example would resize the input to length of the value + 2 characters extra.
One potential problem with the unit ch is that in many fonts (i.e. Helvetica) the width of the character "m" exceeds the width of the character 0 and the character "i" is much narrower. 1ch is usually wider than the average character width, usually by around 20-30% according to this post.
Upvotes: 257
Reputation: 151
Here's a simple function to get what is needed:
function resizeInput() {
const input = document.getElementById('myInput');
input.style.width = `${input.scrollWidth}px`;
};
Upvotes: 2
Reputation: 388
Update for answer https://stackoverflow.com/a/41389961/1022684
If you have multiple inputs and do not want to repeat input text in spans:
$(() => {
$('.container input').on('input', (e) => {
$(e.target).prev().text($(e.target).val())
})
$('.container input').each((idx, elem) => {
$(elem).prev().text($(elem).val())
})
})
Upvotes: 0
Reputation: 477
Here is my React solution, it works with any font size, just make sure you have a monospace font (all font character widths are the same on monospace fonts) like i have in my solution, and it will work perfectly.
JS:
const [value, setValue] = useState(0)
HTML:
<input value={value} onChange={(e) => {setValue(e.target.value)}} style={{width: `${value.toString().length}`ch}}/>
CSS:
@import url("https://fonts.googleapis.com/css2?family=B612+Mono&display=swap");
input{
font-family: "B612 Mono", monospace;
}
Upvotes: 4
Reputation: 1205
Svelte version:
<input type="text" style="width: {tag.length}ch" bind:value={tag} />
Upvotes: 2
Reputation: 9546
It sounds like your expectation is that the style be applied dynamically to the width of the textbox based on the contents of the textbox. If so you will need some js to run on textbox contents changing, something like this:
<input id="txt" type="text" onkeypress="this.style.width = ((this.value.length + 1) * 8) + 'px';">
Note: this solution only works when every character is exactly 8px
wide. You could use the CSS-Unit "ch" (characters) which represents the width of the character "0" in the chosen font. You can read about it here.
Upvotes: 146
Reputation: 106
The best solution is <input ... size={input.value.length} />
Upvotes: 1
Reputation: 589
You would like to change the size attribute as the text changes.
# react
const resizeInput = (e) => {
e.target.setAttribute('size', e.target.value.length || 1);
}
<input
onChange={resizeInput}
size={(propertyInput.current && propertyInput.current.value.length) || 1}
ref={propertyInput}
/>
Upvotes: 0
Reputation: 47
Quite simple:
oninput='this.style.width = (this.scrollWidth - N) + "px";'
Where N is some number (2 in the example, 17 on something I'm developing) that is determined experimentally.
Subtracting N prevents this strange extrenuous space from accumulating long before the text reaches the end of the text box.
Compare. Pay careful attention to how the size changes after even just the first character.
<p>Subtracting N:</p>
<input type="text" placeholder="enter lots of text here" oninput='this.style.width = (this.scrollWidth-2) + "px";'>
<p>Not Subtracting N:</p>
<input type="text" placeholder="enter lots of text here" oninput='this.style.width = (this.scrollWidth) + "px";'>
Upvotes: 3
Reputation: 5081
Here is my 2 cents. Create an empty invisible div. Fill it with the input content and return the width to the input field. Match text styles between each box.
$(".answers_number").keyup(function(){
$( "#number_box" ).html( $( this ).val() );
$( this ).animate({
width: $( "#number_box" ).width()+20
}, 300, function() {
});
});
#number_box {
position: absolute;
visibility: hidden;
height: auto;
width: auto;
white-space: nowrap;
padding:0 4px;
/*Your font styles to match input*/
font-family:Arial;
font-size: 30px;
}
.answers_number {
font-size: 30px;
font-family:Arial;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="number" class="answers_number" />
<div id="number_box">
</div>
Upvotes: 2
Reputation: 3148
If you use Bootstrap, it could be done very easily:
<div contenteditable="true" class="form-control" style="display: inline"></div>
You will just need to fetch div's content and put it in a hidden input before submitting the form.
Upvotes: 2
Reputation: 28728
Just adding on top of other answers.
I noticed that nowadays in some browsers the input field has a scrollWidth. Which means:
this.style.width = this.scrollWidth + 'px';
should work nicely. tested in chrome, firefox and safari.
For deletion support, you can add '=0' first and then readjust.
this.style.width = 0; this.style.width = this.scrollWidth + 'px';
Upvotes: 27
Reputation: 130700
input
elementvar getInputValueWidth = (function(){
// https://stackoverflow.com/a/49982135/104380
function copyNodeStyle(sourceNode, targetNode) {
var computedStyle = window.getComputedStyle(sourceNode);
Array.from(computedStyle).forEach(key => targetNode.style.setProperty(key, computedStyle.getPropertyValue(key), computedStyle.getPropertyPriority(key)))
}
function createInputMeassureElm( inputelm ){
// create a dummy input element for measurements
var meassureElm = document.createElement('span');
// copy the read input's styles to the dummy input
copyNodeStyle(inputelm, meassureElm);
// set hard-coded styles needed for propper meassuring
meassureElm.style.width = 'auto';
meassureElm.style.position = 'absolute';
meassureElm.style.left = '-9999px';
meassureElm.style.top = '-9999px';
meassureElm.style.whiteSpace = 'pre';
meassureElm.textContent = inputelm.value || '';
// add the meassure element to the body
document.body.appendChild(meassureElm);
return meassureElm;
}
return function(){
return createInputMeassureElm(this).offsetWidth;
}
})();
// delegated event binding
document.body.addEventListener('input', onInputDelegate)
function onInputDelegate(e){
if( e.target.classList.contains('autoSize') )
e.target.style.width = getInputValueWidth.call(e.target) + 'px';
}
input{
font-size:1.3em;
padding:5px;
margin-bottom: 1em;
}
input.type2{
font-size: 2.5em;
letter-spacing: 4px;
font-style: italic;
}
<input class='autoSize' value="type something">
<br>
<input class='autoSize type2' value="here too">
Upvotes: 3
Reputation: 1319
FOR A NICER LOOK&FEEL
You should use jQuery keypress() event in combination with String.fromCharCode(e.which) to get the pressed character. Hence you can calculate what your width will be. Why? Because it will look a lot more sexy :)
Here is a jsfiddle that results in a nice behaviour compared to solutions using the keyup event : http://jsfiddle.net/G4FKW/3/
Below is a vanilla JS which listens to the input
event of an <input>
element and sets a span
sibling to have the same text value in order to measure it.
document.querySelector('input').addEventListener('input', onInput)
function onInput(){
var spanElm = this.nextElementSibling;
spanElm.textContent = this.value; // the hidden span takes the value of the input;
this.style.width = spanElm.offsetWidth + 'px'; // apply width of the span to the input
};
/* it's important the input and its span have same styling */
input, .measure {
padding: 5px;
font-size: 2.3rem;
font-family: Sans-serif;
white-space: pre; /* white-spaces will work effectively */
}
.measure{
position: absolute;
left: -9999px;
top: -9999px;
}
<input type="text" />
<span class='measure'></span>
Upvotes: 23
Reputation: 1376
This answer provides one of the most accurate methods of retrieving text width available in the browser and is more accurate than the accepted answer. It uses the canvas html5 element and unlike other answers does not add the element into the DOM and thus avoids any reflow issues caused by excessively adding elements to the DOM.
Read more about the Canvas element here in relation to text width.
NOTE: According to MDN the shorthand versions of the
getPropertyValue()
method such as font can be unreliable. I'd recommend getting the values singularly to improve compatibility. I only used it here for speed.
/**
* returns the width of child text of any DOM node as a float
*/
function getTextWidth(el) {
// uses a cached canvas if available
var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
var context = canvas.getContext("2d");
// get the full font style property
var font = window.getComputedStyle(el, null).getPropertyValue('font');
var text = el.value;
// set the font attr for the canvas text
context.font = font;
var textMeasurement = context.measureText(text);
return textMeasurement.width;
}
var input = document.getElementById('myInput');
// listen for any input on the input field
input.addEventListener('input', function(e) {
var width = Math.floor(getTextWidth(e.target));
// add 10 px to pad the input.
var widthInPx = (width + 10) + "px";
e.target.style.width = widthInPx;
}, false);
#myInput {
font: normal normal 400 normal 18px / normal Roboto, sans-serif;
min-width: 40px;
}
<input id="myInput" />
Upvotes: 19
Reputation: 1451
This is an Angular-specific answer, but this worked for me and has been very satisfying in terms of its simplicity and ease-of-use:
<input [style.width.ch]="value.length" [(ngModel)]="value" />
It automatically updates via the character units in Jani's answer.
Upvotes: 32
Reputation: 28610
Why not using just css?
<div id="wrapper">
<input onkeyup="keyup(event)">
<div id="ghost"></div>
</div>
function keyup(e) {
document.getElementById('ghost').innerText = e.target.value;
}
#wrapper {
position: relative;
min-width: 30px;
display: inline-block;
}
input {
position: absolute;
left:0;
right:0;
border:1px solid blue;
width: 100%;
}
#ghost {
color: transparent;
}
<div id="wrapper">
<input onkeyup="keyup(event)">
<div id="ghost"></div>
</div>
wrapper {
position: relative;
min-width: 30px;
border: 1px solid red;
display: inline-block;
}
input {
position: absolute;
left:0;
right:0;
width: 100%;
}
#ghost {
color: transparent;
}
this code was introduced by @Iain Todd to and I thought I should share it
Upvotes: -2
Reputation:
Better is onvalue
:
<input id="txt" type="text" onvalue="this.style.width = ((this.value.length + 1) * 8) + 'px';">
It also involves pasting, dragging and dropping, etc.
Upvotes: 1
Reputation: 932
Based off Michael's answer, I have created my own version of this using jQuery. I think it is a cleaner/shorter version of most answers here and it seems to get the job done.
I am doing the same thing as most of the people here by using a span to write the input text into then getting the width. Then I am setting the width when the actions keyup
and blur
are called.
Here is a working codepen. This codepen shows how this can be used with multiple input fields.
HTML Structure:
<input type="text" class="plain-field" placeholder="Full Name">
<span style="display: none;"></span>
jQuery:
function resizeInputs($text) {
var text = $text.val().replace(/\s+/g, ' '),
placeholder = $text.attr('placeholder'),
span = $text.next('span');
span.text(placeholder);
var width = span.width();
if(text !== '') {
span.text(text);
var width = span.width();
}
$text.css('width', width + 5);
};
The function above gets the inputs value, trims the extra spaces and sets the text into the span to get the width. If there is no text, it instead gets the placeholder and enters that into the span instead. Once it enters the text into the span it then sets the width of the input. The + 5
on the width is because without that the input gets cut off a tiny bit in the Edge Browser.
$('.plain-field').each(function() {
var $text = $(this);
resizeInputs($text);
});
$('.plain-field').on('keyup blur', function() {
var $text = $(this);
resizeInputs($text);
});
$('.plain-field').on('blur', function() {
var $text = $(this).val().replace(/\s+/g, ' ');
$(this).val($text);
});
If this could be improved please let me know as this is the cleanest solution I could come up with.
Upvotes: 0
Reputation: 935
Here is an alternative way to solve this using a DIV and the 'contenteditable' property:
HTML:
<div contenteditable = "true" class = "fluidInput" data-placeholder = ""></div>
CSS: (to give the DIV some dimensions and make it easier to see)
.fluidInput {
display : inline-block;
vertical-align : top;
min-width : 1em;
height : 1.5em;
font-family : Arial, Helvetica, sans-serif;
font-size : 0.8em;
line-height : 1.5em;
padding : 0px 2px 0px 2px;
border : 1px solid #aaa;
cursor : text;
}
.fluidInput * {
display : inline;
}
.fluidInput br {
display : none;
}
.fluidInput:empty:before {
content : attr(data-placeholder);
color : #ccc;
}
Note: If you are planning on using this inside of a FORM element that you plan to submit, you will need to use Javascript / jQuery to catch the submit event so that you can parse the 'value' ( .innerHTML
or .html()
respectively) of the DIV.
Upvotes: 21
Reputation: 140
I just spend some time figuring out how to do it.
Actually the simplest way I found is to move input value to span just before the input, keeping input 1 symbol width. Though I can't be sure that it fit for your initial need.
Maybe it some extra code, but in react+flux based application it is quite natural solution.
Upvotes: 0
Reputation: 834
Here is a plain JS and a jQuery plugin I wrote that will handle resizing an input element using a canvas and the font size / family to determine the actual string length when rendered. (only works in > IE9, chrome, safari, firefox, opera and most other major browsers that have implemented the canvas element).
PlainJS:
function autoSize(input, o) {
o || (o = {});
o.on || (o.on = 'keyup');
var canvas = document.createElement('canvas');
canvas.setAttribute('style', 'position: absolute; left: -9999px');
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
input.addEventListener(o.on, function () {
ctx.font = getComputedStyle(this,null).getPropertyValue('font');
this.style.width = ctx.measureText(this.value + ' ').width + 'px';
});
}
//Usage
autoSize(document.getElementById('my-input'));
jQuery Plugin:
$.fn.autoSize = function(o) {
o = $.extend({}, {
on: 'keyup'
}, o);
var $canvas = $('<canvas/>').css({position: 'absolute', left: -9999});
$('body').append($canvas);
var ctx = $canvas[0].getContext('2d');
return this.on(o.on, function(){
var $this = $(this);
ctx.font = $this.css('font');
$this.width(ctx.measureText($this.val()).width + 'px');
})
}
//Usage:
$('#my-input').autoSize();
Note: this will not handle text-transforms, line spacing and letter spacing, and probably some other text size changing properties. To handle text-transform property set and adjust the text value to match that property. The others are probably fairly straight forward. I will implement if this starts gaining some traction...
Upvotes: 4
Reputation: 2298
Here's a modification of Lyth's answer that takes into account:
It also allows for any number of input fields! To see it in action: http://jsfiddle.net/4Qsa8/
Script:
$(document).ready(function () {
var $inputs = $('.resizing-input');
// Resize based on text if text.length > 0
// Otherwise resize based on the placeholder
function resizeForText(text) {
var $this = $(this);
if (!text.trim()) {
text = $this.attr('placeholder').trim();
}
var $span = $this.parent().find('span');
$span.text(text);
var $inputSize = $span.width();
$this.css("width", $inputSize);
}
$inputs.find('input').keypress(function (e) {
if (e.which && e.charCode) {
var c = String.fromCharCode(e.keyCode | e.charCode);
var $this = $(this);
resizeForText.call($this, $this.val() + c);
}
});
// Backspace event only fires for keyup
$inputs.find('input').keyup(function (e) {
if (e.keyCode === 8 || e.keyCode === 46) {
resizeForText.call($(this), $(this).val());
}
});
$inputs.find('input').each(function () {
var $this = $(this);
resizeForText.call($this, $this.val())
});
});
Style:
.resizing-input input, .resizing-input span {
font-size: 12px;
font-family: Sans-serif;
white-space: pre;
padding: 5px;
}
HTML:
<div class="resizing-input">
<input type="text" placeholder="placeholder"/>
<span style="display:none"></span>
</div>
$(document).ready(function() {
var $inputs = $('.resizing-input');
// Resize based on text if text.length > 0
// Otherwise resize based on the placeholder
function resizeForText(text) {
var $this = $(this);
if (!text.trim()) {
text = $this.attr('placeholder').trim();
}
var $span = $this.parent().find('span');
$span.text(text);
var $inputSize = $span.width();
$this.css("width", $inputSize);
}
$inputs.find('input').keypress(function(e) {
if (e.which && e.charCode) {
var c = String.fromCharCode(e.keyCode | e.charCode);
var $this = $(this);
resizeForText.call($this, $this.val() + c);
}
});
// Backspace event only fires for keyup
$inputs.find('input').keyup(function(e) {
if (e.keyCode === 8 || e.keyCode === 46) {
resizeForText.call($(this), $(this).val());
}
});
$inputs.find('input').each(function() {
var $this = $(this);
resizeForText.call($this, $this.val())
});
});
.resizing-input input,
.resizing-input span {
font-size: 12px;
font-family: Sans-serif;
white-space: pre;
padding: 5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div class="resizing-input">
First:
<input type="text" placeholder="placeholder" />
<span style="display:none"></span>
</div>
<br>
Upvotes: 32