欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

高度自适应输入框的清晰解题思路

程序员文章站 2022-03-24 15:27:19
...

前言

业务开发中,经常会遇到文本输入框高度随着输入内容高度变化的情况,下面我们来详细说明一下实现这种输入框的方案和解题思路

方案一为一种扩展思路,仅供参考
方案二为常规思路,急着用的小伙伴建议直接看二
第三个板块 附上了react native和react native to web的代码实现方案

※方案一:contenteditable属性法

  • contenteditable属性表示元素是否可编辑,变为可编辑状态的元素还保留其原有的特性,属性值为如下两者之一
    -true或空字符串,表示元素是可编辑的;
    -false表示元素不是可编辑的;
  • 该属性是一个枚举属性,而非布尔属性。这意味着必须显式设置其值为 truefalse 或空字符串中的一个,最好不要简写为<label contenteditable>Example Label</label>
  • 正确的用法是 :
<element contenteditable="value">   --value=true/false
  • ????分析
<style type="text/css">
    .container {
        padding: 20px;
    }
    .auto-input {
        min-height: 100px;
        font-size: 30px;
        border: 1px solid red;
    }
</style>

<body>
    <h1>contenteditable实现的高度自适应输入框</h1>
    <div class="container">
      <div class="auto-input" contenteditable="true" id="auto-input"></div>
    </div>
</body>
  • 注意事项:如想要设置文本输入框的默认高度,设置min-height即可,文本输入框同时支持focus,blur事件,但是即使外表伪装的和textarea一模一样,还是有需要注意的坑点,试着分别输入和复制内容,查看dom节点的变化:
    以下为手打内容和复制黏贴的区别

高度自适应输入框的清晰解题思路
可以看出内容其实并不是真正的纯文本,而是带有样式的富文本格式,黏贴进去的内容呈现的还是复制前的样式,我们可以通过innerText获取到里面的纯文本内容

高度自适应输入框的清晰解题思路
保留此功能很适合展示图片,复制一张图片进入输入框中可直接展示

高度自适应输入框的清晰解题思路

  • 纯文本设置方法:为了完成和textarea同样的作用,我们可以在输入时进行过滤,保证输入的是纯文本文件,有两种办法
  1. 在css中设置
div[contenteditable] {
    user-modify: read-write-plaintext-only
}
user-modify  可以控制普通元素是否可读写
user-modify: read-only; // 只读    
user-modify: read-write; // 可读写,支持富文本   
user-modify: write-only; // 只写,支持的浏览器很少     
user-modify: read-write-plaintext-only;//可读写,纯文本,目前只有webkit内核浏览器支持      
  1. 在html中给div增加属性
<div contenteditable="plaintext-only">
  • 结果分析:
    这种方案的缺点在于,一个元素加上contenteditable,即使解决了可编辑的问题,但是表单控件的一些特性placeholdermaxlengthautofocus,只能js去辅助完成,在移动端会有一些兼容性的问题

方案二:纯textarea文本框实现

思路初始化—创建textarea元素

  • 我们预期通过一个文本输入框textarea即可完成高度自适应,但是实际表明,如果只是通过textarea和一些简单的css方法,设置textarea的min-height后会发现,当输入的元素内容的高度超过设置的最小高度时,会产生滚动条,显然不符合我们的预期

进展1—获取元素的scrollTopoffsetHeight并设置高度

  • 既然产生了滚动条,那就可以尝试着获取文本框的scrollTop,加上文本框的原有高度,在监听到文本框内容改变的时候重新设置文本框的高度
  • 我们用offsetHeight来获取文本框的高度,该属性包含文本框的border + padding + content的高度,因此我们要在css中将textarea设置为border-box,方便设置的时候统一
  • 最后元素高度的设置后只是相当于增加了scrollTop部分的值
  • 实现步骤如下:
    1. input监听文本框内容的改变
    2. 获取文本框的滚动距离scrollTop
    3. 获取文本框的高度offsetHeight
    4. 设置文本框的新heightscrollTop+offsetHeight
  • ????分析:
css:
    <style>
      body,
      html {
        padding-left: 0.1rem;
        margin: 0;
      }
      .auto-input {
        display: block;
        box-sizing: border-box;
        outline: none;
        resize: none;
        padding: 0;
        width: 2rem;
        height: 30px;
        border: 1px solid #000;
        font-size: 0.2rem;
      }
      .title {
        font-size: 0.2rem;
      }
    </style>
html:
 <body>
    <h1 class="title">textarea js高度自适应输入框</h1>
    <textarea class="auto-input" id="autoInput"></textarea>
  </body>
script:
  <script>
      var autoInput = document.getElementById("autoInput");
      autoInput.addEventListener("input", function() {
        var inputScrollTop = autoInput.scrollTop;
        var inputHeight = autoInput.offsetHeight;
        console.log("inputScrollTop:" + inputScrollTop + 'px')
        autoInput.style.height = inputScrollTop + inputHeight + "px";
      });
    </script>
  • 测试结果如下:

高度自适应输入框的清晰解题思路

  • 结果分析: 在输入到下一行的时候,第一个导致换行的字符在触发input事件的时候获取到的scrollTop的值并未改变,仍然为旧值,直到输入新一行的第二个字符的时候才有所响应,猜测原因是scrollTop的获取时机太早导致的问题

进展2—增加定时器延缓执行

  • 我们尝试在js中获取scrollTop时外加入定时器,延缓获取时机
 var autoInput = document.getElementById("autoInput");
      autoInput.addEventListener("input", function() {
          setTimeout(()=>{
            var inputScrollTop = autoInput.scrollTop;
            var inputHeight = autoInput.offsetHeight;
            console.log("inputScrollTop:"+inputScrollTop+'px');
            autoInput.style.height = inputScrollTop + inputHeight + "px";
          },0)
      });
  • 测试结果如下:

高度自适应输入框的清晰解题思路

  • 结果分析:确实如预期,添加定时器延缓了获取scrollTop的时机后,在换行时获取到的scrollTop为准确的。

进展3—获取元素的scrollHeight并设置高度

  • 虽然在开发过程中定时器确实能解决很多头疼的问题,但是个人觉得不是很优雅,这里我们尝试着去获取另一个属性,scrollHeight,对于滚动元素来说scrollHeight代表的是元素原有的高度加上内容滚动到底部时的scrollTop,换句话说也就是元素的完整内容高度,这个属性包含元素的padding,不包含bordermargin
  • 需要注意获取的scrollHeight本身是不包含边框的高度的,但是我们要重置的height,因为设置为border-box,是包含边框的,因此需要将scollHeight加上边框后再设置给textarea的height
  • 实现步骤如下:
    1. 监听元素变化时我们去获取scrollHeight
    2. 设置scrollHeight+border为元素高度
  • ????分析:
script:
    autoInput.addEventListener("input", function() {
        var inputScrollHeight = autoInput.scrollHeight + 2;
        console.log("inputScrollHeight" + inputScrollHeight+"px");
        autoInput.style.height = autoInput.scrollHeight + "px";
      });
  • 测试结果:

高度自适应输入框的清晰解题思路

  • 结果分析:当输入元素内容的时候,确实可以如我们预期的,随着内容的增加高度增加,但是删除的时候表现却发现textarea的高度没有变化,scrollHeight是用来获取元素滚动的scrollTop,padding,以及内容的高度的,那么当删除文本内容时它是否会发生变化呢?从上面视频打印的结果来看删除的时候元素的高度并未发生变化
    原因如下:如果我们没有给文本框设置高度,随着内容的增加
scrollHeight = scrollMaxTop+clientHeight;//元素高度加滚动最大距离

其中scrollTop会随着内容增加可滚动的距离变大而增加,所以在添加文字的情况下我们可以发现scrollHeight会不断增大
我们将盒子本身的高度设置成scrollHeight

newClientHeight = scrollHeight

删除的情况下,盒子高度足够是没有滚动距离的,因此scrollMaxTop为0,newClientHeight不会再更新,因此盒子也就维持了之前的高度

scrollHeight = 0 + newClientHeight

进展4—重置元素的高度

  • 上面因为删除时scrollHeight并不会变化导致元素的高度维持在了之前的最大值,那么我们如果在删除元素时,将元素的高度设置成根据内容自适应(auto)/(""),这样textarea的高度会被重置成最小化
  • 最小化之后重新获取到的scrollHeight,又是可以让当前内容自适应的高度
  • 需要注意auto和"“两者的区别 如果设置为auto的话 textarea的高度会被重置为默认高度,默认高度不是指css中设置的高度,而是浏览器默认的,但是如果设置为(“”)那么相当于清楚的是内联的高度样式,并不会覆盖css的高度,textarea本身的css高度还是存在的,因此表现不同点在于textarea最小时的高度,所以这里建议使用”",可以保留原本设置的高度,但是如果原本设置的是textarea的min-height而不是height,那两个属性均可
  • 实现步骤如下:
    1. 监听元素变化时我们将元素的height设置为““,目的是为了清楚上一次的高度
    2. 重新获取元素的scrollHeight
    3. 设置元素的高度为scrollHeight+border
  • ????分析
autoInput.addEventListener("input", function() {
        autoInput.style.height = "";
        //注意顺序  需要先重置 再获取 
        var inputScrollHeight = autoInput.scrollHeight + 2;
        console.log("inputScrollHeight" + inputScrollHeight+"px");
        autoInput.style.height = inputScrollHeight + "px";
      });
  • 测试结果:

高度自适应输入框的清晰解题思路

  • 结果分析:到此我们终于完成了输入框的基本功能。但是现在每次Input监听时,我们都会将元素的高度重置为空,并且每次都会获取scrollHeight的高度,无疑会对性能有一些损耗,因此我们后面会尝试一下优化方案。

进展5—优化方案

  • 我们尝试增加一些判断条件来减少不必要的执行
  • 在元素内容增多的时候,我们期望只有当元素内容换行的时候才进行重置操作,但是怎么去监听元素的换行呢,我们可以通过获取到的scrollHeight,当该值增加的时候再去进行设置高度的操作
  • 另一方面 当元素内容减少的时候我们才需要将元素的高度置空,是否也可以通过判断scrollHeight的值是否变小才进行这种判断呢,答案无疑是否定的,因为如果不去设置高度为空的话,scrolllHeight的值并不会发生变化,目前想到的判断字符减少的方案为监听输入的字符个数
  • 如果字符数减少的话我们需要将元素的height置为空,然后重新获取元素的scrollHeight,
    • 如果减少的字符导致了换行,那么scrollHeight的值会发生变化
    • 如果减少的字符没有导致换行,那么scrollHeight没有发生变化
  • 无论哪种情况我们都需要去把scrollHeight的值赋值给textarea的height,否则会变为css中设置的最小高度
  • 因此textarea高度重新设置的的条件为 scrollHeight增加导致换行或者文字内容减少
  • 实现步骤如下:
    1. 我们先获取textarea原本的scrollHeight和字符串长度
    2. 在监听到内容改变的时候,我们获取一下新内容的长度
    3. 如果长度变小 重置文本框的高度
    4. 然后获取文本框的scrollHeight
    5. 如果scrollHeight变大或者元素的height不存在进入到if判断中
    6. 将之前存储的文本框的高度设置成新的方便下一次比较
    7. 设置文本框的高度为新获取到的scrollHeight+边框 结束if语句
    8. 最后重置下文本框的新内容的长度
  • ????分析
  var lastScrollHeight = autoInput.scrollHeight;
    var lastTextLength = autoInput.value.length;
    autoInput.addEventListener("input", function() {
        var inputTextLength = autoInput.value.length;
        if (inputTextLength < lastTextLength){
            autoInput.style.height = "";
        }
        //注意这句话一定要写在设置height为空的后面,否则获取不到最新的scrollHeight
        var inputScrollHeight = autoInput.scrollHeight;
        //注意如果height为空的话也需要重置高度 否则高度有问题
        if(lastScrollHeight < inputScrollHeight || !autoInput.style.height){
            lastScrollHeight = inputScrollHeight;
            autoInput.style.height = inputScrollHeight + 2 + "px";
        }
        lastTextLength = autoInput.value.length;
    });

  • 测试结果:同上
  • 结果分析:至此已经完成了比较完善的自适应输入框

react native和rn2web的实现方法

RN的实现方案

  • rn方法提供onContentSizeChange的函数,onContentSizeChange是在内容布局改变(如换行)的时候能获取到当前contentSize中的高度,然后通过state调整为input的高度
  • ????分析
_onChange=(event)=> {
        this.setState({
            text: event.nativeEvent.text,
        });
 }
_onContentSizeChange=(event)=> {
        this.setState({
            height: event.nativeEvent.contentSize.height
        });
}
render() {
        return (
            <TextInput  {...this.props}
                multiline={true}
                onChange={this.onChange}
                onContentSizeChange={this.onContentSizeChange}
                style={[styles.textInputStyle, {height: Math.max(35, this.state.height)}]}
                value={this.state.text}/>
        );
    }
}

  • 结果分析:当检测到文本框内容布局变化时,我们便会将获取到的高度置给TextInput组件,该方法已经兼容了删除时的操作

react native to web的实现方案

  • rn-to-web中只实现了高度变化时会触发onContentSizeChange,但是没有实现内部的逻辑event.nativeEvent属性是不存在的,所以我们要通过类似方案二的解决办法,获取到原生的scrollHeight属性
  • ????分析
render() {
        return (
            <TextInput  {...this.props}
                multiline={true}
                onChange={this.onChange}
                onContentSizeChange={event => {
                  const node = this.input._node
                if (node) {
                    node.style.height = 'inherit'
                    const height = node.scrollHeight
                    node.style.height = `${height}px`
                    this.setState({ height })
                  }
               }}
                style={[styles.textInputStyle, {height: Math.max(35, this.state.height)}]}
                value={this.state.text}/>
        );
    }

  • 结果分析:this.input._node获取到的是原生的dom节点,因此里面采用和是方案二同样的处理方式,
  • 基本原理和方案二相同,但是由于onContentSizeChange的触发时机本就是在高度变化的时候,所以react nativereact native to web的这两种实现方式均不需要进行优化处理

源码参考:

https://github.com/yyn7/study-demo/tree/master/1.%E9%AB%98%E5%BA%A6%E8%87%AA%E9%80%82%E5%BA%94%E7%9A%84%E8%BE%93%E5%85%A5%E6%A1%86

写在最后:

第一篇分享文章,感觉写的略有点啰嗦,比较适合新手阅读,后面会逐渐改进,大家有什么想法和优化思路欢迎来交流啊,一起努力成为合格的前端工程师!
最后的最后 附上wuli超级无敌可爱的琪琪

高度自适应输入框的清晰解题思路

相关标签: 技术