textarea高度自适应的两种方案

image

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

方案一

HTML5 Textarea 元素

1. 自动获得焦点

点击编辑自动获得焦点后光标跳转到了最前面,why?

查文档 MDNtextarea元素存在selectionEndselectionStart的属性用来表示选中的文本开始位置和结束位置,DOM接口实例是 HTMLTextAreaElement, 它具有 setSelectionRange 方法,用来选中输入框中的文本,用法:TextAreaElement.setSelectionRange(selectionStart, selectionEnd, [optional] selectionDirection);

那么我们只要做到selectionStart === selectionEnd === value.length , 这样光标就选中到最末尾。

edit.addEventListener("click", function() {
  txt.classList.add("hidden");
  document.body.insertBefore(textarea, edit);
  textarea.innerHTML = "这是需要编辑的信息";
  textarea.focus();
  // textarea.selectionEnd = textarea.innerHTML.length;

  textarea.setSelectionRange(
    textarea.innerHTML.length,
    textarea.innerHTML.length
  );
});

2. 自适应高度

当不设置高度时换行会出现滚动条,所以只要让textarea滚动条消失,让其height = scrollHieght, 那么如何监听textarea的变化,则需要input事件,让textarea高度动态等于它的scrollHeight即可,代码如下:

textarea.addEventListener("input", function() {
    this.style.height = `${this.scrollHeight}px`;
});

为什么每次输入都会使得高度增加(每次增加4px),经过测试Safari同样也是这个效果,Firefox符合预期高度不异常增加,查看Chrome控制台发现(查看 chromium) textarea内置了很多样式,尝试将去掉padding后,可以正常随着内容增加高度自适应了。

<style>
textarea {
  padding: 0;
}
</style>
<textarea></textarea>
<script>
const textarea = document.createElement("textarea");
textarea.addEventListener("input", function(e) {
  this.style.height = `${this.scrollHeight}px`;
});
</script>

为什么去掉padding值,输入后高度就不增加了?(未找到合理的解释,欢迎留言讨论解答)

但是删除文本的情况下height并未能自动缩放,分析原因, 当删除文本换行的时候scrollHeight的大小并未发生改变,scrollHeight作为只读属性( MDN-文档),如何让scrollHeight高度重新计算哪?只有改变高度,于是:

textarea.addEventListener("input", function(e) {
  this.style.height = "inherit";
  this.style.height = `${this.scrollHeight}px`;
});

到此高度自适应输入内容完成,但是每次换行的闪动很难受,transition走一波,

textarea {
  overflow: hidden;  // 防止换行出现滚动条闪动
  padding: 5px 10px;
  box-sizing: border-box;
  transition: all 0.2s linear;
}

结果transition并没效果,原因height初始值或者参考值必须为数值过渡动画才会生效,height设置为auto或者inherit动画不会生效, 所以折中的办法就是当删除的时候重新重置高度,输入时不需要重置,这样输入时动画生效, 删除没有动画, 暂时没想到更好的办法。

textarea.addEventListener("keyup", function(e) {
  if (e.keyCode === 8) {
    this.style.height = "inherit";
    this.style.height = `${this.scrollHeight}px`;
  } else {
    this.style.height = `${this.scrollHeight}px`;
  }
});

3. 支持粘贴文本,图片等

textarea只能输入文本,无法实现粘贴图片。

textarea.addEventListener(
  "paste",
  function(e) {
    e.preventDefault();
    console.log(
      "paste",
      e.clipboardData.items,
      e.clipboardData.types,
      e.clipboardData.getData("text/html"),
      e.clipboardData.getData("text/plain"),
      e.clipboardData.getData("text/Files")
    );
  },
  false
);

方案二

div contenteditable 替换textarea

1. 自动获得焦点

编辑div contenteditabletrue,然后利用RangeSelection 光标移动到最后的效果。

edit.addEventListener("click", () => {
  textarea.setAttribute("contenteditable", true);
  textarea.focus();
  const range = document.createRange();

  // range 包含的内容
  range.selectNodeContents(textarea);

  // range.setStart(textarea.firstChild, 0);
  // range.setStart(textarea.lastChild, textarea.innerHTML.length);

  // 起始位置是否相同
  range.collapse(false);
  const sel = window.getSelection();
  
  // 将所有的区域都从选区中移除。
  sel.removeAllRanges();
  
  // 一个区域(Range)对象将被加入选区。
  sel.addRange(range);
});

2. 自适应高度

div contenteditable 天然支持根据输入来自适应高度。

3. 粘贴图片, 文本等

textarea.addEventListener("paste", function(e) {
  e.preventDefault();
  const clipboardData = e.clipboardData || e.originalEvent.clipboardData;
  // 获取纯文本
  let text = clipboardData.getData("text/plain");
  let file = clipboardData.getData("text/plain");
  // console.log(clipboardData.items, clipboardData.getData("text/Files"));

  // 插入img,可以做一些上传图片的一些操作
  insertImg(clipboardData);

  // 只输入纯文本
  document.execCommand("insertText", false, text);
});

此方法可以限定只能上传文本或者图片。

欢迎留言讨论其他textarea高度自适应的方案。

前端学习路-郭盖
此专栏旨在记录开发过程中自己觉得需要记录的、值得分享的知识点和技术坑位,能帮到别人我将非常荣幸, ...
1.1k 声望
314 粉丝
0 条评论
推荐阅读
React Hook 系列(三):记一次中台项目的Hook沉淀
200 bytes to never think about React state management libraries ever again.永远不必再考虑 React 状态管理了,仅仅 200 字节的状态管理解决方案。

郭盖6阅读 3.7k

安全地在前后端之间传输数据 - 「3」真的安全吗?
在「2」注册和登录示例中,我们通过非对称加密算法实现了浏览器和 Web 服务器之间的安全传输。看起来一切都很美好,但是危险就在哪里,有些人发现了,有些人嗅到了,更多人却浑然不知。就像是给门上了把好锁,还...

边城31阅读 7.1k评论 5

封面图
涨姿势了,有意思的气泡 Loading 效果
今日,群友提问,如何实现这么一个 Loading 效果:这个确实有点意思,但是这是 CSS 能够完成的?没错,这个效果中的核心气泡效果,其实借助 CSS 中的滤镜,能够比较轻松的实现,就是所需的元素可能多点。参考我们...

chokcoco20阅读 2k评论 2

在前端使用 JS 进行分类汇总
最近遇到一些同学在问 JS 中进行数据统计的问题。虽然数据统计一般会在数据库中进行,但是后端遇到需要使用程序来进行统计的情况也非常多。.NET 就为了对内存数据和数据库数据进行统一地数据处理,发明了 LINQ (L...

边城17阅读 1.9k

封面图
【已结束】SegmentFault 思否写作挑战赛!
SegmentFault 思否写作挑战赛 是思否社区新上线的系列社区活动在 2 月 8 日 正式面向社区所有用户开启;挑战赛中包含多个可供作者选择的热门技术方向,根据挑战难度分为多个等级,快来参与挑战,向更好的自己前进!

SegmentFault思否20阅读 5.6k评论 10

封面图
过滤/筛选树节点
又是树,是我跟树杠上了吗?—— 不,是树的问题太多了!🔗 相关文章推荐:使用递归遍历并转换树形数据(以 TypeScript 为例)从列表生成树 (JavaScript/TypeScript) 过滤和筛选是一个意思,都是 filter。对于列表来...

边城18阅读 7.6k评论 3

封面图
Vue2 导出excel
2020-07-15更新 excel导出安装 {代码...} src文件夹下新建一个libs文件夹,新建一个excel.js {代码...} vue页面中使用 {代码...} ===========================以下为早期的文章今天在开发的过程中需要做一个Vue的...

原谅我一生不羁放歌搞文艺14阅读 19.8k评论 9

1.1k 声望
314 粉丝
宣传栏