Implementation of textarea adaptive height solution in Vue

Implementation of textarea adaptive height solution in Vue

Give the solution first. Students who need Vue stack can directly download vue-awesome-textarea

Hidden Problems

Apart from native JS, most of the UI libraries of the framework support the adaptive textarea height function, but one function is generally overlooked, which is the adaptive height echo.

When using these libraries, we can easily type content in the textarea, and it will automatically extend one line when it exceeds the range to ensure the content is highly adaptive. When we submit content and use the same UI to render on other pages, trouble arises. Some UI libraries do not support adaptive echo, which requires us to calculate a base value between line height, number of lines, and even height to achieve echo.

Solution to adaptive height

There are two common solutions. One is to add a ghost DOM in the "remote area" of the page to simulate input line breaks. This DOM may be a div with the editable attribute set to true or an identical textarea.
Taking the input component of element-ui as an example, when we enter a value in the component, the resizeTextarea method will be called

resizeTextarea() {
 if (this.$isServer) return;
 const { autosize, type } = this;
 if (type !== 'textarea') return;
 if (!autosize) {
  this.textareaCalcStyle = {
   minHeight: calcTextareaHeight(this.$refs.textarea).minHeight
  };
  return;
 }
 const minRows = autosize.minRows;
 const maxRows = autosize.maxRows;

 this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);
}

When autosize is set to true, the textarea is set to adaptive height. At this time, the height of the textarea will be calculated in real time through the calcTextareaHeight method.

 export default function calcTextareaHeight(
 targetElement,
 minRows = 1,
 maxRows = null
) {
 if (!hiddenTextarea) {
  hiddenTextarea = document.createElement('textarea');
  document.body.appendChild(hiddenTextarea);
 }

 let {
  paddingSize,
  borderSize,
  boxSizing,
  contextStyle
 } = calculateNodeStyling(targetElement);

 hiddenTextarea.setAttribute('style', `${contextStyle};${HIDDEN_STYLE}`);
 hiddenTextarea.value = targetElement.value || targetElement.placeholder || '';

 let height = hiddenTextarea.scrollHeight;
 const result = {};

 if (boxSizing === 'border-box') {
  height = height + borderSize;
 } else if (boxSizing === 'content-box') {
  height = height - paddingSize;
 }

 hiddenTextarea.value = '';
 let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize;

 if (minRows !== null) {
  let minHeight = singleRowHeight * minRows;
  if (boxSizing === 'border-box') {
   minHeight = minHeight + paddingSize + borderSize;
  }
  height = Math.max(minHeight, height);
  result.minHeight = `${minHeight}px`;
 }
 if (maxRows !== null) {
  let maxHeight = singleRowHeight * maxRows;
  if (boxSizing === 'border-box') {
   maxHeight = maxHeight + paddingSize + borderSize;
  }
  height = Math.min(maxHeight, height);
 }
 result.height = `${ height }px`;
 hiddenTextarea.parentNode && hiddenTextarea.parentNode.removeChild(hiddenTextarea);
 hiddenTextarea = null;
 return result;
};

We can see

 if (!hiddenTextarea) {
  hiddenTextarea = document.createElement('textarea');
  document.body.appendChild(hiddenTextarea);
}

Element-ui creates a textarea DOM and copies the style of the real textarea to the hiddenTextarea through the calculateNodeStyling method (the overflow is not synchronized, the real textarea is hidden). Then monitor the input value of textarea and synchronize it to hiddenTextarea. At the same time, synchronize the scrollHeight of hiddenTextarea to the height of textarea, and finally destroy the dom.

Regarding style synchronization, element uses the two APIs getComputedStyle and getPropertyValue. Of course, if you wrap it yourself, you can also use the CSS preprocessor mixin.

The second solution is similar to the first solution, but no additional DOM is created. Take the vue-awesome-textarea at the beginning as an example:

 init() {
  this.initAutoResize()
},
initAutoResize () {
  this.autoResize && this.$nextTick(this.calcResize)
}

When the page is mounted or the content changes and autoResize is turned on, the this.calcResize method is executed.

 calcResize() {
 this.resetHeight()
 this.calcTextareaH()
},

resetHeight() {
 this.height = 'auto'
},

calcTextareaH() {
 let contentHeight = this.calcContentHeight()
 this.height = this.calcHeightChange(contentHeight) + 'px'
 if (this.needUpdateRows(contentHeight)) {
  this.updateRows(contentHeight)
 }
 this.oldContentHeight = contentHeight
},

calcContentHeight () {
 const { paddingSize } = this.calcNodeStyle(this.$el)
 return this.$el.scrollHeight - paddingSize
},

resetHeight() is used to initialize the height of the textarea, the default is auto. The calcTextareaH() method is used to calculate the height of the content area (the scrollHeight of the textarea minus the height of the padding), and synchronize the calculated height to the height of the textarea in real time:
this.height = this.calcHeightChange(contentHeight) + 'px'

Compared with Solution 1, this solution adopts the same idea (dynamically modifying the height), but reduces the additional DOM creation and destruction process.
In addition, vue-awesome-textarea also supports callback of the number of lines during the adaptive process, which can better support data echo. The implementation method is also very simple:

 computed: {
 ...
 oneRowsHeight() {
  return this.calcContentHeight() / Number(this.rows) || 0
 }
 ...
}

In computed we calculate the height of a single line, and when executing the this.calcTextareaH() method we record the content height:

 this.oldContentHeight = contentHeight

Next, we will check whether there is an added row operation. If it is added, the new content height will be different from the old content height:

 needUpdateRows(newContentHeight) {
  return this.oldContentHeight !== newContentHeight
},

At this point we will emit the latest row height outside the component:

 updateRows(contentHeight) {
  this.$emit('getRows', Math.round(contentHeight / this.oneRowsHeight))
}

This is the end of this article about the implementation of textarea adaptive height solution in Vue. For more relevant Vue textarea adaptive content, please search 123WORDPRESS.COM's previous articles or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Detailed explanation of the idea of ​​Vue to implement fixed number of input lines and add underline style for textarea
  • Vue implements dynamic display of the remaining words in textarea

<<:  Text pop-up effects implemented with CSS3

>>:  Detailed explanation of some settings for Table adaptation and overflow

Recommend

HTML web page image tag

Insert image tag <IMG> The colorful web page...

Solve the problem of running jupyter notebook on the server

Table of contents The server runs jupyter noteboo...

Let's learn about the MySQL storage engine

Table of contents Preface 1. MySQL main storage e...

XHTML tags should be used properly

<br />In previous tutorials of 123WORDPRESS....

Implementation of webpack code fragmentation

Table of contents background CommonsChunkPlugin s...

Detailed analysis of MySQL 8.0 memory consumption

Table of contents 1. innodb_buffer_pool_size 2. i...

jQuery implements all selection and reverse selection operation case

This article shares the specific code of jQuery t...

Quickly learn MySQL basics

Table of contents Understanding SQL Understanding...

Design Theory: Ten Tips for Content Presentation

<br /> Focusing on the three aspects of text...

What codes should I master when learning web page design?

This article introduces in detail some of the tech...

An Uncommon Error and Solution for SQL Server Full Backup

1. Error details Once when manually performing a ...