cavaunpeu
cavaunpeu

Reputation: 644

Dynamically expand <input> element with text

I feel like this should be easy. I've been searching around / trying things out for at least a week, and still to no avail.

I'd like an <input> element with placeholder text. I'd like the element to only be as wide as the placeholder text. When you click into the <input>, I'd like the width to remain the same. As you type, if your input text exceeds the width of the original placeholder text (and therefore the <input> element itself), I'd like the <input> to expand to accommodate the text.

Thoughts, SO?

Upvotes: 1

Views: 245

Answers (2)

Patrick Roberts
Patrick Roberts

Reputation: 51876

I added some minor improvements to @Rounin's answer to make my own attempt:

  • Parsing consecutive spaces in HTML compresses to one space unless they're non-breaking, so I mapped to &nbsp. In addition, I mapped < to &lt; to escape HTML. The reason I did not use textContent instead of innerHTML was because there's no way to inject non-breaking spaces into textContent.
  • The hidden span is no longer part of the DOM when it is not needed, so it doesn't trigger the parent element to scroll when it's too wide.
  • the pixel values for width are actually floats, not integers, so using parseFloat instead of parseInt makes the <input/> width more precise.
  • I trimmed down some of the code to reduce verbosity while trying to maintain readability.

/* Set up */
var regExp = /[ <]/g;

function replacer(c) {
  return {
    ' ': '&nbsp;',
    '<': '&lt;'
  }[c];
}

/* Grab various elements */
var body = document.querySelector('body');
var input = document.querySelector('input');
var placeholder = input.getAttribute('placeholder');

/* Create hiddenSpan */
var hiddenSpan = document.createElement('span');
hiddenSpan.innerHTML = placeholder.replace(regExp, replacer);

/* Style hiddenSpan */
var inputStyles = getComputedStyle(input);

['font', 'padding', 'border', 'display'].forEach(function(prop) {
  hiddenSpan.style[prop] = inputStyles.getPropertyValue(prop);
});

hiddenSpan.style.visibility = 'hidden';
hiddenSpan.style.pointerEvents = 'none';

var hiddenSpanStyles = getComputedStyle(hiddenSpan);

/* Initialise <input> width */
body.appendChild(hiddenSpan);

var startWidth = parseFloat(hiddenSpanStyles.getPropertyValue('width'));

body.removeChild(hiddenSpan);

function reviewWidth() {
  var inputValue = input.value;

  /* Update text content of hiddenSpan */
  hiddenSpan.innerHTML = inputValue.replace(regExp, replacer);

  body.appendChild(hiddenSpan);

  /* Update <input> width */
  var newWidth = parseFloat(hiddenSpanStyles.getPropertyValue('width'));

  body.removeChild(hiddenSpan);

  if (newWidth > startWidth) {
    input.style.width = newWidth + 'px';
  } else {
    input.style.width = startWidth + 'px';
  }
}

reviewWidth();

/* Add Event Listener to <input> to trigger reviewWidth() function */
input.addEventListener('input', reviewWidth, false);
input {
  font-family: arial, sans-serif;
  font-size: 0.8em;
  padding: 2px;
}
<input type="text" placeholder="Example Placeholder" />

Feel free to leave comments if you have questions or suggestions to improve this answer.

Upvotes: 0

Rounin
Rounin

Reputation: 29463

Here is one attempt (in vanilla javascript) at a working solution which takes into account different font sizes, different character widths etc.:

function reviewWidth(startWidth) {

/* Grab various elements */
var hiddenSpan = document.getElementsByClassName('hidden')[0];
var inputValue = document.getElementsByTagName('input')[0].value;

/* Update text content of hiddenSpan */
hiddenSpan.innerHTML = inputValue;

/* Update <input> width */
var hiddenSpanStyles = getComputedStyle(hiddenSpan);
var newWidth = parseInt(hiddenSpanStyles.getPropertyValue('width'));

if (newWidth > startWidth) {
    input.style.width = newWidth + 'px';
}

else {
    input.style.width = startWidth + 'px';	
}

}

/* Grab various elements */
var body = document.getElementsByTagName('body')[0];
var input = document.getElementsByTagName('input')[0];
var placeholder = input.getAttribute('placeholder');

/* Create hiddenSpan */
var hiddenSpan = document.createElement('span');
var placeholderText = document.createTextNode(placeholder);
hiddenSpan.appendChild(placeholderText);

/* Style hiddenSpan */
var inputStyles = getComputedStyle(input);
hiddenSpan.style.fontFamily = inputStyles.getPropertyValue('font-family');
hiddenSpan.style.fontSize = inputStyles.getPropertyValue('font-size');
hiddenSpan.style.borderLeftWidth = inputStyles.getPropertyValue('border-left-width');
hiddenSpan.style.paddingLeft = inputStyles.getPropertyValue('padding-left');
hiddenSpan.style.paddingRight = inputStyles.getPropertyValue('padding-right');
hiddenSpan.style.borderRightWidth = inputStyles.getPropertyValue('border-right-width');
hiddenSpan.style.display = 'inline-block';
hiddenSpan.style.opacity = '0';
hiddenSpan.classList.add('hidden');

/* Add hiddenSpan to document body */
body.appendChild(hiddenSpan);

/* Initialise <input> width */
var hiddenSpanStyles = getComputedStyle(hiddenSpan);
var startWidth = parseInt(hiddenSpanStyles.getPropertyValue('width'));
input.style.width = startWidth + 'px';

/* Run reviewWidth() function once */
if (input.value != '') {
    reviewWidth(startWidth);
}


/* Add Event Listener to <input> to trigger reviewWidth() function */
input.addEventListener('input',function(){reviewWidth(startWidth);},false);
input {
font-family: arial, sans-serif;
font-size: 0.8em;
padding: 2px;
}
<input type="text" placeholder="Example Placeholder" />

Upvotes: 1

Related Questions