Question

I am using the accepted answer to this question to build a textarea that expands vertically as text overflows:

<!DOCTYPE html>
<html>
<head>
<title>autoresizing textarea</title>
<style type="text/css">
textarea {
 border: 0 none white;
 overflow: hidden;
 padding: 0;
 outline: none;
 background-color: #D0D0D0;
 resize: none;
}
</style>
<script type="text/javascript">
var observe;
if (window.attachEvent) {
 observe = function (element, event, handler) {
  element.attachEvent('on'+event, handler);
 };
}
else {
 observe = function (element, event, handler) {
  element.addEventListener(event, handler, false);
 };
}
function init () {
 var text = document.getElementById('text');
 function resize () {
  text.style.height = 'auto';
  text.style.height = text.scrollHeight+'px';
 }
 /* 0-timeout to get the already changed text */
 function delayedResize () {
  window.setTimeout(resize, 0);
 }
 observe(text, 'change',  resize);
 observe(text, 'cut',     delayedResize);
 observe(text, 'paste',   delayedResize);
 observe(text, 'drop',    delayedResize);
 observe(text, 'keydown', delayedResize);

 text.focus();
 text.select();
 resize();
}
</script>
</head>
<body onload="init();">
<textarea rows="1" style="height:1em;" id="text"></textarea>
</body>
</html>

It works well until the size of the textarea grows longer than the browser window. At that point the top of the window jumps to the top of the textarea every time a key is pressed. Can you help me understand why and how to fix it?

An ideal fix would be to keep the page from moving at all. But if it's easier to keep the bottom of the page tied to the bottom of the textarea, that would work too.

I am having this issue in Firefox 21.0 and Chrome 28.0: http://jsfiddle.net/CbqFv/

Was it helpful?

Solution

Save the scrollLeft, scrollTop values, and then restore them after resizing the textarea:

function resize () {
   var scrollLeft = window.pageXOffset ||
   (document.documentElement || document.body.parentNode || document.body).scrollLeft;

   var scrollTop  = window.pageYOffset ||
   (document.documentElement || document.body.parentNode || document.body).scrollTop;

   text.style.height = "auto";
   text.style.height = text.scrollHeight + 'px';

   window.scrollTo(scrollLeft, scrollTop);
}

JSFiddle: http://jsfiddle.net/losnir/nnkeH/1

OTHER TIPS

you should scroll the page to: scroll bar position + (textarea height after resize - textarea height before resize)

here is the code:

    function resize () {
      var scrollLeft = window.pageXOffset ||
      (document.documentElement || document.body.parentNode || document.body).scrollLeft;

      var scrollTop  = window.pageYOffset ||
      (document.documentElement || document.body.parentNode || document.body).scrollTop;

      var prevHeight = text.style.height.slice(0, -2);
      text.style.height = "auto";
      var nextHeight = text.scrollHeight;
      text.style.height = text.scrollHeight + 'px';
      window.scrollTo(scrollLeft, scrollTop + (nextHeight - prevHeight));
    }

OR you can do this using jquery (based on this answer):

    $('body').on('keyup', 'textarea', function (e) {
      var scrollTop  = $(document).scrollTop();
      var prevHeight = $(this).height();
      $(this).css('height', 'auto');
      var nextHeight = this.scrollHeight;
      $(this).height(nextHeight);
      $(document).scrollTop(scrollTop + (nextHeight - prevHeight));
    });
    $( 'textarea' ).keyup();

A way to do the accepted answer when the textarea is in a scrollable div:

function getScrollParent(node) {
    if (node == null) {
        return null;
    }

    if (node.scrollHeight > node.clientHeight) {
        return node;
    } else {
        return getScrollParent(node.parentNode);
    }
}

function resize(){
    // 'this' is the textarea
    const scrollParent = getScrollParent(this);
    const scrollTop = scrollParent ? scrollParent.scrollTop : null;
    const scrollLeft = scrollParent ? scrollParent.scrollLeft : null;

    this.style.height = "auto";
    this.style.height = this.scrollHeight + "px";

    if (scrollParent) {
        scrollParent.scrollTo(scrollLeft, scrollTop);
    }
};

My response is based on the fiddle in the accepted answer for: height of textarea increases when value increased but does not reduce when value is decreased.

This works on page load and when the text is altered in any way (cut, paste, typing, etc.)

$('textarea').on('keyup keydown', function () {
var $this = $(this);
var initialHeight = $this.height();

$this
    .height(initialHeight - 40)
    .height($this[0].scrollHeight + 20);}).trigger('keyup');

The reason I grab the initial height is so that the textarea doesn't have to jump to 1 or any other static number to trigger a value for scrollHeight. (it's my understanding that is what causes the screen to change scrolling because the textbox is set to that static height (usually 1) and then expands) The reason that I add 20 to scrollHeight is because sometimes scrollHeight is off by 1 or 2px and the text looks bunched up at the bottom. So to give it more space I personally add 20.

Found this to be the best solution so far, combined code from Adam Beres-Deak & @losnir and came up with the result below.

By far the most useful out of the bunch:

if(window.attachEvent){
    observe = function(element, event, handler){ element.attachEvent('on'+event, handler); };
}else{
    observe = function(element, event, handler){ element.addEventListener(event, handler, false); };
}
function init(){
    var textAreas = [].slice.call(document.querySelectorAll('textarea[data-adaptheight]'));
    textAreas.forEach(function(el){
        function resize(){
            var scrollLeft = window.pageXOffset ||
                (document.documentElement || document.body.parentNode || document.body).scrollLeft;
            var scrollTop = window.pageYOffset ||
                (document.documentElement || document.body.parentNode || document.body).scrollTop;
            el.style.resize = "none";
            el.style.overflow = 'hidden';
            el.style.boxSizing = el.style.mozBoxSizing = 'border-box';
            el.style.height = "auto";
            el.style.height = el.scrollHeight + 'px';
            window.scrollTo(scrollLeft, scrollTop);
        }
        observe(el, 'input', resize);
        resize();
    });
}
init();
<textarea data-adaptheight style="padding: 7px;width: 100%;display: block;" rows="1" cols="40" placeholder="Your input">
</textarea>

Re losnir's answer, in Firefox Desktop (88) it seems that the window.pageXOffset returns a non-integer value but the window.scrollTo() rounds this to an integer value. So if the initial window.pageXOffset value has a decimal part 0.5 or more, then at each change of text, the textarea box shifts down by a small amount. This may happen is other browsers. The solution is to declare the scroll variables as rounded, then while there is a small shift at the first change of text, after that all the values will be integers anyway and no further shifting will occur. This gives:

function resize () {
    var scrollLeft = Math.round(window.pageXOffset ||
                (document.documentElement || document.body.parentNode || document.body).scrollLeft);
   var scrollTop  = Math.round(window.pageYOffset ||
                (document.documentElement || document.body.parentNode || document.body).scrollTop);

   text.style.height = "auto";
   text.style.height = text.scrollHeight + 'px';

   window.scrollTo(scrollLeft, scrollTop);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top