f

unk rock


Resizing TextAreas

July 28th, 2008

Yesterday I was working on making a textarea element automatically resize itself to fit its content without a scroll bar. I’ve seen this done before, so it isn’t something strictly new, but I did notice that a lot of people are doing hacks with rows/columns, so I thought I’d share my own approach.

In my case, I wanted a fixed minimum height, and then I wanted the box to grow as the user typed additional text. Assuming you have a reference to your textarea, and a MIN_HEIGHT variable defined somewhere, the javascript is actually very simple:

textArea.style.height = MIN_HEIGHT + "px";
textArea.style.height = Math.max(MIN_HEIGHT, textArea.scrollHeight) + "px";


We need to do the first set because otherwise Firefox and Safari will not shrink if the user deletes text. I should note that the textarea was styled with a fixed width in pixels, which shouldn’t be strictly necessary.

The real reason I wanted this auto-expanding and contracting view was actually because I wanted to use it in my own custom scroller. For the scroller to work properly, it needs to know the size of whatever its scrolling inside of it. The other problem with putting a textarea into my custom scroller was knowing where to scroll too. Since textarea’s have their own caret, and since most browsers will scroll the textarea to follow the caret no matter what you set overflow to, I needed a way to know where exactly the caret was in the textarea.

Both Firefox and Safari have the selectionStart and selectionEnd properties on textarea elements. When there’s no selection, these two numbers are the same; they tell you the character index of the caret. Strictly speaking, knowing the index of the caret isn’t enough; I need to know the vertical offset. There’s no easy way to do this, so I reused one of the hack’s I’ve been playing around with lately.

What I did was create a duplicate of the textarea’s text content inside another element (a div in our case). We make sure to style the textarea and div the same so that the rendered text would look exactly the same, except one would be editable and the other wouldn’t. The trick is, in our new element, I surround each character with it’s own span. Then, I can use the caret index we got before as an index into the childNode array of our div element to get a reference to the specific character we’re dealing with. Here’s a relatively simple implementation of what I’m talking about:

var start = textArea.selectionStart,
    end = textArea.selectionEnd,
    imposter = document.createElement('div'),
    referenceSpan = document.createElement('span'),
    stringValue = textArea.value;

//You may need to copy more than just these two styles
imposter.style.height = textArea.style.height;
imposter.style.width  = getComputedStyle(textArea, "").getPropertyValue('width');

for(var i=0; i {
    referenceSpan.innerHTML = stringValue.charAt(i).replace("\n", "
"
);
    imposter.appendChild(referenceSpan.cloneNode(true));
}

document.body.appendChild(imposter);

var caretOffsetTop = imposter.childNodes[start - 1].offsetTop - imposter.offsetTop,
    caretHeight = imposter.childNodes[start-1].offsetHeight;

document.body.removeChild(imposter);


At the end of that block we have the vertical position of the caret in the textarea (or at least an incredibly good approximation). There are a few things I’ve glossed over; the most obvious is that if you call this on every keypress you’ve turned an O(1) operation into an O(n) operation.

The enterprising individual will want to cache and and keep track of the state of the textarea rather than build from scratch, but I can tell you from personal experience that it’s a real pain, and may not be worth the effort. For most uses, and in pretty much any version of Firefox or Safari, the above code will work well enough. The other thing to watch out for is that many browsers won’t report the size/position of whitespace characters, so you’ll need to scan for the nearest non-whitespace character.

Right now this doesn’t work in Internet Explorer. IE uses a different mechanism — document.selection.createTextRange() — to return information about the position of the caret. Unfortunately, the character index is not one of the properties of the TextRange object. The object does have an offsetTop property, but it seems to return the offset into the whole window, and I’m having difficulty keeping that in sync with the scrolling textarea. If anybody has any feedback on that front, or on the hack in general, I’d like to hear it.

Ross at 4:06 pm | Posted in Web | No Comments