How to autosize a textarea using Prototype?

153,395

Solution 1

Facebook does it, when you write on people's walls, but only resizes vertically.

Horizontal resize strikes me as being a mess, due to word-wrap, long lines, and so on, but vertical resize seems to be pretty safe and nice.

None of the Facebook-using-newbies I know have ever mentioned anything about it or been confused. I'd use this as anecdotal evidence to say 'go ahead, implement it'.

Some JavaScript code to do it, using Prototype (because that's what I'm familiar with):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <script src="http://www.google.com/jsapi"></script>
        <script language="javascript">
            google.load('prototype', '1.6.0.2');
        </script>
    </head>

    <body>
        <textarea id="text-area" rows="1" cols="50"></textarea>

        <script type="text/javascript" language="javascript">
            resizeIt = function() {
              var str = $('text-area').value;
              var cols = $('text-area').cols;

              var linecount = 0;
              $A(str.split("\n")).each( function(l) {
                  linecount += Math.ceil( l.length / cols ); // Take into account long lines
              })
              $('text-area').rows = linecount + 1;
            };

            // You could attach to keyUp, etc. if keydown doesn't work
            Event.observe('text-area', 'keydown', resizeIt );

            resizeIt(); //Initial on load
        </script>
    </body>
</html>

PS: Obviously this JavaScript code is very naive and not well tested, and you probably don't want to use it on textboxes with novels in them, but you get the general idea.

Solution 2

One refinement to some of these answers is to let CSS do more of the work.

The basic route seems to be:

  1. Create a container element to hold the textarea and a hidden div
  2. Using Javascript, keep the textarea’s contents synced with the div’s
  3. Let the browser do the work of calculating the height of that div
  4. Because the browser handles rendering / sizing the hidden div, we avoid explicitly setting the textarea’s height.

document.addEventListener('DOMContentLoaded', () => {
    textArea.addEventListener('change', autosize, false)
    textArea.addEventListener('keydown', autosize, false)
    textArea.addEventListener('keyup', autosize, false)
    autosize()
}, false)

function autosize() {
    // Copy textarea contents to div browser will calculate correct height
    // of copy, which will make overall container taller, which will make
    // textarea taller.
    textCopy.innerHTML = textArea.value.replace(/\n/g, '<br/>')
}
html, body, textarea {
    font-family: sans-serif;
    font-size: 14px;
}

.textarea-container {
    position: relative;
}

.textarea-container > div, .textarea-container > textarea {
    word-wrap: break-word; /* make sure the div and the textarea wrap words in the same way */
    box-sizing: border-box;
    padding: 2px;
    width: 100%;
}

.textarea-container > textarea {
    overflow: hidden;
    position: absolute;
    height: 100%;
}

.textarea-container > div {
    padding-bottom: 1.5em; /* A bit more than one additional line of text. */ 
    visibility: hidden;
}
<div class="textarea-container">
    <textarea id="textArea"></textarea>
    <div id="textCopy"></div>
</div>

Solution 3

Here's another technique for autosizing a textarea.

  • Uses pixel height instead of line height: more accurate handling of line wrap if a proportional font is used.
  • Accepts either ID or element as input
  • Accepts an optional maximum height parameter - useful if you'd rather not let the text area grow beyond a certain size (keep it all on-screen, avoid breaking layout, etc.)
  • Tested on Firefox 3 and Internet Explorer 6

Code: (plain vanilla JavaScript)

function FitToContent(id, maxHeight)
{
   var text = id && id.style ? id : document.getElementById(id);
   if (!text)
      return;

   /* Accounts for rows being deleted, pixel value may need adjusting */
   if (text.clientHeight == text.scrollHeight) {
      text.style.height = "30px";
   }

   var adjustedHeight = text.clientHeight;
   if (!maxHeight || maxHeight > adjustedHeight)
   {
      adjustedHeight = Math.max(text.scrollHeight, adjustedHeight);
      if (maxHeight)
         adjustedHeight = Math.min(maxHeight, adjustedHeight);
      if (adjustedHeight > text.clientHeight)
         text.style.height = adjustedHeight + "px";
   }
}

Demo: (uses jQuery, targets on the textarea I'm typing into right now - if you have Firebug installed, paste both samples into the console and test on this page)

$("#post-text").keyup(function()
{
   FitToContent(this, document.documentElement.clientHeight)
});

Solution 4

Probably the shortest solution:

jQuery(document).ready(function(){
    jQuery("#textArea").on("keydown keyup", function(){
        this.style.height = "1px";
        this.style.height = (this.scrollHeight) + "px"; 
    });
});

This way you don't need any hidden divs or anything like that.

Note: you might have to play with this.style.height = (this.scrollHeight) + "px"; depending on how you style the textarea (line-height, padding and that kind of stuff).

Solution 5

Here's a Prototype version of resizing a text area that is not dependent on the number of columns in the textarea. This is a superior technique because it allows you to control the text area via CSS as well as have variable width textarea. Additionally, this version displays the number of characters remaining. While not requested, it's a pretty useful feature and is easily removed if unwanted.

//inspired by: http://github.com/jaz303/jquery-grab-bag/blob/63d7e445b09698272b2923cb081878fd145b5e3d/javascripts/jquery.autogrow-textarea.js
if (window.Widget == undefined) window.Widget = {}; 

Widget.Textarea = Class.create({
  initialize: function(textarea, options)
  {
    this.textarea = $(textarea);
    this.options = $H({
      'min_height' : 30,
      'max_length' : 400
    }).update(options);

    this.textarea.observe('keyup', this.refresh.bind(this));

    this._shadow = new Element('div').setStyle({
      lineHeight : this.textarea.getStyle('lineHeight'),
      fontSize : this.textarea.getStyle('fontSize'),
      fontFamily : this.textarea.getStyle('fontFamily'),
      position : 'absolute',
      top: '-10000px',
      left: '-10000px',
      width: this.textarea.getWidth() + 'px'
    });
    this.textarea.insert({ after: this._shadow });

    this._remainingCharacters = new Element('p').addClassName('remainingCharacters');
    this.textarea.insert({after: this._remainingCharacters});  
    this.refresh();  
  },

  refresh: function()
  { 
    this._shadow.update($F(this.textarea).replace(/\n/g, '<br/>'));
    this.textarea.setStyle({
      height: Math.max(parseInt(this._shadow.getHeight()) + parseInt(this.textarea.getStyle('lineHeight').replace('px', '')), this.options.get('min_height')) + 'px'
    });

    var remaining = this.options.get('max_length') - $F(this.textarea).length;
    this._remainingCharacters.update(Math.abs(remaining)  + ' characters ' + (remaining > 0 ? 'remaining' : 'over the limit'));
  }
});

Create the widget by calling new Widget.Textarea('element_id'). The default options can be overridden by passing them as an object, e.g. new Widget.Textarea('element_id', { max_length: 600, min_height: 50}). If you want to create it for all textareas on the page, do something like:

Event.observe(window, 'load', function() {
  $$('textarea').each(function(textarea) {
    new Widget.Textarea(textarea);
  });   
});
Share:
153,395
Mike
Author by

Mike

I like stuff.

Updated on March 10, 2020

Comments

  • Mike
    Mike over 4 years

    I'm currently working on an internal sales application for the company I work for, and I've got a form that allows the user to change the delivery address.

    Now I think it would look much nicer, if the textarea I'm using for the main address details would just take up the area of the text in it, and automatically resize if the text was changed.

    Here's a screenshot of it currently.

    ISO Address

    Any ideas?


    @Chris

    A good point, but there are reasons I want it to resize. I want the area it takes up to be the area of the information contained in it. As you can see in the screen shot, if I have a fixed textarea, it takes up a fair wack of vertical space.

    I can reduce the font, but I need address to be large and readable. Now I can reduce the size of the text area, but then I have problems with people who have an address line that takes 3 or 4 (one takes 5) lines. Needing to have the user use a scrollbar is a major no-no.

    I guess I should be a bit more specific. I'm after vertical resizing, and the width doesn't matter as much. The only problem that happens with that, is the ISO number (the large "1") gets pushed under the address when the window width is too small (as you can see on the screenshot).

    It's not about having a gimick; it's about having a text field the user can edit that won't take up unnecessary space, but will show all the text in it.

    Though if someone comes up with another way to approach the problem I'm open to that too.


    I've modified the code a little because it was acting a little odd. I changed it to activate on keyup, because it wouldn't take into consideration the character that was just typed.

    resizeIt = function() {
      var str = $('iso_address').value;
      var cols = $('iso_address').cols;
      var linecount = 0;
    
      $A(str.split("\n")).each(function(l) {
        linecount += 1 + Math.floor(l.length / cols); // Take into account long lines
      })
    
      $('iso_address').rows = linecount;
    };
    
    • Einar Ólafsson
      Einar Ólafsson almost 12 years
      Can you create a demo site where we can see this at work?
    • Gaurav Shah
      Gaurav Shah over 11 years
      this plugin seems good jacklmoore.com/autosize
    • Zach
      Zach over 11 years
      Is there a JQuery verion? How to access cols and rows of a TextArea in JQuery?
    • Ciro Santilli OurBigBook.com
      Ciro Santilli OurBigBook.com almost 10 years
      Almost the same, but with explicit requirement that should become smaller when text is removed: stackoverflow.com/questions/454202/…
  • Ricardo Gomes
    Ricardo Gomes over 14 years
    @SMB - that probably wouldn't be too hard to implement, just add another conditional
  • Brant Bobby
    Brant Bobby over 13 years
    @Jason: When the text in the box doesn't fill it, clientHeight and scrollHeight are equal, so you can't use this method if you want your textarea to shrink as well as grow.
  • Antonio Salazar Cardozo
    Antonio Salazar Cardozo over 13 years
    This is an awesome solution! The only caveat is that browsers that don't support box-sizing (IE<7) won't properly calculate the width and therefore you'll lose some accuracy, but if you're leaving a line's worth of space at the end, you should still be okay. Definitely love it for the simplicity.
  • Gapipro
    Gapipro over 12 years
    To simply make it shrink to, you just need to add this code before line 6: if (text.clientHeight == text.scrollHeight) text.style.height = "20px";
  • 321X
    321X about 12 years
    Works even better with resize:none; in the style for the textarea!
  • Xion
    Xion almost 12 years
    I took liberty in implementing this solution as standalone, simple to use jQuery plugin, with no markup or styling required. xion.io/jQuery.xarea in case someone's could use it :)
  • st-boost
    st-boost almost 12 years
    This assumes the browser breaks at any character, which is incorrect. Try it on <textarea cols='5'>0 12345 12345 0</textarea>. (I wrote a nearly identical implementation and didn't catch this until after a month of using it.) It also fails for blank lines.
  • Matthew Hui
    Matthew Hui almost 12 years
  • topwik
    topwik over 11 years
    setting textCopy to hidden still allows the hidden div to take up space in the markup so as the textCopy contents get bigger, you will eventually get a scrollbar along the entire length of your page...
  • Ross Brasseaux
    Ross Brasseaux almost 11 years
    It'd be nice to see this in a fiddle.
  • cfillol
    cfillol almost 10 years
    The best solution if you don't mind scrollbar blinking.
  • cfillol
    cfillol almost 10 years
    It would be enough with catching only keyup event. Don't you think so?
  • cfillol
    cfillol almost 10 years
    Set textarea overflow property to "hidden" to avoid scrollbar blinking.
  • Eduard Luca
    Eduard Luca almost 10 years
    keyup could be enough, but I'm not sure. I was so happy that I figured it out, that I stopped trying anything else.
  • Jan Miksovsky
    Jan Miksovsky over 9 years
    For people using web components, this solution was incorporated into the component basic-autosize-textarea. There's a demo.
  • unwitting
    unwitting over 9 years
    Another nice tweak to this; var text = $("#textArea").val().replace(/\n/g, '<br/>') + '&nbsp;'; makes sure you don't get jerky behaviour when going from an empty new line at the end of the textarea to a non-empty one, since the hidden div makes sure to wrap to the nbsp.
  • davnicwil
    davnicwil almost 9 years
    @unwitting great call on adding a '&nbsp;' to get rid of that sudden increase in size when you add the first letter to a new line - to me it's broken without this - this should be added to the answer!
  • emeraldhieu
    emeraldhieu over 8 years
    Is there a JQuery version for this?
  • SidOfc
    SidOfc over 8 years
    When I read this, I actually got goosebumps - so simple and elegant, quite rare to see a solution like this while randomly browsing SO. Thanks for the inspiration and may many others decide to scroll down the page!
  • gherson
    gherson about 5 years
    The backslash in $A(str.split("\n")) needs another. (My edit of the above answer didn't survive for some reason.)
  • Balasubramani M
    Balasubramani M over 4 years
    I'm still trying to learn why did you add this.style.height = "1px"; or this.style.height = "auto"; before. I know that, textarea will not resize when we remove the content if we don't add this line. Can someone care to explain?
  • Endless
    Endless almost 4 years
    You should really change textCopy.innerHTML for textCopy.innerText! and use pre instead to handle line break. otherwise you deal with dangerous html
  • S..
    S.. about 3 years
    this is awesome! A perfectly easy way to always use the most out of a textarea