angular / material

Material design for AngularJS
https://material.angularjs.org/
MIT License
16.54k stars 3.39k forks source link

textarea: long textarea causes scroll jumping issues #3070

Open asans opened 9 years ago

asans commented 9 years ago

Place a textarea inside a content pane (ie. md-content or md-dialog-content). As one starts to create a very long message that span many rows and causes overflow to occur and scrollbars to show, one will notice that when you type anything, the scrollbar will jump to the top of the overflow content pane. If one tries to type text, the screen will jump down to the text (which is what the browser does automatically) but then the screen is immediately jumped back up to the top of the scrolling content.

The result is that you cannot see what you're typing once you get to a very long text message. Additionally, the scroll jumping causes headaches. Somewhere along the line, I think someone is doing some sort of scroll reset or setting the scrollTop.

You can duplicate this using the Demo on ngmaterial website.

https://material.angularjs.org/latest/#/demo/material.components.input

Simply keep creating new rows and making the overflown content type expand until it begins to jump.

mckenzielong commented 9 years ago

This is happening because of this line in growTextarea():

node.style.height = "auto";

This handles the shrinking (and also expanding, but as far as I can see it is really here for shrinking) of the textarea by setting the height back to auto.

I took a stab at a fix for this today as it was interfering with a part of my project, and perhaps this will work for anyone looking for a temp fix... I have only tested this on webkit. In src/components/input/input.js:

Add the variable minTextAreaHeight to setupTextarea(), we will use this for saving the initial height to limit shrinking.

function setupTextarea() {
  var node = element[0];
  var onChangeTextarea = $mdUtil.debounce(growTextarea, 1);
  var minTextareaHeight;
...

Adjust the scroll event listener to call the same textarea change function. Remove onScroll as it will no longer be needed.

element.on('scroll', onChangeTextarea);

growTextarea becomes:

  function growTextarea() {
    node.scrollTop = 0;
    var height = getHeight();
    if (height) node.style.height = height + 'px';
    }

getHeight becomes:

function getHeight() {
  var line = node.scrollHeight - node.offsetHeight;
  var height = Number(window.getComputedStyle(node)["height"].replace("px", ""));
  var lineHeight = Number(window.getComputedStyle(node)["line-height"].replace("px", ""));
  var newHeight = height + (line >= 0 ? line : lineHeight*-1);

  //cache the initial height that can be adjusted using 'rows'
  if (!minTextareaHeight) {
    minTextareaHeight = height
  }

  if (newHeight <= minTextareaHeight) {
      return minTextareaHeight;
  } else {
      return newHeight;
  }
}

As I mentioned, I am sure this is a better way of doing this.

mckenzielong commented 9 years ago

Also, this is a dupe of #3024

orange-buffalo commented 9 years ago

@mckenzielong, I applied the fix you proposed for my application and textarea behaves fine now. Thank you!

WhiteAbeLincoln commented 9 years ago

This also happens when the textarea is in a tab container. When you don't have md-dynamic-height on, the jumping starts much sooner. When you do have md-dynamic-height applied to the tabs, then the scrollbar doesn't jump until it reaches the bottom of the mdcontent area.

I havent tried @mckenzielong's fix yet, but I'll update if it works for me.

plunker

WhiteAbeLincoln commented 9 years ago

@mckenzielong Your fixed worked for me, even in a tab container. There is still a slight jump when a newline is added (press enter key), but as soon as you start typing, the textarea focuses on the correct line, without jumps. I think that is reasonable, and isn't that annoying.

Another thing is if you select the content of the textarea and press the backspace, the content is deleted, including the newlines, but the textarea stays the same size. When you start typing again the content area shrinks everytime you hit a key, but there wasn't actually anything in the content area... This didn't happen with the old text area, but I don't find it to be too much of an issue.

here is a screenrecording of the issue

This isn't too much of an issue, and the textarea is a ton better after this fix, so I think this fix outweighs the minor new issues.

If this works in all of the use cases you can think of, and hasn't broken anything else, then I suggest you submit a pull request. I'm sure the developers would appreciate it, since they have 700+ open issues right now.

kamarouski commented 9 years ago

:+1: have the same issue in my project as well. Looking forward to the official fix.

DevinHolland commented 9 years ago

Same problem here. @mckenzielong This fix worked, thank you! Hope this fix makes it into the repo soon.

mckenzielong commented 9 years ago

Here is a different (better?) fix based on how iron-autogrow-textarea works. This gets rid of the case when the textarea doesn't collapse based on a large delete (as @WhiteAbeLincoln demonstrated above.) Again, I have only tested this on webkit.

First add the following css:

md-input-container .md-textarea-container {
    display: inline-block;
    position: relative;
    padding:0;
    border-bottom: none;
}

md-input-container .md-textarea-container textarea {
    height: 100%;
    position: absolute;
    width: 100%;
    padding:0;
}

md-input-container .md-textarea-container .md-textarea-mirror {
    padding-top: 2px;
    padding-left: 2px;
    padding-right: 2px;
    width: 100%;
    min-height: 56px;
    visibility: hidden;
    word-wrap: break-word;
}

Next, in input.js, make the following changes: Change the scroll listener and remove onScroll

element.on('scroll', onChangeTextarea);

Adjust setupTextarea()

function setupTextarea() {
  var mirror = angular.element('<div class="md-textarea-mirror"></div>');
  var onChangeTextarea = $mdUtil.debounce(growTextarea, 1);
  var minTextareaHeight;

  element.wrap('<div class="md-textarea-container md-input"></div>');
  element.after(mirror);
  ...

Adjust growTextarea()

function growTextarea() {
  var rows = element.prop('rows');
  //see https://github.com/PolymerElements/iron-autogrow-textarea/blob/master/iron-autogrow-textarea.html
  var tokens = element.val().replace(/&/gm, '&amp;').replace(/"/gm, '&quot;')
    .replace(/'/gm, '&#39;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;').split('\n');
  while (rows > tokens.length) {
    tokens.push('');
  }

  mirror.html(tokens.join('<br>') + '&nbsp;');
}
asans commented 9 years ago

This issue didn't get closed, but I assume this should be addressed and fixed by commit:

https://github.com/angular/material/commit/5fdcf905b4355c0385a02f59d2875b93e7a18ce4

Whether new bugs appear is likely a separate story.

mckenzielong commented 9 years ago

Please reopen. The issue still present in demo, same root cause:

node.style.height = "auto";
asans commented 9 years ago

Reopened.

sagidayan commented 9 years ago

Just a small fix to @mckenzielong getHeight function. so that the text area will collapse to the right size after a massive deletion:

function getHeight() {
        var line = node.scrollHeight - node.offsetHeight;
        var height = Number(window.getComputedStyle(node)["height"].replace("px", ""));
        var lineHeight = Number(window.getComputedStyle(node)["line-height"].replace("px", ""));
        var newHeight = node.value.split('\n').length * lineHeight; //This Will set the new height to the number of lines in the Value of node

        //cache the initial height that can be adjusted using 'rows'
        if (!minTextareaHeight) {
          minTextareaHeight = height;
        }

        if (newHeight <= minTextareaHeight) {
            return minTextareaHeight;
        } else {
            return newHeight;
        }
      }

Hope this helps

EladBezalel commented 8 years ago

@asans it seems to be fixed in HEAD, closing for now. if it's still happening please write here and i will reopen this issue.

Rel #3024 #2902

kiro64 commented 8 years ago

@EladBezalel i still have an issue when <md-input-contaner> contain more than textarea tag, such as md-icon, auto grow and auto collapse don't work with firefox and safari. i'm using v1.0.0-rc6.

EladBezalel commented 8 years ago

Please post a codepen so I can investigate further

kiro64 commented 8 years ago

@EladBezalel Here the codepen , http://codepen.io/kiro64/pen/gPOmxp on my codepen safari is work fine, but still have an issue on firefox i can't find v1.0.0-rc6 on cdn. so, i use 1.0.0-rc5 instead,

EladBezalel commented 8 years ago

@robertmesserle can you take a look at this with Firefox?

shrutiramnarayan commented 8 years ago

Faced the same issue.Found the below solution which is working for me in IE 10 and chrome.

$(".Autoresizetextarea").on("input",function(){
//set the height
$("#textarea").focus();
})
ThomasBurleson commented 8 years ago

This issue is closed as part of our ‘Surge Focus on Material 2' efforts. For details, see our forum posting @ http://bit.ly/1UhZyWs.

eric-borland commented 8 years ago

Is there any official suggestion on how to fix this bug? I didn't try the suggested fixes in this thread, are they still working on the version 1.1.1?

This behavior is really annoying for IE users...

adam-s commented 8 years ago

I'm having a problem with this. Is there a reasonable hack to fix this problem available?

dmopuri commented 7 years ago

This issue still exists in ios mobile devices and we can see this problem in angular material demo with the version 1.1.1. Looking forward to official fix in the main repo!

msrinivasan3 commented 6 years ago

I am still facing the same issue in IE in 1.1.3. Is the bug closed really and if so what is the version of material is it fixed?

Splaktar commented 6 years ago

Reopened as there are many reports of this still being a problem. Need to investigate.

shannahutch commented 5 years ago

Has this bug even been addressed. Or is there a clean way to solve it?

Splaktar commented 5 years ago

@shannahutch no, it hasn't been fixed yet. There are some workarounds mentioned above but no one has submitted a PR to fix this yet.

Splaktar commented 5 years ago

Based on the discussion here, it appears that this was caused by the auto grow enhancements in https://github.com/angular/material/commit/5fdcf905b4355c0385a02f59d2875b93e7a18ce4.

Splaktar commented 5 years ago

Can anyone provide some updated reproduction steps for this issue in 1.1.10 or HEAD?

I just tested on various browsers and did not see any scroll jumping in the Input demo when adding lots of text to a textarea. I had to resize the text area on Firefox and Safari as they appear to keep growing as more text is added unless you resize it to force a scrollbar. Either way, with or without the scrollbar, adding more text isn't causing me any scroll issues on these browsers on macOS:

Is this only an issue now with iOS and/or IE11? Please confirm.

mckenzielong commented 5 years ago

@Splaktar We see this a lot less now, but I am guessing that is because we made the move from BB10 devices to Android. The way chrome seems to handle this is a bit better.

That being said, you can still trigger a jump by pumping a bunch of newlines into the demo field (Chrome 71, Windows 10):

jump-example

Edit: specify windows.

shannahutch commented 5 years ago

We have an elastic textarea. It works as long as the maxLength is small. In a few places maxLength="32000" this is when the issue happens. You can put 32000 in. However, if you save and come back you can delete but when you try to add the characters it jumps and won't add. This only takes place on ios devices.

shannahutch commented 5 years ago

@Splaktar So I got an email recreating this issue from you guess. But is there a solution. This is occurring specifically on iOS devices.

ygyg70 commented 5 years ago

I have a similar issue - but not with material It is an Ionic app - on iOS and MacOS Safari - when there is a long text it jumps to top after space, jumps back to bottom when typing the next word.

ricardosaracino commented 5 years ago

I have a hack for ie11

  public focus() {
    this.textAreaElement.nativeElement.focus();
  }

 <textarea matTextareaAutosize matInput #textAreaElement (keyup)="focus()" (keypress)="focus()"></textarea>
jonBennevall commented 5 years ago

@ricardosaracino Your IE11 hack unfortunately doesn't work for me. :/ We also tried event.stopPropagation() on keydown and keyup whithout success. We had the same issue with scroll jumping in long textareas when clicking inside the textarea in IE11. We solved that problem with event.stopPropagation() on the click event.

riyajk commented 5 years ago

Reopened.

The issue still exists

Splaktar commented 5 years ago

Just FYI, this likely won't be fixed unless someone from the community comes up with a fix and opens a PR for it. I will be happy to help support, verify the fix, and work to get it merged, but I will be focused on P1-P2 issues for the next few months.

saurabhsingh1003 commented 4 years ago

it is still happing with me in angular material when i'm using matTextareaAutosize it causes jumping to the bottom when editing.

Splaktar commented 4 years ago

it is still happening with me in angular material when i'm using matTextareaAutosize it causes jumping to the bottom when editing.

@saurabhsingh1003 This repo is for AngularJS Material. Please submit Angular Material questions here and issues here.

Splaktar commented 4 years ago

I just tried to reproduce this on iOS (iPad OS 14.0) using Safari 14, but I was not able to do so on https://material.angularjs.org/1.2.0/demo/input#basic-usage.

If anyone can provide a reproduction demo and steps for iOS, please let me know.

I'll try to take a look on Windows soon.

Splaktar commented 4 years ago

Here the codepen, http://codepen.io/kiro64/pen/gPOmxp on my codepen safari is work fine, but still have an issue on firefox

I tried to reproduce this on macOS using Firefox 80.0.1 with an updated CodePen but I was not able to reproduce it. I had to fix up the CodePen a bit to get it to render properly since the way that md-input-container was being used wasn't correct. But auto growing was working fine w/o any jumping on Firefox and Chrome.

justusromijn commented 2 years ago

Just want to chip in here, not related to angular material, but a native solution to this scrolling issue with auto-height trigger to do a calculation. What I've done, is before setting the height to auto for the textarea, have another element next to it take over the current height, and when the new height is calculated, apply this also to that other element. This way, the space stays reserved and the page won't notice a height-drop when setting the textarea to auto. You could see this as some sort of memoization pattern for the height :)

Pseudo code below:

<textarea id="my-textarea" />
<div id="my-textarea-stub" />
calculateTextareaHeight() {
const textarea = document.querySelector('#my-textarea');
const textareaStub = document.querySelector('#my-textarea-stub');
// store the reserved height in our stub element before setting "auto"
textareaStub.style.height = textarea.style.height;
textarea.style.height = 'auto';

// some real calculations here...
let someCalculatedHeight = 2000;

// reset the textarea height, and after that, reset the stub height
textarea.style.height = `${someCalculatedHeight}px`;
textareaStub.style.height = textarea.style.height;
codeunifier commented 2 years ago

Seeing this issue as well in Catalina / Safari 14 using an angular directive to manually change the textarea height.