美解决textarea输入框提示文字,必须添加默认内容
<input/>有placeholder标签,可以添加提示文字 ,但是<textarea>没有;所以提出以下解决方案
1.
<textarea id="t" rows="20" cols="20"></textarea> <script> var t = document.getElementById('t'); let aaa = '项目需求概要'; t.innerHTML=aaa; t.onfocus = function(){ if(this.value == aaa){this.value = ''} }; t.onblur = function(){ if(this.value == ''){ this.value = aaa; } }; </script>
2.
<textarea cols="50" rows="5" id="textarea" onfocus="if(value=='限100字'){value=''}" onblur="if (value ==''){value='限100字'}">限100字</textarea>
3.
这你需要把id='note'的div 定位到textarea上面
<div style="position:relative;"> <textarea class="textarea" onfocus="document.getElementById('note').style.display='none'" onblur="if(value=='')document.getElementById('note').style.display='block'"></textarea> <div id="note" class="note"> <font color="#777">项目需求概要</font> </div> </div>
4.
rea 对象
Area 对象代表图像映射的一个区域(图像映射指的是带有可点击区域的图像)
在 HTML 文档中 <area> 标签每出现一次,就会创建一个 Area 对象。
Area 对象的属性
accessKey 设置或返回访问某个区域的快捷键。
alt 设置或返回当浏览器无法显示某个区域时的替换文字。
coords 设置或返回图像映射中可点击区域的坐标。
hash 设置或返回某个区域中 URL 的锚部分。
host 设置或返回某个区域中 URL 的主机名和端口。
href 设置或返回图像映射中链接的 URL。
id 设置或返回某个区域的 id。
noHref 设置或返回某个区域是否应是活动的还是非活动的。
pathname 设置或返回某个区域中的 URL 的路径名。
protocol 设置或返回某个区域中的 URL 的协议。
search 设置或返回某个区域中 URL 的查询字符串部分。
shape 设置或返回图像映射中某个区域的形状。
tabIndex 设置或返回某个区域的 tab 键控制次序。
target 设置或返回在何处打开区域中的 link-URL。
正如我们所知道的 textarea 是一个行内块元素 display: inline-block 并且它的默认宽高由 cols & rows 决定, 也就是说 textarea 的 height 并不会自适应于内容长度.
textarea 的宽高是如何决定的? 参考张鑫旭的文章 HTML textarea cols,rows属性和宽度高度关系研究
那么, 我们今天的任务就是来思考如何创建一个 高度内容自适应的 textarea 组件,我将介绍三种思路实现 高度内容自适应的 textarea,具体代码 textareaAutoSizeSolutions
这是三种方案的概述和实现思路的简介, 实现方案 & 遇到的坑 & 拓展知识点, 点击查看 teeeemoji 的 demo.
方案一: 两次调整 textarea.style.height
textarea 的 onchange 触发 resize 方法,下面是 resize 方法的逻辑
textarea.style.height = 'auto';// 1. 让 textarea 的高度恢复默认 textarea.style.height = textarea.scrollHeight + 'px';// 2. textarea.scrollHeight 表示 *textarea* 内容的实际高度
方案二: 利用一个 ghostTextarea 获得输入框内容高度, 再将这个高度设置给真实的 textarea
textarea 构建时创建 ghostTextarea, onchange 触发 resize 方法:
resize 方法处理流程:
方案三: 使用 (div | p | ...).contenteditable 代替 textarea 作为输入框
div 是块级元素, 高度本身就是内容自适应的(除非设置 max-width or min-widht) 使用 contenteditable 让 div 代替 textarea, 省去各种计算高度的逻辑。
方案对比
满分3分, 三种方案通过优化, 在用户体验和兼容性上都能达到满分. 因此差别仅仅在于这几个方案的实现难度. (仅仅是基于 react 组件的实现复杂度). 方案对比:
毫无疑问方案一是最优选择, 多加1分以示奖励;
方案一两次调整 textarea.style.height
<textarea ref={this.bindRef} className={style['textarea'] + ' ' + className} placeholder={placeholder} value={value} onChange={this.handleChange} // 看这里 />
handleChange(e) { this.props.onChange(e.target.value); this.resize(); // 看这里 }
// 重新计算 textarea 的高度 resize() { if (this.inputRef) { console.log('resizing...') this.inputRef.style.height = 'auto'; this.inputRef.style.height = this.inputRef.scrollHeight + 'px'; } }
避免两次渲染,造成内容抖动
在 react 中, 组件 receiveProps 的时候会 render 一次, 直接调整 textarea 的 height 也会浏览器的重绘,那么就会造成两次重绘, 并且两次重绘的时候, textarea 的内容可能会发生抖动.
优化思路:先触发 resize 后触发 render 用最简单的思路完美解决问题
方案二: 利用一个 ghostTextarea 获得输入框内容高度, 再将这个高度设置给真实的 textarea
实现思路
同时渲染两个 textarea, 一个真实 textarea 一个隐藏 textarea
return ( <div className={style['comp-textarea-with-ghost']}> <textarea // 这个是真的 ref={this.bindRef} className={style['textarea'] + ' ' + className} placeholder={placeholder} value={value} onChange={this.handleChange} style={{height}} /> <textarea // 这个是 ghostTextarea className={style['textarea-ghost']} ref={this.bindGhostRef} onChange={noop} /> </div> )
初始化的时候拷贝属性,初始化必须使用工具方法将 textarea 的属性拷贝到 ghostTextarea 去. 因为 textarea 的样式再组件外也能控制, 因此初始化的时候 copy style 是最安全的。
这是所以要拷贝的属性的列表:
const SIZING_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-right-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', };
这是拷贝 style 的工具方法
// 拿到真实 textarea 的所有 style function calculateNodeStyling(node) { const style = window.getComputedStyle(node); if (style === null) { return null; } return SIZING_STYLE.reduce((obj, name) => { obj[name] = style.getPropertyValue(name); return obj; }, {}); } // 拷贝 真实 textarea 的 style 到 ghostTextarea export const copyStyle = function (toNode, fromNode) { const nodeStyling = calculateNodeStyling(fromNode); if (nodeStyling === null) { return null; } Object.keys(nodeStyling).forEach(key => { toNode.style[key] = nodeStyling[key]; }); Object.keys(HIDDEN_TEXTAREA_STYLE).forEach(key => { toNode.style.setProperty( key, HIDDEN_TEXTAREA_STYLE[key], 'important', ); }); }
textarea 的 onChange 事件 先 reize 再触发 change 事件
handleChange(e) { this.resize(); let value = e.target.value; this.props.onChange(value); }
textarea 的 resize 方法
resize() { console.log('resizing...') const height = calculateGhostTextareaHeight(this.ghostRef, this.inputRef); this.setState({height}); }
calculateGhostTextareaHeight 工具方法
// 先将内容设置进 ghostTextarea, 再拿到 ghostTextarea.scrollHeight export const calculateGhostTextareaHeight = function (ghostTextarea, textarea) { if (!ghostTextarea) { return; } ghostTextarea.value = textarea.value || textarea.placeholder || 'x' return ghostTextarea.scrollHeight; }
优化点
避免两次渲染,造成内容抖动
在 react 中, 组件 receiveProps 的时候会 render 一次, 给 textarea 设置 height 属性也会浏览器的重绘.那么就会造成两次重绘, 并且两次重绘的时候, textarea 的内容可能会发生抖动.
下面两种思路, 在 demo 中均有体现
优化思路一: 合并祯渲染
使用 window.requestAnimationFrame & window.cancelAnimationFrame 来取消第一祯的渲染, 而直接渲染高度已经调整好的 textarea;
优化思路二: 减少渲染次数
利用 react 批处理 setState 方法, 减少 rerender 的特性; 在 textarea onChange 方法中同时触发两个 setState;
更多优化思路
方案三: 使用 div.contenteditable 代替 textarea
实现思路
渲染一个 div.contenteditable=true
return ( <div className={style['comp-div-contenteditable']}> <div ref={this.bindRef} className={classname(style['textarea'], className, {[style['empty']]: !value})} onChange={this.handleChange} onPaste={this.handlePaste} placeholder={placeholder} contentEditable /> </div> )
获取 & 设置 编辑的内容: textarea 通过 textarea.value 来取值 or 设置值, 但换成了 div 之后, 就要使用 div.innerHTML or div.innerText 来取值 or 设置值.
使用 div.innerHTML 会出现以下两种问题:
因此使用哪种方式 主要看需求.
placeholder 的实现:
div 的 placeholder 属性是无效, 不会显示出来的, 现存一种最简单的方式, 使用纯 css 的方式实现 div 的 placeholder
.textarea[placeholder]:empty:before { /*empty & before 两个伪类*/ content: attr(placeholder); /*attr 函数*/ color: #555; }
优化点
去除支持富文本
div.contenteditable 是默认支持富文本的, 可能会以 粘贴 or 拖拽 让输入框出现富文本;
监听 div 的 onPaste 事件
handlePaste(e) { e.preventDefault(); let text = e.clipboardData.getData('text/plain'); // 拿到纯文本 document.execCommand('insertText', false, text); // 让浏览器执行插入文本操作 }
handlePaste 的更多兼容性处理
几个大网站的高度自适应 textarea 对比
我分别查看了微博, ant.design组件库, 知乎 的自适应输入框的实现.
几个大网站的高度自适应 textarea 对比
我分别查看了微博, ant.design组件库, 知乎 的自适应输入框的实现.
微博: 采用方案二
未输入时
输入后
但是微博的实现存在用户体验上的缺陷, 会抖动!!!
ant.design: 采用方案二
体验超级棒哦
知乎: 采用方案三
看上去竟然存在 bug , 其实上面的截图也有
点赞+转发,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓-_-)
关注 {我},享受文章首发体验!
每周重点攻克一个前端技术难点。更多精彩前端内容私信 我 回复“教程”
原文链接:http://eux.baidu.com/blog/fe/%E9%AB%98%E5%BA%A6%E8%87%AA%E9%80%82%E5%BA%94%E7%9A%84%20Textarea
作者:张庭岑
*请认真填写需求信息,我们会在24小时内与您取得联系。