angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.28k stars 6.71k forks source link

[Textarea] Scroll position changes unexpectedly #8992

Open MatthieuBarthel opened 6 years ago

MatthieuBarthel commented 6 years ago

Bug, feature request, or proposal:

Bug

What is the expected behavior?

When filling a textarea, we should see what we are writing :-)

What is the current behavior?

Under some conditions, writing inside a textarea with autoSize directive makes the page scroll position changes (we get scrolled up ? sorry for my english).

What are the steps to reproduce?

To reproduce my issue:

It also happens on iOS 11 when the textarea is inside a parent with scroll overflow (not the case on the stackblitz example, but I can create another one if needed). I cannot reproduce it on Chrome and I haven't tested other browsers.

I believe the issue is related to this line, the parent's height get shorter for an instant which explains we get scrolled up.

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

Latest Angular and Material

xileftenurb commented 6 years ago

Any new or workaround for this issue?

I cannot use very long textarea (for prettyfied JSON, by example) because of this.

the scrolling up happen with firefox for every character, and for chrome when adding a new line. it also happen for both when I programmaticaly change the value of the textarea.

thank you

fynnfeldpausch commented 5 years ago

We have the same issue and this ticket is a blocker for us. Any updates?

alexmarh commented 5 years ago

I'm having a really terrible solution, but it works (CSS3, haven't checked if polyfill works). Just add following line to the css rule of scrollable container: scroll-padding-bottom: 1rem;

kevin-lozoya commented 4 years ago

It look like Firefox puts the focus on the row where it is being written. But in Angular, the textareas not have 'rows' attribute, so only have one row and it puts the focus on the first line.

If we put textarea.setAttribute('rows', Math.floor(height / this._cachedLineHeight)); after https://github.com/angular/components/blob/fcd9bf73cfea05ae20f28120014ad55a604602dd/src/cdk/text-field/autosize.ts#L226

we have solved the bug on Firefox, but on Chrome this calc: https://github.com/angular/components/blob/fcd9bf73cfea05ae20f28120014ad55a604602dd/src/cdk/text-field/autosize.ts#L222

is incorrect, unlike on Firefox. The issue is when have less lines that before, not decreases the height. But when have more lines that before, it's fine, increases the height.

I tried put textarea.removeAttribute('rows'); after https://github.com/angular/components/blob/fcd9bf73cfea05ae20f28120014ad55a604602dd/src/cdk/text-field/autosize.ts#L217

but I have same problem that before: Scroll position change.

I think that we need to do the calc in another way or distinguish in some way between browsers.

What do you think?

mmalerba commented 4 years ago

@kevin-lozoya I would prefer to find a method that works on all browsers, but if that is not possible we can try special-casing certain browsers. If you have a suggestion for a better way to calculate it, please feel free to send a PR and mention me on it. This calculation always seemed kind of hacky, but was the closest I was able to get after looking at it for a while

whein commented 4 years ago

I found that this issue could be avoided by removing the placeholder logic:

https://github.com/angular/components/blob/fcd9bf73cfea05ae20f28120014ad55a604602dd/src/cdk/text-field/autosize.ts#L210 https://github.com/angular/components/blob/fcd9bf73cfea05ae20f28120014ad55a604602dd/src/cdk/text-field/autosize.ts#L218 https://github.com/angular/components/blob/fcd9bf73cfea05ae20f28120014ad55a604602dd/src/cdk/text-field/autosize.ts#L227

AFAIK this is only an option if your placeholder text will always fit on 1 line in the textarea.

xileftenurb commented 4 years ago

It there any update on this issue? It does not seem to have any workaround for now, as all solution proposed need to modify the cdk direcly.

thank you

samthe13th commented 3 years ago

Also having an issue with this

wiedikerli commented 3 years ago

fixed by using autosize

j2L4e commented 2 years ago

Had a similar issue where this happened on creation of the textarea. Leaving this here as it seems related.

The measuring process temporarily adds a hidden clone of the textarea as a sibling. It might interfere with your layout causing the scroll position change. In my case, wrapping every textarea in a div with position: relative fixes/works around the issue.

https://github.com/angular/components/blob/c2a20c4a035ef57bf598fd78bc7284c180b34c78/src/cdk/text-field/autosize.ts#L205

Find your own workaround:

alexandrasavina commented 1 year ago

I faced with similar issue in Chrome. I fixed it with workaround without using cdk: this.textarea.nativeElement.style.marginBottom = '${this.textarea.nativeElement.clientHeight}px'; this.textarea.nativeElement.style.overflow = 'hidden'; this.textarea.nativeElement.style.height = '0px'; this.textarea.nativeElement.style.height = this.textarea.nativeElement.scrollHeight + 'px'; this.textarea.nativeElement.style.marginBottom = '0px';

So I suppose there is need to add the same margin as it is for Firefox:

https://github.com/angular/components/blob/15.0.x/src/cdk/text-field/autosize.ts#L242

fynnfluegge commented 1 year ago

My workaround is to intercept Enter and tailor the content string accordingly:

        textarea.addEventListener('keydown', (event) => {
          // if the key code is 13 (ENTER)
          if (event.keyCode === 13) {
            event.preventDefault(); // prevent usual browser behavour
            var currentPos = textarea.selectionStart;
            var value = textarea.value;
            var newValue =
              value.substr(0, currentPos) + '\n' + value.substr(currentPos);
            textarea.value = newValue;
            textarea.selectionEnd = currentPos + 1;
          }
        });
ameramayreh commented 6 months ago

I have fixed this by temporary set the parent's min-height to its offsetHeight before adding the measuring class:

    const previousParentMinHeight = element.parentElement.style.minHeight;
    element.parentElement.style.minHeight = `${element.parentElement.offsetHeight}px`;
    // Reset the textarea height to auto in order to shrink back to its default size.
    // Also temporarily force overflow:hidden, so scroll bars do not interfere with calculations.
    element.classList.add(measuringClass);
    // The measuring class includes a 2px padding to workaround an issue with Chrome,
    // so we account for that extra space here by subtracting 4 (2px top + 2px bottom).
    const scrollHeight = element.scrollHeight;
    element.classList.remove(measuringClass);
    element.parentElement.style.minHeight = previousParentMinHeight;

It worked perfectly with me. Is there any concerns about this fix?

If not, I can submit a pull request for it.

Thanks.