Why is scrollHeight not the same as lineHeight * lines in a textarea?

Viewed 1970

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?

2 Answers

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.

Related