1

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?

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
cavaunpeu
  • 614
  • 1
  • 6
  • 16
  • [Duplicate](http://stackoverflow.com/a/8100949/3938676)? – Filipe Jan 14 '16 at 20:38
  • This question has already been aswered before: http://stackoverflow.com/questions/17302794/how-to-set-an-input-width-to-match-the-placeholder-text-width – JazzCat Jan 14 '16 at 20:40
  • 1
    http://stackoverflow.com/questions/3392493/adjust-width-of-input-field-to-its-input – RyanL Jan 14 '16 at 20:41
  • @JazzCat this answer does nothing more than expand the given long *placeholder* text, no? – cavaunpeu Jan 14 '16 at 20:42
  • @RyanL the answer give in that thread only works if you text is a fixed size. This seems pretty brittle, no? – cavaunpeu Jan 14 '16 at 20:43
  • Possible duplicate of [Auto-scaling input\[type=text\] to width of value?](http://stackoverflow.com/questions/8100770/auto-scaling-inputtype-text-to-width-of-value) – Stefan Neuenschwander Jan 14 '16 at 20:44
  • I'll continue to try all suggestions posted. Will close this when successful. – cavaunpeu Jan 14 '16 at 20:46
  • @cavaunpeu there are non-accepted answers in that thread that take into consideration different character width using various methods – RyanL Jan 14 '16 at 20:47
  • will report back soon. thanks all. my biggest hesitation is that it's so much code for something I'd intuitively think would be so simple. also, most threads are from 3+ years ago. – cavaunpeu Jan 14 '16 at 21:51

2 Answers2

1

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" />
Rounin
  • 27,134
  • 9
  • 83
  • 108
  • 1
    What about `border-width`? Though you haven't set one, I'm pretty sure that affects the width of the `input` element relative to the `span`. In addition, I think you can listen to `keydown` so there's no delay in responsiveness. – Patrick Roberts Jan 14 '16 at 23:52
  • You're right about `border-width` - thanks for that @Patrick. In fact it should be `border-left-width + padding-left + padding-right + border-right-width`. I'll update the post above to reflect these improvements. On the other point, it needs to be `keyup`, rather than `keydown` or `keypress`, because otherwise, the `inputValue` is 1 character short. – Rounin Jan 15 '16 at 00:00
  • 1
    Oh, then try `input` instead of `keydown`. – Patrick Roberts Jan 15 '16 at 00:17
  • Brilliant! I wasn't even aware of the `input` event. Many thanks for your valuable additions. If there are any points awarded for this answer, can I give half of them to you? – Rounin Jan 15 '16 at 00:20
  • I appreciate the sentiment but I don't think that's possible. However, even with all my suggestions, the width seems to be 1 or 2 pixels too small for the input, so I'm gonna post an answer in a while that tries to address the discrepancy. – Patrick Roberts Jan 15 '16 at 00:21
  • In the script, `startWidth` and `newWidth` are just integers, so anyone who wanted to tweak the script to make the `` element 2 pixels wider could always add `2` to either or both of them. As it is, everything should be pixel perfect. I haven't tested the script in other browsers, but it looks that way in Firefox 43.0.4. – Rounin Jan 15 '16 at 00:29
  • 1
    Running Chrome version 47.0.2526.111, and it's not quite right. – Patrick Roberts Jan 15 '16 at 00:40
0

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.

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153