高度自适应的 textarea

背景介绍

正如我们所知道的 textarea 是一个行内块元素 display: inline-block 并且它的默认宽高由 cols & rows 决定, 也就是说 textarea 的 height 并不会自适应于内容长度.

textarea 的宽高是如何决定的?

参考文章: http://www.zhangxinxu.com/wordpress/2016/02/html-textarea-rows-height/

那么, 我们今天的任务就是来思考如何创建一个 高度内容自适应的 textarea 组件.

我将介绍三种思路实现 高度内容自适应的 textarea.

这三种思路的 React 组件实现代码如下:

https://github.com/teeeemoji/textareaAutoSizeSolutions

所有参考链接(锚点失效, 参考链接在最后)

方案概要

这是三种方案的概述和实现思路的简介, 实现方案 & 遇到的坑 & 拓展知识点, 点击 (查看更多) or 直接看 teeeemoji 的 demo.

  • 方案一: 两次调整 textarea.style.height

    textarea 的 onchange 触发 resize 方法
    下面是 resize 方法的逻辑
    ① textarea.style.height = 'auto';// 让 textarea 的高度恢复默认
    ② textarea.style.height = textarea.scrollHeight + 'px';// textarea.scrollHeight 表示 textarea 内容的实际高度

  • 方案二: 利用一个 ghostTextarea 获得输入框内容高度, 再将这个高度设置给真实的 textarea

    textarea 构建时创建 ghostTextarea, onchange 触发 resize 方法
    ① 创建 textarea 的时候, 同时创建一个一模一样的隐藏 ghostTextarea;
    ② ghostTextarea 的属性全部克隆自 textarea, 但是 ghostTextarea 是隐藏的, 并且 ghostTextarea.style.height = 0; 也就是说 ghostTextarea.scrollHeight 就是 textarea 中内容的真是高度
    resize 方法处理流程

    • step-1: textarea.value 先设置给 ghostTextarea,
    • step-2: 拿到 ghostTextarea.scrollHeight
    • step-2: 将 textarea.style.height = ghostTextarea.scrollHeight
  • 方案三: 使用 (div | p | ...).contenteditable 代替 textarea 作为输入框

    div 是块级元素, 高度本身就是内容自适应的(除非设置 max-width or min-widht
    使用contenteditable 让 div 代替 textarea, 省去各种计算高度的逻辑

方案对比

满分3分, 三种方案通过优化, 在用户体验和兼容性上都能达到满分. 因此差别仅仅在于这几个方案的实现难度. (仅仅是基于 react 组件的实现复杂度).

用户体验对比(在最后面, 简书对 markdown 内嵌 html 支持不友好, 锚点都不能用了)

方案 用户体验 兼容性 易用性 综合评价
方案一 3 3 3 10
方案二 3 3 1 7
方案三 3 3 2 8

毫无疑问方案一是最优选择, 多加1分以示奖励;

方案一: 两次调整 textarea.style.height

实现思路

  1. 渲染一个 textarea 元素
<textarearef={this.bindRef}className={style['textarea'] + ' ' + className}placeholder={placeholder}value={value}onChange={this.handleChange} // 看这里
/>
  1. textarea 的 onChange 事件触发 resize
handleChange(e) {this.props.onChange(e.target.value);this.resize();  // 看这里
}
  1. reize 事件的实现
// 重新计算 textarea 的高度
resize() {if (this.inputRef) {console.log('resizing...')this.inputRef.style.height = 'auto';this.inputRef.style.height = this.inputRef.scrollHeight + 'px';}
}
  1. 注意 componentDidMount 的时候, 执行一次 resize 方法, 初始化 textarea 的高度哦.

优化点

避免两次渲染,造成内容抖动

在 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 // 这个是 ghostTextareaclassName={style['textarea-ghost']}ref={this.bindGhostRef}onChange={noop}/></div>
)
  • 初始化的时候拷贝属性

    初始化必须使用工具方法将 textarea 的属性拷贝到 ghostTextarea 去. 因为 textarea 的样式再组件外也能控制, 因此初始化的时候 copy 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 的隐藏属性列表

    '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 事件

        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;

123.png

更多优化思路

  • 页面存在多个 textarea 的时候, 能不能考虑 复用同一个 ghostTextarea

方案三: 使用 div.contenteditable 代替 textarea

实现思路

  • 渲染一个 div.contenteditable=true

    return (
    <div className={style['comp-div-contenteditable']}><divref={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 会出现以下两种问题:

    • & 会被转码成 &amp ;
    • 空白符合并
      使用 div.innerText 在低版本 firfox 上要做兼容处理.

    因此使用哪种方式主要看需求.

  • placeholder 的实现

    div 的 placeholder 属性是无效, 不会显示出来的, 现存一种最简单的方式, 使用纯 css 的方式实现 div 的 placeholder

    .textarea[placeholder]:empty:before { /*empty & before 两个伪类*/content: attr(placeholder); /*attr 函数*/color: #555;
    }
    

优化点

去除支持富文本

div.contenteditable 是默认支持富文本的, 可能会以 粘贴 or 拖拽 让输入框出现富文本;

234.png

监听 div 的 onPaste 事件

handlePaste(e) {e.preventDefault();let text = e.clipboardData.getData('text/plain'); // 拿到纯文本document.execCommand('insertText', false, text); // 让浏览器执行插入文本操作
}

handlePaste 的更多兼容性处理

几个大网站的高度自适应 textarea 对比

我分别查看了微博, ant.design组件库, 知乎 的自适应输入框的实现.

微博: 采用方案二

未输入时

5aa4b41fdf0082f1c9.png

输入后

5aa4b4517a668254df.png

但是微博的实现存在用户体验上的缺陷, 会抖动!!!

weibo.git.gif

ant.design: 采用方案二

体验超级棒哦

antd.gif

知乎: 采用方案三

看上去竟然存在 bug , 其实上面的截图也有

zhih.gif

参考链接列表

  • textarea mdn 文档: 看看有哪些影响 textarea 宽高的属性
  • HTML textarea cols,rows属性和宽度高度关系研究
  • CSS Tricks: textarea 使用上的小技巧. 一些样式技巧.
  • 开源的 react auto resize textarea: 一个更好的方案二的实现, 源码优雅短小
  • can i use 兼容性检查工具: execCommand, innerText, requireAnimationFrame, 等等各种各样属性的兼容性检查
  • contenteditable MDN
  • 一个方案三的实现, a good demo
  • 小tip: 如何让contenteditable元素只能输入纯文本

高度自适应的 textarea react 解决方案相关推荐

  1. html文本域 高度自适应,textarea高度自适应,textarea随着内容高度变化

    有时候我们需要textarea的高度随着内容的多少去自适应的变化,今天我使用了JS原生和JQ写了一个textarea的高度随着他的内容高度变化的代码,希望能帮上忙. 废话不多说直接贴代码: texta ...

  2. textarea高度自适应

    方法一.直接在textarea中添加oninput方法: <textarea οninput="this.style.height = this.scrollHeight + 'px' ...

  3. php textarea 默认值,html中的textarea属性大全(设置默认值 高度自适应 获取内容 限制输入字数 placeholder)...

    1.textarea设置默认值 HTML: 此段代码设置一个textarea文本框 并且设置为隐藏 2.textarea高度自适应 今天需要些一个回复评论的页面,设计师给的初始界面就是一个只有一行的框 ...

  4. textarea的高度自适应

    1.只读状态下的高度自适应 //maxHeight为最大高度 function autoTextarea(maxHeight){$.each($("textarea"), func ...

  5. java textarea 大小_textarea高度自适应的两种方案

    阅读全文你将获得以下解决方案. 点击长文本编辑textarea,自动获得焦点 随着输入值自动伸缩高度 可复制添加信息 可粘贴文本 可粘贴图片 以下实例代码执行环境为Chrome80 方案一 HTML5 ...

  6. div模拟textarea文本域轻松实现高度自适应——张鑫旭

    by zhangxinxu from http://www.zhangxinxu.com 本文地址: http://www.zhangxinxu.com/wordpress/?p=1362 一.关于t ...

  7. freemarker模板文件中文本域(textarea)的高度自适应实现

    2019独角兽企业重金招聘Python工程师标准>>> freemarker模板文件中实现文本域(textarea)的高度自适应实现. 从网上找的大部分办法中很多方法兼容性并不好,要 ...

  8. div模拟textarea文本框,输入文字高度自适应,且实现字数统计和限制

    需求: 文本框可根据输入内容高度自适应,不带滚动条,高度可变的那种,我搜索了很多,发现textarea无法实现我的需求,scrollHeigh这个无法获取实际内容的高度(我用的是那种简单的,可能我写的 ...

  9. textarea如何实现高度自适应?

    文章来源:http://blog.csdn.net/tianyitianyi1/article/details/49923069 转自:http://www.xuanfengge.com/textar ...

最新文章

  1. [导入]日志 20071206 (WCF Architecture)
  2. html5 css3 卡片切换,HTML5之纯CSS3实现的tab标签切换
  3. 两种高性能I/O设计模式(Reactor/Proactor)的比较
  4. readyboost提升明显吗_iphone12promax参数对比11ProMax区别 性能提升多少
  5. 【Tools】Linux远程拷贝工具(WinSCP)
  6. c#中Dictionary、ArrayList、Hashtable和数组 Array 的区别(转)
  7. 《游戏编程入门 4th》笔记(2 / 14):监听Windows消息
  8. JAVAEWEB实现文件的上传案例
  9. Python入门--列表元素的增加操作,append(),extend(),insert(),切片
  10. 2021年全球及中国企业级路由器市场竞争格局分析,呈现出寡头竞争的市场格局「图」
  11. ORACLE递归_ 树形遍历查询根节点、父节点、子节点_002
  12. 《天勤数据结构》笔记——使用两个栈实现共享栈实现(C/C++)
  13. 大版本号跨越,AIDA64更新6.0版本:更新测试,支持Zen 2架构
  14. 如何使用网易有道词典翻译英文文档(word格式、pdf格式)
  15. IIS 禁用自动目录列表功能
  16. 迅雷如何添加html文件夹,迅雷7上我的收藏怎么找
  17. Wind7外接显示器选择拓展模式后,鼠标只能往右移动才能切换到外接显示器上,不能修改切换方向...
  18. la la love on my mind
  19. 嵌入式系统开发笔记91:认识ARM微控制器架构
  20. 用R语言进行数据探索

热门文章

  1. DSPE-PEG2-propargyl分子式:C49H92NO11P化学表征
  2. 手机 html5 hammer drag widget,Hammer.js+轮播原理实现简洁的滑屏功能
  3. CA证书和SSL证书的区别
  4. [HNOI]2003 消防局的建立
  5. 个人爱好篇---纸模型
  6. 无线充电鼠标垫 B.FRIENDit壁虎忍者MP20办公游戏鼠标垫 RGB发光电竞防滑加厚鼠标垫黑色
  7. (原创)虚幻3--控制台命令参数--1
  8. 手撕OpenCV源码之resize(INTER_AREA)
  9. 微软1G网盘注册方法
  10. IIS7 中多个版本php共存的方法