4

Summary:

The number of lines in a text area, times line height, is not the same as the height of the textarea element, why is that?

Detail

I'm working on a function to resize textarea elements in an AngularJs directive, and the answer I found gave this as the meat of the solution in the link function:

angular.module('auto.resize', []).directive('autoResize', ['$timeout', function($timeout) {
  return {
    restrict: 'A',
    link: function link(scope, elm) {
      const resize = function() {
        elm[0].style.height = elm[0].scrollHeight + "px";
      }
      elm.on("blur keyup change", resize);
      $timeout(resize);
    }
  }
}]);
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>AngularJS </title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.js"></script>
</head>

<body>
  <div>
    <textarea auto-resize></textarea>
  </div>
</body>

This doesn't work as I'd like when you add extra lines, then type on anything but the bottom line it slowly shrinks down by an amount smaller than the line height.

My fix is this:

angular.module('auto.resize', []).directive('autoResize', ['$timeout', function($timeout) {
  return {
    restrict: 'A',
    link: function link(scope, elm) {
      const resize = function() {
        const lines = elm[0].value.split("\n").length;
        const lineHeight = parseInt(getComputedStyle(elm[0]).lineHeight.match(/(\d+)/)[1];
        elm[0].style.height = lines * lineHeight + "px";
      }
      elm.on("blur keyup change", resize);
      $timeout(resize);
    }
  }
}]);
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>AngularJS </title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.js"></script>
</head>

<body>
  <div>
    <textarea auto-resize></textarea>
  </div>
</body>

This corrects itself for not resizing unnecessarily, however, my issue is that lines * lineHeight is not the same as scrollHeight, there seems to be some padding, that I can't divine.

How do I map between the two?

Community
  • 1
  • 1
AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173

2 Answers2

1

The scrollHeight of an element leaves a little extra padding (in addition to the actual padding of the element) to make room for any ascenders or descenders in the text. This will differ depending on the particular font metrics:

console.log($('#a').prop('scrollHeight'))
console.log($('#b').prop('scrollHeight'));
div {
    padding: 0;
    line-height: 10px;
    white-space: pre-wrap; /* simulate textarea handling */
    }
#a {font-family: serif}
#b {font-family: monospace}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="a">one
two
three</div>

<div id="b">one
two
three</div>

The 'shrinking' behavior you describe in the approach used in this question's accepted answer is likely related to the same issue. This could be corrected by using element's innerHeight instead of scrollHeight -- innerHeight does match the line height multiplied by the number of rows (so long as there isn't any text wrapping.)

Daniel Beck
  • 20,653
  • 5
  • 38
  • 53
  • In more clear terms, the shinking is when you add extra newlines, and then trigger the listener (with a keyup event, say) then it starts to recalculate the height as less that the current height, shrinking it until the scroll height matches the actual height. – AncientSwordRage Jul 04 '17 at 07:02
  • I'll likley revert to the answer in the linked question, but I am still curious as to why they aren't the same. The text I'm using to test is very short and won't encounter the issue you've raised with it (line wrapping) – AncientSwordRage Jul 04 '17 at 07:03
  • @Pureferret Apologies, I completely misunderstood the issue! I've rewritten the answer to explain the discrepancy between scrollHeight and lineHeight. – Daniel Beck Jul 04 '17 at 14:22
  • Ah so it's font specific as well? It's a little sad you can't properly derive the correct heights in some way. Thanks for your more detailed explanation! – AncientSwordRage Jul 04 '17 at 14:27
  • 1
    @Pureferret Updated again: TL;DR use innerHeight instead of scrollHeight, that will behave as you expect. (Thanks for asking this, it took a bit of research -- I learned something today! :) – Daniel Beck Jul 04 '17 at 14:28
  • Except `textarea` doens't have an innerHeight! I'll stick to scrollHeight – AncientSwordRage Jul 04 '17 at 14:55
  • 1
    Oh drat, there's always something isn't there :/ – Daniel Beck Jul 04 '17 at 18:24
1

I think the real solution is not "extra padding" or some other mystic stuff, but the line-height being smaller than the actual needed space for the font rendering.

The example from above works just fine when increasing the line-height property:

console.log($('#a').prop('scrollHeight'))
console.log($('#b').prop('scrollHeight'));
div {
    padding: 0;
    line-height: 20px;
    white-space: pre-wrap; /* simulate textarea handling */
    }
#a {font-family: serif}
#b {font-family: monospace}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="a">one
two
three</div>

<div id="b">one
two
three</div>

I know the thread is old, but I just spend a long time solving another problem with the same symptoms. Maybe other Google users will need this in the future.

AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173
Enn Vee
  • 13
  • 3