Textarea Auto Resize

Textarea Auto ResizeOn a current project, I was trying to find a way to auto-resize a textarea according to some content that would be loaded in dynamically via Ajax. I didn’t know the height of the content and the textarea element doesn’t resize naturally like other HTML elements, so I needed to update the height of the element with JavaScript each time the content changed.

It seemed like a simple task. After doing a search to see what types of plugins and scripts were floating around to do this, the examples I found seemed a little overly complex. While most solutions seemed to incorporate some complex math calculations, I thought of a better way.

Using a Hidden Clone Element

A <div> element will naturally stretch to fit the height of its content (assuming no floats or absolutely positioned elements are involved). So to get the height of the textarea, I just need to do the following:

  • Grab the content loaded into the textarea
  • Create an invisible clone div
  • Give the clone the same width and typographical properties as the textarea
  • Place the content into the clone
  • Get the height of the clone
  • Apply the height of the clone to the height of the textarea

The Code

One of the keys to this solution is the CSS. As mentioned, the invisible clone needs to have the same typographical properties as the textarea. Not only does this include stuff line font-size and font-family, but also the white-space and word-wrap properties of the clone need to be set to mimic what happens inside the textarea.

First here’s the CSS for the textarea:

textarea {
    width: 500px;
    min-height: 50px;
    font-family: Arial, sans-serif;
    font-size: 13px;
    color: #444;
    padding: 5px;
}

.noscroll {
    overflow: hidden;
}

Take note that I’ve added a separate class with overflow: hidden, to prevent scrollbars from appearing. Normally this would not be a good thing to add to a textarea element, but because I’ll be resizing it with JavaScript, it’s fine. This class will be added to the textarea with JavaScript, to ensure that if JavaScript is turned off, the textarea will scroll normally.

Here is the CSS I’ll be applying to the hidden clone element:

.hiddendiv {
    display: none;
    white-space: pre-wrap;
    width: 500px;
    min-height: 50px;
    font-family: Arial, sans-serif;
    font-size: 13px;
    padding: 5px;
    word-wrap: break-word;
}

A quick break-down: First, I set it to display: none because I don’t want it visible to the user. I believe this should be fine for screen readers, because I don’t want it read out to them either. If anyone has a better solution for hiding it for assistive devices, let me know.

I’ve also set the white-space property to a value of “pre-wrap”. This ensures that lines will wrap correctly, but everything else gets pre-formatted. I’ve also set the width to be equal to the textarea, and duplicated a few typographical properties. In both examples, I’m giving the clone and the textarea a min-height so it will always start out at a standard, usable height.

Now for the JavaScript (which is using jQuery, sorry):

$(function() {
	var txt = $('#comments'),
	hiddenDiv = $(document.createElement('div')),
	content = null;

	txt.addClass('noscroll');
	hiddenDiv.addClass('hiddendiv');

	$('body').append(hiddenDiv);

	txt.bind('keyup', function() {

	    content = txt.val();
	    content = content.replace(/\n/g, '<br>');
	    hiddenDiv.html(content);

	    txt.css('height', hiddenDiv.height());

	});
});

This code assumes we’re targeting a single textarea element on the page. If you need this to affect more than one element, then just change the first line inside the function that defines the element we’re working with.

I’m dynamically changing the height based on jQuery’s keyup event. You could easily change this to respond to an Ajax request instead, if you happen to be loading the content that way.

Using keyup, however, is a good solution because it’s the most likely reason that you’ll want to auto-resize a textarea — user-entered data.

What About IE6-8?

I almost didn’t write this article, because the code wasn’t working at all in IE6-8. The reason for this had to do with the poor way IE handles grabbing content using innerHTML. So after I had written this simple solution that seemed to work in all the newer browsers, I came across this jQuery plugin. That solution uses the exact same method that I’m using (the cloned element), and it worked (mostly) in IE.

So the line that I borrowed from that example that (mostly) fixed mine in IE was:

// fixes the IE innerHTML problem
content = content.replace(/\n/g, '<br>');

But even after adding this line, there was still an issue: Long, unbroken strings of text wouldn’t affect the height of the textarea (which isn’t a big problem, really). A simple fix was adding word-wrap: break-word to the CSS for the clone element.

Bugs? Problems?

Like the other solutions floating around, this could easily be turned into a plugin. In that case, I’d have to add a little more jQuery so that the characteristics of the clone element aren’t dependent on the CSS. Also, if the width of the textarea is fluid, then you’d have to use jQuery to grab that, and then apply it to the clone.

The solution I linked to in the previous section is just about perfect — you just need to add the word-wrap fix that I mentioned.

For a demo, you can view my JSFiddle using the link below. Just be sure to hit the “run” button before you test it. Let me know if you find any problems with it.

22 Responses

  1. This would make for a fantastic jQuery plugin! Kudos.

    • Yeah, that was one of the ones that I found when initially researching this. The one bonus of James’s is that it actually works on input elements too, for expanding the width as you type.

      But I’m hesitant to recommend a plugin for this solution that’s 275 lines long. Maybe I’m missing something here, but my solution in this example is about 20 lines and if converted to a plugin could probably be around 30-40 max.

      There’s also an option on SitePoint:

      http://www.sitepoint.com/build-auto-expanding-textarea-1/

      Which is about 60 lines, commented — so much more reasonable I think.

      Edit:
      I initially said I couldn’t think of a reason to auto-expand an input but on second thought it would be useful to have an input element start out small then expand as you type (for long email addresses or whatever).

  2. Thank you this trick help me a lot.

  3. I like the idea, clever. Just for the sake of another option, here’s one that is fairly short and library free that works really efficiently I think:

    http://www.alistapart.com/articles/expanding-text-areas-made-elegant/

    • Nice, I didn’t know about that one. Usually the ALA stuff doesn’t go under my radar. Obvious drawback for that one is the lack of support for IE6/7, but certainly a good option since old IE support is becoming less and less of an issue.

  4. Brian Nickel:

    One problem I’ve noticed is that if you press “enter” to expand to a new line, the text area will be slightly scrolled, most likely because “<br>” at the end of a <div> doesn’t do anything. I overcame this with the following:

    hiddenDiv.html(content + ' ');

    If you don’t want the text to scroll briefly every time you reach a new line, you could do the following:

    hiddenDiv.html(content + ' <br> ');

  5. Nice Article. i am learning Web designing, i was just searching On “resizing” i found this article, which is really useful that Auto Resize, it will gonna save more time n Progress in this way! thanks for this very nice explanation n writing worth reading :)

  6. Jon:

    This is seriously awesome! I can’t wait to use it on my blog.

  7. Great post, I enjoyed trying it out.

  8. Realy great article. Thank you, i try it :)

  9. kaeku:

    Hey! I had to fix the same issue at work just last week. I solved it by getting the scroll-height of textarea and expanding it by the difference that was determined by subtracting the actual height from the scroll-height.

    It would look kind of like this:

    
    var el = $(this);
    
    setTimeout(function() {
    
        var height = $(el).height();
        var scrollHeight = $(el).prop('scrollHeight');
    
        $('#log').text('height: ' + height + ' scrollHeight: ' + scrollHeight);
    
        if (scrollHeight > height)
        {
            $(el).height(scrollHeight);
        }
    }, 1);
    

    After that, you just bind the function to the desired events

  10. I’d rather use bind('input', .... instead of keyup as there more ways to enter text than just by keyboard ;)
    Moreover, instead of using div and replacing newlines with <br>, you could use <pre>.

    It would also be a great idea to set opacity of textarea to 0 and then apply some kind of syntax highlighting to the text-containing element (I’m thinking of binding highlighting to blur event).

    My code (from 1st Nov) can be found here: http://pastebin.com/j9LbTQb8

  11. Filip Bech-Larsen:

    You could argue it makes sence to either add textarea{ resize: none; } to your css, or add something that turns the feature off when the user uses the manual resize of pulling the corner of the textarea. Maybe your script could be only to resize bigger, and not smaller? I, for one, like the illlusion of overview therefore often resizing textareas to be really large. If that cancelled after each key-up that would make me very tired :-D But great idea with the cloned element.

  12. Mc Benny:

    A difficulty of your solution in case of several textareas or if you want to turn it to a generic system is the way you prepare your clone: by applying fixed styles. I encountered the problem a few years ago and I solved it by just getting the styles of the targetted textarea involved in presentation and putting them to the cloned element on the fly:

    
    $(tArea)
    .data({
    	'default':	$(this).val()
    	,'w':		$(this).width()
    	,'ttransform':	$(this).css('text-transform')
    	,'lheight':	$(this).css('line-height')
    	,'wspacing':	$(this).css('word-spacing')
    	,'ffamily':	$(this).css('font-family')
    	,'fsize':	$(this).css('font-size')
    	,'fweight':	$(this).css('font-weight')
    	,'lspacing':	$(this).css('letter-spacing')
    	,'wrap':	$(this).css('word-wrap')
    })
    ^t		.after('<div />')
    .next()
    	.css({
    		position:		'absolute'
    		,left:			'-9999em'
    		,width:			$(tArea).data('w')
    		,'text-transform':	$(tArea).data('ttransform')
    		,'line-height':		$(tArea).data('lheight')
    		,'word-spacing':	$(tArea).data('wspacing')
    		,'font-family':		$(tArea).data('ffamily')
    		,'font-size':		$(tArea).data('fsize')
    		,'font-weight':		$(tArea).data('fweight')
    		,'letter-spacing':	$(tArea).data('lspacing')
    		,'word-wrap':	$(tArea).data('wrap')
    	})
    	.text($(tArea).data('default'));
    
    

    By the way, you don’t need any padding on your cloned element.

    My function is about 70 lines of code and also accepts multiple classes to target the textareas, if any is interested.

  13. ckozl:

    Keep it real [simple] in this rap game:

    
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset=utf-8 />
    <title>auto resize textbox</title>
    <style>
      textarea {
        resize:none;
        overflow:hidden;
        width:100%;
        height:100%;
        position:absolute;
        top:0;
        left:0;
      }
      div {
        position:relative;
        width:auto;
        height:auto;
        white-space: pre-wrap;
        word-wrap: break-word;
      }
      div, textarea {
        font-family:arial;
        font-size:1em;
        padding:10px;
      }
    </style>
    </head>
    <body>
      <div><textarea></textarea></div>
      <script>
      (function() {
        var d = document,
            c = d.getElementsByTagName('div')[0],
            t = d.getElementsByTagName('textarea')[0],
            f = d.createTextNode('');
    
        c.appendChild(f);
    
        function updateSize() {
           f.nodeValue = t.value + '\n\n';
        }
    
        t.onkeypress = t.onkeyup = t.onchange = updateSize;
    
        updateSize();
      })();
    </script>
    </body>
    </html>
    

    link to jsbin: http://jsbin.com/adebol

    Simpler, no jQuery, shorter easier javascript, quicker updating, no height calculation, reflow resize via css. You’re welcome ::bows:: -ck

    • Doesn’t work in IE6-8.

      You’re welcome. :)

      • ckozl:

        LIES! works fine in ie8. ::shakes head at you for not telling the truth::

        as for ie6-7 support,
        change

        white-space: pre-wrap;
        to
        white-space: pre;
        using an ie < 7 specific hack…

        like so:

        
        <!--[if lte IE 7]>
        <style>
        div {
            white-space: pre;
          }
        </style>
        <![endif]-->
        

        jsbin: http://jsbin.com/uvebag

        …so i’ll say it again. You’re welcome!
        And due to the invalidity of your prior statements, I expect a written apology… preferably with some mother effin smiley face stickers… ;-)
        -ck

        • That’s strange, because I was looking at it in “edit” mode on JSBin, and it doesn’t work in IE8 there, but it does work in preview mode.

          That is strange, as it should work the same, since it’s just an iframe.

          So, you’re right, sorry for the hasty reply, but I was looking at the code view in Chrome then copied and pasted the URL into IE8. Thanks.

        • I have to admit, after examining this code a little closer, this really is a great solution. The only minuscule drawback is the extra div, but you could easily add that via JavaScript instead, and then the HTML would be clean. But I’m fine with it either way.

          Kudos on this, great thinking — and great that you can take advantage of as much CSS as possible without needing a JS library or much JS at all.

          • ckozl:

            Thanks for the kind words. It’s only a slight evolution of your concept, at its core its still using the “use a DIV to measure text size”. And I agree if I were packaging this as a plugin or in a library the DIV would get added dynamically on the target textarea/input via
            $('textarea#foo').autoResize({ vertical: true, horizontal 400 });
            or something to that effect, and you would obviously use targeted css class names… I just wanted represent the concept as simply as possible. Hats off for doing the leg work on the original concept. -ck

  14. This should become default functionality in browsers. I get sick of 3-line textareas.

Leave a Reply

Comment Rules: Please use a real name or alias. Keywords are not allowed in the "name" field. If you use keywords, your comment will be deleted, or your name will be replaced with the alias from your email address. No foul language, please. Thank you for cooperating.

To Post Code Snippets in Comments: Wrap inline code in <code> tags; wrap blocks of code in <pre> and <code> tags; and make sure you use &lt; and &gt; for HTML, instead of < and >, otherwise your code will not show up properly.