背景介绍我们知道,textarea是一个inline block元素显示:inline-block及其默认的宽度和高度由COLS &决定;Rows确定textarea的高度不适应内容长度。
textarea的宽度和高度是如何确定的?参考张新旭的文章HTML TextArea Cols,研究行属性与宽度和高度的关系
因此,我们今天的任务是考虑如何创建一个高度内容自适应的textarea组件。我将介绍实现高度内容自适应的textarea的三个想法,具体代码为textareaAutoSizeSolutions。
方案概要
这是对三种方案的概述和实现思路的简单介绍。遇到的坑&;扩展知识点,点击查看teeeemoji的演示。
方案一:调整textarea.style.height两次。
textarea的onchange触发resize方法,下面是resize方法的逻辑。
textarea . style . height = ‘ auto ‘;// 1.将textarea的高度恢复为默认的textarea . style . height = textarea . scroll height+’ px ‘;// 2.textarea.scrollHeight表示*textarea*内容的实际高度。方案二:用一个ghostTextarea获取输入框内容的高度,然后将这个高度设置为真实的Textarea。
构建textarea时创建ghost textarea,onchange触发resize方法:
创建 textarea 的时候, 同时创建一个一模一样的隐藏 ghostTextarea;ghostTextarea 的属性全部克隆自 textarea, 但是 ghostTextarea 是 隐藏 的, 并且 ghostTextarea.style.height = 0; 也就是说 ghostTextarea.scrollHeight 就是 textarea 中内容的真是高度。
Resize方法处理流程:
textarea.value 先设置给 ghostTextarea,拿到 ghostTextarea.scrollHeight将 textarea.style.height = ghostTextarea.scrollHeight
方案三:使用(div | p |…).contenteditable而不是textarea作为输入框。
Div是块级元素,高度本身是内容自适应的(除非设置了max-width或min-width)。用contenteditable让div替换textarea,省去了计算高度的各种逻辑。
方案比较
满分3分,通过优化,三个方案在用户体验和兼容性方面都能达到满分。所以区别只在于这些方案的实现难度(仅基于react组件的实现复杂度)。方案比较:
毫无疑问,方案1是最佳选择,多加一分作为奖励;
调整textarea.style.height一次或两次。
实现思路渲染一个 textarea 元素
& lttextareref = { this . bind ref } class name = { style[‘ textarea ‘]+’+class name } placeholder = { placeholder } value = { value } onchange = { this . handle change }//看这里/& gt;Textarea的onChange事件触发ResizeHandleChange(e){ this . props . onChange(e . target . value);this . resize();//这里看一下}resize事件的实现//重新计算textarea resize()的高度{if (this。inputref) {console.log(‘调整大小…)这个。输入参考。style.height = ‘ autothis . input ref . style . height = this . input ref . scroll height+’ px ‘;}}注意componentDidMount的时候,执行一次resize方法,初始化textarea的高度。优化点,避免二次渲染,会造成内容抖动。
react中,组件receiveProps会渲染一次,直接调整textarea的高度也会重绘浏览器,会导致两次重绘,两次重绘时textarea的内容可能会抖动。
优化思路:先触发resize再触发render,用最简单的思路完美解决问题。
方案二:用一个ghostTextarea获取输入框的内容高度,然后将这个高度设置为真实的Textarea。
实现理念
同时渲染两个文本区域,一个真实文本区域和一个隐藏文本区域。
return(& lt;div class name = { style[‘ comp-textarea-with-ghost ‘]} & gt;& ltTextarea // This is true ref = {this。bind ref } class name = { style[‘ textarea ‘]+’+class name } placeholder = { placeholder } value = { value } onchange = { this。handle change } style = { { height } }/> & lt;Textarea //这是ghost textarea class name = { style[‘ textarea-ghost ‘]} ref = { This。bindgosteref } onchange = { noop }/> & lt;/div & gt;)初始化时,复制属性。初始化时,必须使用工具将textarea的属性复制到ghostTextarea。因为可以在组件外部控制textarea的样式,所以在初始化时复制样式是最安全的。
这是要复制的所有属性的列表:
const size _ STYLE =[‘ letter-spacing ‘,’ line-height ‘,’ font-family ‘,’ font-weight ‘,’ font-size ‘,’ font-style ‘,’ tab-size ‘,’ text-rendering ‘,’ text-transform ‘,’ width ‘,’ text-indent ‘,’ padding-top ‘,’ padding-right ‘,’ padding-bottom ‘,’ padding-left ‘,’ border-top-width ‘,’ border-bottom-width ‘,’ border-left-width ‘,’ box-SIZING ‘];这是ghostTextarea的隐藏属性列表:
const HIDDEN _ TEXTAREA _ STYLE = { ‘ min-height ‘:’ 0 ‘,’ max-height’: ‘none ‘,height: ‘0 ‘,visibility: ‘hidden ‘,overflow: ‘hidden ‘,position: ‘absolute ‘,’ z-index ‘:-1000 ‘,top: ‘0 ‘,right: ‘0 ‘,};这是一种复制风格的工具方法。
//获取real textarea {const style = window的所有样式函数。getComputedStyle(node);if(style = = = null){ return null;} return SIZING _ style . reduce((obj,name)= & gt;{ obj[name]= style . getpropertyvalue(name);返回obj}, {});}//将真实textarea的样式复制到ghosttextarea export const copy style = function(to node,from node){ constnode styling = calculatenodestying(from node);if(nodestying = = = null){ return null;}Object.keys(节点样式)。forEach(key = & gt;{ to node . style[key]= nodestying[key];});object . keys(HIDDEN _ TEXTAREA _ STYLE)。forEach(key = & gt;{toNode.style.setProperty(key,HIDDEN_TEXTAREA_STYLE[key],’ important ‘,);});}textarea的onChange事件首先reize,然后触发Change事件。
handle change(e){ this . resize();设value = e . target . value;this.props.onChange(值);}textarea的resize方法
resize() {console.log(‘resizing … ‘)const height = calculategghosttextareaheight(this . ghost ref,this . input ref);this . setstate({ height });} calculateGhostTextareaHeight工具方法
//首先将内容设置到ghostTextarea中,然后获取ghost textarea。scroll height export const calculategghosttextareaaheight = function(ghost textarea,textarea) {if(!ghost textarea){ return;} ghost textarea . value = textarea . value | | textarea . placeholder | | ‘ x ‘ return ghost textarea . scroll height;}优化点
避免二次渲染,造成内容抖动。
在react中,组件receiveProps将被渲染一次,将height属性设置为textarea也将重绘浏览器。那么就会造成两次重绘,两次重绘的时候textarea的内容可能会抖动。
演示中体现了以下两个想法。
优化思路一:合并帧渲染
使用窗口。requestanimationframe &;Window.cancelAnimationFrame取消第一帧的渲染,直接渲染高度已经调整的textarea
优化思路二:减少渲染数量
使用反应式批处理setState方法来减少重新呈现的特性;在textarea onChange方法中同时触发两个setState
更多优化想法
页面存在多个 textarea 的时候, 能不能考虑 复用同一个 ghostTextarea
选项3:使用div.contenteditable代替textarea。
实现理念
呈现一个div.contenteditable=true。
return(& lt;div class name = { style[‘ comp-div-content editable ‘]} & gt;& ltdiv ref = { this . bind ref } className = { class name(style[‘ textarea ‘],class name,{[style[’empty’]]:!value })} onChange = { this . handle change } on paste = { this . handle paste } placeholder = { placeholder } content editable/& gt;& lt/div & gt;)get &;设置编辑的内容:textarea取值或通过textarea.value设置值,但改为div后,需要使用div.innerHTML或div.innerText取值或设置值。
使用div.innerHTML时有两个问题:
& 会被转码成 &空白符合并 使用 div.innerText 在低版本 firfox 上要做兼容处理.
所以用哪种方法主要看需求。
占位符的实现:
div的占位符属性无效,将不会显示。用纯css实现div的占位符有一个最简单的方法。
。textarea[placeholder]:empty:before {/* empty & amp;*/content之前的两个伪类:attr(占位符);/*属性函数*/color:# 555;}优化点
移除对rtf的支持
Div.contenteditable默认支持富文本,富文本可以通过粘贴或拖动的方式出现在输入框中。
侦听div的onPaste事件。
handle paste(e){ e . prevent default();let text = e . clipboard data . get data(‘ text/plain ‘);//获取纯文本文档。execcommand(‘插入文本’,false,文本);//让浏览器执行插入文本的操作}更多兼容处理}handlePaste。
几个大型网站高适应性textarea的比较
我分别在微博、ant.design组件库、知乎查看了自适应输入框的实现。
几个大型网站高适应性textarea的比较
我分别在微博、ant.design组件库、知乎查看了自适应输入框的实现。
微博:采用方案二。
未输入时
输入后
但是微博的实现在用户体验上有些缺陷,会抖!!!
Ant.design:采用方案二。
体验超级棒。
Zhihu:采用方案三。
好像有bug,其实上面的截图也有。
希望本文能帮助到您!
喜欢+转发,让更多人看到这个内容(不喜欢都是流氓-_-)
关注{I},享受首条体验!
每周集中攻克一个前端技术难点。更多精彩前端内容,私信我回复“教程”
原文链接:http://eux.baidu.com/blog/Fe/%e9% AB % 98% E5 % BA % A6 % E8 % 87% AA % E9 % 80% 82% E5 % BA % 94% E7 % 9A % 84% 20。
作者:张廷岑
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。