动态扩展输入宽度以适应字符串长度

9

我想创建一个输入框,输入的字符串长度变化时,该输入框至少在宽度上动态扩展,甚至可以支持多行输入。

在Angular Material 2中,使用input元素是否可以实现这个功能?

使用Angular Material 2中的textarea字段,我只能通过以下代码扩展文本框的高度,无法扩展其宽度:

<mat-form-field (keydown.enter)="onEnter()" 
                floatPlaceholder="never">
  <textarea matInput 
            placeholder="Autosize textarea" 
            matTextareaAutosize
            matAutosizeMinRows="1" 
            matAutosizeMaxRows="8">
  </textarea>
</mat-form-field>

在使用

1
你是否一定需要使用一个输入框?我建议的解决方案是使用一个内联块级div,通过JavaScript实现类似于输入框的功能。这样它就可以随着内容的增加而扩展。 - Jack Dalton
可能这样做可以解决问题,但我肯定不是第一个尝试这个的人(虽然我在stackoverflow上找不到这个问题)。我只是有一种感觉,应该有一个更简单的选项,比如扩展textarea的高度。 - Night Train
1
我建议在一个span上使用“contenteditable”是避免错误和确保浏览器支持的最佳选择。我看不出使用输入dom元素的优势在哪里。 - Jack Dalton
好的,我会尝试一下!但也许可以用Angular来实现这个? - Night Train
我在这个stackoverflow帖子https://dev59.com/8msz5IYBdhLWcg3wHUS0#34224563中发现了一个相当棒的可扩展输入框,唯一的缺点是它使用了jQuerry。这里是一个jFiddle示例http://jsfiddle.net/kjxdr50a/。 - Night Train
4个回答

7

简单且唯一的模板解决方案:

<mat-form-field
  [ngStyle]="{'width.ch': inputText.value.length, 'min-width.ch': 10}"> 
  <input  #inputText  matInput>
</mat-form-field>

1
这甚至可以不使用 mat-form-field 实现。ngStyle 可以应用于输入本身,非常棒的答案! - Emil Hovhannisyan

6

您可以使用ngStyle将mat-form-field的宽度绑定到一个计算出的值,并使用输入事件在输入时设置该值。例如,这里有一个输入框,它的宽度跟随文本宽度超过64px:

<mat-form-field [ngStyle]="{'width.px': width}">
    <input #elasticInput matInput (input)="resize()">
</mat-form-field>
<span #hiddenText style="visibility: hidden; white-space: pre;">{{elasticInput.value}}</span>

export class InputTextWidthExample {

    @ViewChild('hiddenText') textEl: ElementRef;

    minWidth: number = 64;
    width: number = this.minWidth;

    resize() {
        setTimeout(() => this.width = Math.max(this.minWidth, this.textEl.nativeElement.offsetWidth));
    }
}

显然,这个例子使用了隐藏的 span 元素来获取文本宽度,这有点取巧。当然,计算字符串宽度的方法肯定不止一种,包括这个这里是 Stackblitz 上的示例

我把 keyup 改成了 keydown。这样流畅度会更好一些。单位 ch 表示字符 0 的宽度。https://developer.mozilla.org/en-US/docs/Web/CSS/length。也许没有别的办法除了用 JS 来测量宽度... - Night Train
1
有方法可以测量文本宽度,这里有一个。由于 keydown 事件尚未更新输入的值字符串,因此您需要将事件字符附加到值字符串以进行测量,并且为了正确执行此操作,您需要区分退格等控制字符和实际文本以及空格。因此,有许多小事情需要担心,使得这个过程有些繁琐,但它可以工作。 - G. Tranter
这太棒了!在我的情况下,可能只需要设置:min-width: 30vw; width: 30vw; max-width: 98vw; text-align: center; 但这样更好!! - Night Train

1

我需要在mat-table(动态大小)中为matInput提供动态宽度,并尝试了几种基于G.Tranter的解决方案的方法。然而,根据呈现的表格单元格数量查看动态数量的<span>元素是不切实际的。因此,我想出了一个相当实用的解决方案:

每当数据更新时,我创建一个<span>元素,将其填充为更新后的数据,将其添加到HTML body(或我的情况下的div),提取<span>offsetWidth并立即从HTML文档中删除它。在测试过程中,我没有观察到任何闪烁或渲染问题(虽然我认为这是可能的)。

模板

...

<!-- col1 Column -->
    <ng-container matColumnDef="col1">
      <th *matHeaderCellDef mat-header-cell mat-sort-header scope="col"> ifPattern</th>
      <td *matCellDef="let dataRecord;" mat-cell>

        <mat-form-field [ngStyle]="{'width.px': columnWidth[0]}" floatLabel="never">

          <input (input)="resize(0, dataRecord.col1)" [(ngModel)]="dataRecord.col1"
                 [value]="dataRecord.col1" matInput placeholder="col1 value">

        </mat-form-field>

      </td>
    </ng-container>
...

调整大小方法

displayedColumns: string[] = ["col1", "col2", "col3"];
minWidth: number = 64; // pixel
columnWidth: number[] = [this.minWidth, this.minWidth, this.minWidth];

  resize(column_index: number, value: string) {
    setTimeout(() => {
      let span = document.createElement('span');
      span.innerText = value;
      let div = document.getElementById('case-cleaning-overview')
      div?.append(span);
      this.columnWidth[column_index] = Math.max(this.minWidth, span.offsetWidth);
      div?.removeChild(span);
    });
  }

1
我现在为这个问题创建了一个更加合适的解决方案。在我找到了jQuery中的完美解决方案并且@Obsidian添加了相应的JS代码之后。我尝试将其适应于Angular input,最终得出了以下结果。 我还添加了一些支持字符串剪切和粘贴的场景。 这里是在StackBlitz上的演示以及相应的代码: 模板:
<style>
    #invisibleTextID {
      white-space: pre;
    }

    // prevents flickering:
    ::ng-deep .mat-form-field-flex {
      width: 102% !important;
    }
</style>

<mat-form-field
  #formFieldInput
  [ngStyle]="{'width.px': width}">

  <input
    #inputText
    id="inputTextID"
    matInput
    [(ngModel)]="inString"
    (paste)="resizeInput()"
    (cut)="resizeInput()"
    (input)="resizeInput()">

</mat-form-field>

<span #invisibleText id="invisibleTextID">{{ inString }}</span>

调整大小方法:

@ViewChild('invisibleText') invTextER: ElementRef;

inString: string = '';
width: number = 64;

resizeInput() {

    // without setTimeout the width gets updated to the previous length
    setTimeout ( () =>{

      const minWidth = 64;

      if (this.invTextER.nativeElement.offsetWidth > minWidth) {
        this.width = this.invTextER.nativeElement.offsetWidth + 2;
      } else {
        this.width = minWidth;
      }

    }, 0)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接