Please see edit for a better wording of the real issue.
I'm creating a text-area that auto-adapts its height to the content, but it breaks when a scrollbar appears on the page (not caused by the textarea itself, there can't even be a scrollbar on the textarea as I have overflow-y: hidden;
on the second example. Javascript detect scrollbar in textarea is not related to my issue).
Below is a snippet that shows what I mean. Expand the lower textarea downwards and observe the upper textarea. When the scrollbar appears on the right side, it shrinks the upper textarea a bit, and if you have the same screen width than me, it creates a new line in the textarea. For the example to work, a word has to be close to the right and wrap when the scrollbar appears and the last line has to be "full" and wrap so that it wraps on a new line.
<textarea style="font-size: 25px;width: 100%;min-height: 100px">
Lorem ipsum dolor sit amet, consetetur aaaaa sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna </textarea>
<br>
<textarea placeholder="Expand me"></textarea>
I want to somehow detect that this happens and use it to compensate by giving it more height.
Below is the full context of what I'm trying to do:
let textareas = document.getElementsByClassName("auto-resize-textarea");
// Loop through textareas and add event listeners as well as other needed css attributes
for (const textarea of textareas) {
// Initially set height as otherwise the textarea is not high enough on load
textarea.style.height = textarea.scrollHeight.toString();
// Hide scrollbar
textarea.style.overflowY = 'hidden';
// Call resize function with "this" context once during initialisation as it's too high otherwise
resizeTextarea.call(textarea);
// Add event listener to resize textarea on input
textarea.addEventListener('input', resizeTextarea, false);
// Also resize textarea on window resize event binding textarea to be "this"
window.addEventListener('resize', resizeTextarea.bind(textarea), false);
}
function resizeTextarea() {
// Textareas have default 2px padding and if not set it returns 0px
let padding = window.getComputedStyle(this).getPropertyValue('padding-bottom');
// getPropertyValue('padding-bottom') returns "px" at the end it needs to be removed to be added to scrollHeight
padding = parseInt(padding.replace('px',''));
this.style.height = "auto";
this.style.height = (this.scrollHeight + padding) + "px";
}
// Source: https://stackoverflow.com/a/25621277/9013718
<textarea style="font-size: 25px;width: 100%;" class="auto-resize-textarea">
Lorem ipsum dolor sit amet, consetetur aaaaa sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna </textarea>
<br>
<textarea placeholder="Expand me"></textarea>
Because I'm not sure if you can reproduce the examples like I want to show them, I made a screen recording:
At first, I show that on normal window resize, the textarea adapts its height to fit the content. Then, when writing and creating a new line, the textarea also grows its height. But if the scrollbar causes the line wrap it doesn't detect it as "window resize" and thus the textarea doesn't grow, and the bottom text disappears below.
What do I have to change in the above JS function for it to account for the extra needed space for line breaks caused by the scrollbar?
Edit - ResizeObserver works but doesn't fix my issue
Thanks a lot @Ouroborus for the suggestion.
I implemented an observer for the textarea, and it does detect the width change when a scrollbar appears; but the scrollHeight
is still not correct.
Here is the code with the observer calling resizeTextarea()
when it observes a change.
let textareas = document.getElementsByClassName("auto-resize-textarea");
// Init observer to call resizeTextarea when the dimensions of the textareas change
let observer = new ResizeObserver(entries => {
for (let entry of entries) {
let element = entry.target;
const cr = entry.contentRect;
console.log(`Size change observed. New width: ${cr.width}px height: ${cr.height}px`);
resizeTextarea.call(element);
}
});
// Loop through textareas and add event listeners as well as other needed css attributes
for (const textarea of textareas) {
// Initially set height as otherwise the textarea is not high enough on load
textarea.style.height = textarea.scrollHeight.toString();
// Hide scrollbar
textarea.style.overflowY = 'hidden';
// Call resize function with "this" context once during initialisation as it's too high otherwise
resizeTextarea.call(textarea);
// Add event listener to resize textarea on input
textarea.addEventListener('input', resizeTextarea, false);
// Also resize textarea on window resize event binding textarea to be "this"
observer.observe(textarea);
}
function resizeTextarea() {
// Textareas have default 2px padding and if not set it returns 0px
let padding = window.getComputedStyle(this).getPropertyValue('padding-bottom');
// getPropertyValue('padding-bottom') returns "px" at the end it needs to be removed to be added to scrollHeight
padding = parseInt(padding.replace('px',''));
// Reset textarea height to (supposedly) have correct scrollHeight afterwards
const scrollHeightBefore = this.scrollHeight;
this.style.height = "auto";
console.log('scrollHeight before height reset:' + scrollHeightBefore + '; after: ' + this.scrollHeight);
// Adapt height of textarea to new scrollHeight and padding
this.style.height = (this.scrollHeight + padding) + "px";
}
// Clear console on load
setTimeout(() => {
console.clear();
}, 800);
// Clear console every 7 sec
window.setInterval(function(){
console.clear();
}, 7000);
// Source: https://stackoverflow.com/a/25621277/9013718
<textarea style="font-size: 25px;width: 100%;" class="auto-resize-textarea">
Lorem ipsum dolor sit amet, consetetur aaaaa sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna </textarea>
<br>
<textarea placeholder="Expand me"></textarea>
When a size change is observed by the ResizeObserver
, the function resizeTextarea()
is correctly called.
I tried illustrating the issue with the snippet above, but basically what I noticed is the following:
The textarea.scrollHeight
does increase and change when the scrollbar appears but for the height calculation to work a height "reset" is needed which is done with height: auto
and after this reset the scrollHeight
(which will become the textarea height) changes again to the same old value NOT taking into account the now new extra line from the wrapping caused by the shrunk space because of the scrollbar.
Here is an example with real numbers from my environment:
Assuming the textarea scrollHeight
is 221 on initial load, after the height reset it may be 214, so the textarea height will be set to 214 (+ padding, but let's forget that for a sec).
Now, when a scrollbar appears (for example by expanding the second textarea) the scrollHeight
becomes 237 before the reset but after the reset it becomes the same 214 as before when it should be a bit more.
My deduction is that it's the height reset height: auto
that does not
not taking into account the extra line and changes the scrollHeight
back to the wrong value (the same as the old one) like this wrapped line doesn't exist.
The question: How can I make the textarea scrollHeight
take into account line wrapping caused by an external scrollbar after height reset; or how to make a height reset on a textarea that takes into account wrapped line caused by a page scrollbar ?
I kind of think it should be working, might it be a bug ? I'd love an explanation of this behaviour.