Android键盘弹出时,动态添加元素导致底部输入框被遮挡的聊天布局问题求助
大家好,我现在遇到一个聊天布局的棘手问题,想请教各位大佬帮忙看看!
问题现象
我做了一个从下往上堆叠消息的聊天界面(最新消息在最底部),用Tailwind实现了布局。但当动态显示“typing...”提示框时,底部的输入框会被Android键盘挤下去,导致部分被遮挡,就像这样:
我需要保证输入框固定在原本的位置,同时“typing...”提示要从上到下出现(也就是新内容在消息列表的视觉上方添加)。我已经试了各种办法:把justify-end换成margin-top:auto、用flex-col-reverse、甚至旋转180度,结果都一样。还试过把typing提示直接放进循环的消息数组里,这种方式只在部分场景有效——比如对方发消息时没问题,但用户自己发消息后再显示typing,还是会出现同样的遮挡问题。
我的疑问
为什么会出现这个情况?为什么用@for循环添加元素时完全正常,用@if条件动态添加就出问题?而且如果消息是从上到下排列(不用justify-end)就完全没这个问题,反过来布局就出bug?
如果有其他实现“消息从下往上堆叠”的布局方案,不会触发这个遮挡问题的,也请告诉我!不管是原生CSS、JS还是Tailwind的方案都可以,万分感谢!
复现步骤
- 先确保“typing...”提示框处于隐藏状态
- 打开Android键盘
- 触发显示“typing...”提示框,此时就能看到输入框被推下去部分遮挡的问题
附带小问题
我把输入框type设为search时,键盘右下角显示的是搜索图标;改成text类型后,输入框顶部又会弹出密码管理栏(就是常见的保存密码提示)。有没有办法让键盘右下角显示“提交箭头”,同时又不弹出密码管理栏?
我的代码实现
app.component.html
<div class="flex flex-col h-dvh bg-yellow-300"> <header class="fixed top-0 h-10 border w-full border-blue-600"> Banner </header> <div class="my-10 h-full flex flex-col-reverse overflow-y-auto"> @if (showElement) { <div class="bg-yellow-100 text-xs">...typing</div> } @for (message of messages(); track $index) { <div>{{ message }}</div> } </div> <div class="flex bottom-0 fixed border-2 h-10 border-red-500 w-full"> <input type="search" autocomplete="off" class="border-2 border-green-500 grow normal-text"> </div> </div>
app.component.ts
import { isPlatformBrowser } from '@angular/common'; import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID, signal } from '@angular/core'; @Component({ selector: 'app-root', imports: [], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent implements OnInit, OnDestroy { showElement = false; messages = signal<string[]>([]); intervalId: any; intervalId2: any; counter = 1; constructor(@Inject(PLATFORM_ID) private platformId: Object) {} ngOnInit(): void { if(isPlatformBrowser(this.platformId)) { this.intervalId = setInterval(() => { this.messages.set(['test'+this.counter++, ...this.messages()]); }, 500); setInterval(() => { this.intervalId2 = this.showElement = !this.showElement; }, 1000); // Toggles every second } } ngOnDestroy() { if (this.intervalId) { clearInterval(this.intervalId); // Cleanup to prevent memory leaks } if (this.intervalId2) { clearInterval(this.intervalId2); // Cleanup to prevent memory leaks } } }
app.component.scss
.normal-text { -webkit-appearance: none; appearance: none; -moz-appearance: none; }
styles.scss
/* You can add global styles to this file, and also import other style files */ @tailwind base; @tailwind components; @tailwind utilities;
更新:尝试滚动修复但未解决
后来按照建议,在显示typing提示时自动滚动消息容器到底部,但输入框还是会被推下去遮挡,演示如下:
更新后的代码:
app.component.ts
import { isPlatformBrowser } from '@angular/common'; import { Component, ElementRef, Inject, OnDestroy, OnInit, PLATFORM_ID, Renderer2, signal, ViewChild, viewChild } from '@angular/core'; @Component({ selector: 'app-root', imports: [], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent implements OnInit, OnDestroy { // 获取消息容器的DOM引用 @ViewChild('messageContainer') messageContainer: ElementRef | undefined = undefined; showElement = false; messages = signal<string[]>([]); intervalId: any; intervalId2: any; counter = 1; constructor( @Inject(PLATFORM_ID) private platformId: Object, private renderer: Renderer2 ) {} ngOnInit(): void { if(isPlatformBrowser(this.platformId)) { this.intervalId = setInterval(() => { this.messages.set(['test'+this.counter++, ...this.messages()]); }, 500); setInterval(() => { this.intervalId2 = this.showElement = !this.showElement; if(this.showElement) { if(this.messageContainer) { // 滚动容器到底部(因为用了flex-col-reverse,所以scrollTop设为0) let elem = this.renderer.selectRootElement(this.messageContainer)?.nativeElement; elem.scrollTop = 0; } } }, 1000); } } ngOnDestroy() { if (this.intervalId) { clearInterval(this.intervalId); } if (this.intervalId2) { clearInterval(this.intervalId2); } } }
app.component.html
<div class="flex flex-col h-dvh bg-yellow-300"> <header class="fixed top-0 h-10 border w-full border-blue-600"> Banner </header> <!-- 滚动这个容器 --> <div class="my-10 h-full flex flex-col-reverse overflow-y-auto" #messageContainer> @if (showElement) { <div class="bg-yellow-100 text-xs">...typing</div> } @for (message of messages(); track $index) { <div>{{ message }}</div> } </div> <div class="flex bottom-0 fixed border-2 h-10 border-red-500 w-full"> <input type="search" autocomplete="off" class="border-2 border-green-500 grow normal-text"> </div> </div>
另外,我在Chrome远程调试里发现,可能是容器的尺寸发生了变化,演示如下:
备注:内容来源于stack exchange,提问作者Sidharth Bajpai




