Vue awesome-Textarea can be downloaded directly if you need Vue stack

Hidden problems

Aside from native JS, most of the framework’s UI libraries support adaptive Textarea height functionality, but one feature that is generally ignored is adaptive height echo.

When using these libraries, it is easy to type in a Textarea, which automatically extends a line out of range, ensuring that the content is highly adaptive. The trouble comes when we submit content and use the same UI to render on other pages. Some UI libraries do not support adaptive echo, which requires us to calculate a base value between the line height, the number of lines, and even the height to implement the echo.

A 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 entering a newline. The DOM can be a div with the True editable attribute or a textarea that is identical.

Taking the input component of Element-UI as an example, the resizeTextarea method is called when we enter values within the component

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); }Copy the code

When autosize is set to true the textarea is set to adaptive height. The height of the textarea is calculated in real time by calcTextareaHeight.

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; };Copy the code

We can see that

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

The element- UI creates a DOM of textarea and copies the style of the real Textarea to hiddenTextarea via the calculateNodeStyling method (overflow is out of sync, the real Textarea is hidden). The input value of the Textarea is then listened for and synchronized to the hiddenTextarea. At the same time, synchronize scrollHeight of hiddenTextarea to textarea height, and finally destroy dom.

For style synchronization, Element uses the getComputedStyle and getPropertyValue apis. Of course, you can also use MIXins with CSS preprocessors if you wrap them yourself.

The second scenario is similar to the first, but does not create additional DOM. Example of vue-awesome-textarea at the beginning:

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

Copy the code

When a page mounted or content changes and autoResize is enabled, execute this.calcResize.

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 },Copy the code

ResetHeight () is used to initialize the height of the textarea, which defaults to auto. The calcTextareaH() method calculates the height of the content area (the scrollHeight of the textarea minus the padding height) and synchronizes the calculated height to the height of the textarea in real time:

this.height = this.calcHeightChange(contentHeight) + 'px'
Copy the code

This scheme takes the same approach as scheme 1 (dynamically changing the height), but reduces the extra DOM creation and destruction.

In addition, vuE-aw-textarea also supports the number of lines to be called back 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
  }
  ...
}

Copy the code

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

this.oldContentHeight = contentHeight
Copy the code

We then check to see if there is an add line operation, which will cause the new content height to be different from the old content height:

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

At this point we emit the latest line high to the component:

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