How to stop window jumping when typing in autoresizing textarea
Asked Answered
R

6

7

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/

Refinement answered 15/8, 2013 at 22:9 Comment(1)
I don't have this same problem, in FF, but do see it in Chrome..Audio
P
14

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

Pappose answered 15/8, 2013 at 22:27 Comment(1)
@losnir, Do you know of a way to do this except in the context of a scrollable div? I have a textarea that's inside a quickview div so it's position is not that of the window but of the div.Canonicate
R
1

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();
Radiotelephone answered 30/3, 2014 at 4:6 Comment(1)
Do you know of a way to do this except in the context of a scrollable div? I have a textarea that's inside a quickview div so it's position is not that of the window but of the div.Canonicate
E
1

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);
    }
};
Elizabethelizabethan answered 18/3, 2019 at 10:52 Comment(2)
Or you can use the accepted answer how I did: https://mcmap.net/q/1409568/-how-to-stop-window-jumping-when-typing-in-autoresizing-textareaNarah
I had to change this to this.parentNode else it kept getting the textarea as the scrollParent and nothing happenedMaurili
R
1

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.

Revolutionary answered 26/3, 2020 at 13:26 Comment(0)
N
1

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>
Narah answered 5/2, 2021 at 21:2 Comment(0)
F
1

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);
}
Foster answered 23/4, 2021 at 16:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.