autoresize.gif

>> 前往stackblitz编辑代码

核心思路

textarea-autosize.jpg

  1. 创建两个textarea,这里暂取名为text和text1。(最后会将text1隐藏,调试时先让text1显示)。
  2. 将text1的高度和rows设置为仅能输入一行,这么做是为了用元素的scrollHeight表示其内容的高度。
  3. 用户将在text中输入,我们将输入的值同步绑定到text1中,并通过text1的scrollHeight获取输入内容的高度,并同步改变text的height。

实现

准备工作

  1. 首先,新建一个模块textarea.module.ts,并引入FormsModule,因为接下来将会用到ngModel进行双向数据绑定。
  2. 在模块内新建一个组件textarea。
  3. 在模块内exports出该组件。以后只需引入该模块即可使用该组件。

实作

  1. 在组件模板内写两个textarea,并标记为模板变量#text和#text1。
  2. 在模板中数据绑定,并监听数据变化。

    <textarea (ngModelChange)="onChange()" [(ngModel)]="val" #text class="autosize"  rows="1"></textarea>
    <textarea class="autosize hidden"  rows="1" [value]="val" #text1></textarea>
  3. 在textarea.component.ts中增加一个输入属性和一个输出属性。输入属性maxHeight表示textarea的heigh的极限。输出属性valChange将会在用户输入的数据变化时发出数据。

     @Input('max-height') maxHeight = 100;
     @Output('valChange') valChange = new EventEmitter();
  4. 在textarea.component.ts中写模板中调用的onChange方法。让text的高度始终等于text1的scrollHeight;这里是直接操作Dom,建议最好使用Renderer2进行dom的修改。

     onChange() {
    this.reset();
    setTimeout(() => {
      this.valChange.emit(this.val);
      this.reset();
    }, 0)
    
    }
    reset() {
        this.text1.nativeElement.style.width = (this.text.nativeElement.scrollWidth + 2) + 'px';
        if (this.text1.nativeElement.scrollHeight < this.maxHeight) {
          this.text.nativeElement.style.height = (this.text1.nativeElement.scrollHeight + 2) + 'px'
        }
    }

    注意1:这里获取scrollwidth的目的是因为不同的浏览器对滚动条的呈现逻辑有差异,我们在css中已经设置了text1的overflow=hidden,始终不会让text1出现滚动条,因此我们需要让他的宽度始终等于text1的宽度,以保证当text出现滚动条是他的的宽度也保持一致,从而让scrollHeight可以完美映射到text,否则会出现text中明明还没有达到边界,高度就自行变化了。
    注意2:setTimeout中的逻辑是为了应付事件环,因为我们监听的是text的变化,当text中输入变化时,text1中通过数据绑定得到的值往往还没有改变,需要等一个节拍。

使用

  • 只需要监听输出属性valChange,并传入$event就可以获取用户输入了。
  • 如有需要可以在此基础上继续扩展,使其兼容响应式表单。

    <app-yu-textarea  (valChange)="onChange($event)" max-height='100' class="tex"></app-yu-textarea>
  • 修改样式需要注意选择器的权重。

野生爬山虎
134 声望6 粉丝

野生前端玩家