经过建立 textarea 标签,而且指定其 rows 和 cols 属性,就能够建立一个多行文本输入框。html
可是当输入的内容超过指定的 rows 以后,就会出现滚动条,若是用户想要查看所有内容,那就必须来回的拖动滚动条。并且这个滚动条只有在用户滚动的时候才会出现,在一些状况下,用户可能并不知道该区域有更多的内容。前端
一般状况下,为了给用户一个良好的体验,须要让这个多行文本输入框的高度自适应,从而避免滚动条带来的问题。git
实现高度自适应的文本输入框的思路很简单:监听输入相关的事件,获取到元素的内容高度,修改 textarea 的固定高度。github
其中涉及不少基础的知识,也就是咱们常说的细节问题处理:web
scrollHeight 这个只读属性是一个元素内容高度的度量,包括因为溢出的视图中不可见的内容。浏览器
scrollHeight 包含元素的 padding,可是不包含元素的 border 和 margin 。当元素中不存在溢出内容,则 scrollHeight 与 clientHeight 是相同的。函数
接下来只要将获取到的 scrollHeight 属性值赋给元素样式中的 height 属性,是否是就能够动态的更改高度呢?固然,事情并无那么简单,这里又要引出另外一个基础知识点。优化
CSS 中的盒模型基本上是常考的一个知识点,CSS 中能够经过设置 box-sizing 属性值,从而更改盒模型高度和宽度的计算,下面以高度为例:ui
因而可知,为元素设置样式中的 height 属性时,须要弄清楚元素的 box-sizing 、 padding 以及 border。this
对于前端新手来讲,要获取到元素样式的 box-sizing 属性值,可能第一时间会想到:
document.getElementById('demo').style.boxSizing
复制代码
可是大部分状况下,该属性获取的是空值,由于它只可以获取行内样式,若是 style 属性中并无设置 boxSizing 属性值,那天然就是空值。
在 CSS 中,开发者能够经过不少方式去设置元素的样式,而且它们的优先级各不相同,那么就须要一个 API 来肯定元素最终的样式,而 Window.getComputedStyle() 方法正是所以而生。
Window.getComputedStyle() 方法返回一个实时的 CSSStyleDeclaration 对象,经过调用其 getPropertyValue() 方法,获取相应的属性值:
const style = window.getComputedStyle(el)
style.getPropertyValue('box-sizing')
复制代码
有了上述三个知识点的补充,接下来就是代码实现:
function AutoSize (el) {
if (!(this instanceof AutoSize)) {
return new AutoSize(el)
}
if (!el) {
throw new Error('element can not be empty')
}
if (typeof el === 'string') {
el = document.querySelector(el)
}
this.el = el
const attrs = ['box-sizing', 'padding-top', 'padding-bottom', 'border-top', 'border-bottom']
// 初始化信息
this.heightOffset = 0
const style = window.getComputedStyle(el)
const [boxSizing, paddingTop, paddingBottom, borderTop, borderBottom] = attrs.map(item => style.getPropertyValue(item))
if (boxSizing === 'content-box') {
this.heightOffset = -(parseFloat(paddingTop)) - parseFloat(paddingBottom)
} else {
this.heightOffset = parseFloat(borderTop) + parseFloat(borderBottom)
}
this.initEvent()
}
AutoSize.prototype = {
initEvent () {
this.listener = this.handleAction.bind(this)
this.el.addEventListener('input', this.listener, false)
},
destroy () {
this.el.removeEventListener('input', this.listener, false)
this.listener = null
},
handleAction (e) {
const event = e || window.event
const target = event.target || event.srcElement
target.style.height = ''
target.style.height = target.scrollHeight + this.heightOffset + 'px'
}
}
复制代码
!
对于 input 这样高频度触发的事件,通常须要采用函数节流或者函数防抖的方式进行优化,这里就留给读者折腾吧。
HTML 中还有一个很特别的属性 -- contenteditable,该属性能够规定当前元素是否可编辑(下文统称这样的元素为可编辑元素),该属性的取值有如下几种:
当用户向可编辑元素中输入内容时,浏览器会生成对应的 DOM 元素,因此可编辑元素能够作富文本编辑功能,例如:medium-editor。
可是因为各个浏览器对于标签的生成规则不一样,兼容性方面的处理是很大的难题。
如今回到实现高度自适应的多行文本输入框的需求上来,考虑到用户可能输入或者粘贴富文本内容,这里须要将 contenteditable 属性设置为 plaintext-only :
<div contenteditable="plaintext-only" class="demo" id="js-div" data-placeholder="这是占位文字"></div>
复制代码
如今这个 div 标签变成了一个高度自适应的文本输入框,是否是很神奇!不过不要高兴的太早,还有须要考虑一些事情:
对于一个正儿八经的输入框,是否是应该有一个 placeholder 效果啊:
[contenteditable=true]:empty::before {
content: attr(data-placeholder);
color: red;
display: block;
}
复制代码
对于 textarea 标签,能够经过 value 属性值获取用户输入的内容。可是对于设置 contenteditable 属性的元素来讲,就须要具体状况具体分析了:
innerText 和 textContent 是否是又让你傻傻分不清了,关于它们的区别主要有:
因为上述 contenteditable 属性指定了 plaintext-only 属性值,因此这三种属性获取到的值是同样的。
除了指定 plaintext-only 属性值的方法,张鑫旭大神在不少年前写过一个div 模拟 textarea 实现高度自适应,其中是经过 CSS 属性实现只容许输入文本内容:
-webkit-user-modify: read-write-plaintext-only;
复制代码
现在 user-modify 这个 CSS 属性已经被 contenteditable 替代了,不过这依然是一个很神奇的 CSS 属性。
到此,才算实现一个高度自适应的多行文本输入框。不过仍然有不少奇怪的问题,欢迎踩过这方面坑的同窗留言讨论。
除了实现高度自适应的多行文本输入框以外,可编辑元素与 input 和 textarea 标签还有一个很大的不一样,它能够完美的融入到文本当中,例如实现这样一个填空题输入框的效果:
以上即是高度自适应多行文本输入框的两种实现方式:
相比较后者,前者的适用性更强,也是大部分组件库所采用的方式。