前几天做了一个类似qq布局的h5的聊天界面,输入框固定在最底下。本来初始情况会有默认的两条聊天记录,但是当点击底部的输入框时,输入框聚焦,弹起键盘,然后整个界面就被顶上去了,然后那两条默认的聊天记录也被顶上去了,导致整个页面没内容了。
这里给出一个demo(如果样式有问题,请将浏览器的模拟器调到iphone 6/7/8)
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport"content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no,viewport-fit=cover" /><title>Document</title><style>* {margin: 0;padding: 0;}html,body {width: 100%;height: 100%;overflow: hidden;background-color: pink;}p {margin-bottom: 1.875rem;}.left {text-align: left;}.right {text-align: right;color: blue;}.chatContainer {height: 100%;overflow: auto;}.inputBox {display: flex;position: fixed;bottom: 0;left: 0;right: 0;height: 1.875rem;z-index: 999;}input {flex: 1;margin-right: .5rem;}button {width: 6.25rem;height: 100%;}</style>
</head><body><div class="chatContainer"><div class="flag"></div><div class="chat"><p class="left">你好,我是XXX,一个机器人</p><p class="left">请问您需要问什么</p></div></div><div class="inputBox"><input type="text" /><button>发送</button></div><script>let oBtn = document.querySelector('button')let oChatContainer = document.querySelector('.chatContainer')let oInput = document.querySelector('input')let oFlag = document.querySelector('.flag')oBtn.onclick = () => {let inputValue = oInput.valueif (!inputValue) returnlet oDiv1 = document.createElement('p')let oDiv2 = document.createElement('p')oDiv1.className = 'right'oDiv1.innerText = inputValueoDiv2.className = 'left'oDiv2.innerText = '感谢您的回复!'oChatContainer.appendChild(oDiv1)oChatContainer.appendChild(oDiv2)oDiv2.scrollIntoView({behavior: "smooth"})oInput.value = ''}</script>
</body></html>
感觉这个好像并没有什么问题,安卓可以正常的显示,交互。但是IOS就是不行,并且我还设置了
.inputBox {display: flex;bottom: 0;z-index: 999;}
让我百思不得其解,,最后发现这是IOS手机的通病,没办法,只能进行hack了。
我的最终解决思路是,既然聚焦会定顶起内容区域,那么顶起来多少,我就用空白的元素占位多少高度,这样内容就显示出来了。当手指在屏幕滑动时,再把占位块高度变为0。如果内容区域的高度大于了被顶起的高度(或者内容区域的高度多出了被顶起的高度20px之类的),那么就不需要占位了,因为这个时候肯定有内容区域的元素露出来了。
最终的js代码如下
insetPlaceholder(oChatContainer, oInput)
function insetPlaceholder(containerEl, inputEl) {// 只有苹果手机会导致输入框聚焦顶底整个内容区域,所有这个函数只对IOS系统进行兼容处理let isIos = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);if (!isIos) return/*** 这里这个是一个死的元素结构* 类似这样* <div class="container"><div class="flag"></div><div class="content"></div></div>* container是整体,flag是占位元素,根据情况切换高度* content是实际的内容区域*/let oFlag = containerEl.children[0]let oContent = containerEl.children[1]let containerElTouchstartIsRemove = false/*** 记录第一次聚焦的时候,container这个大盒子究竟被顶起来多少,把这个值记录为flag的高度* 以后每次聚焦都会和这个值做比较,如果content的高度大于了顶起来的高度,那么说明内容已经足够多了,所以就不需要flag来占位了,然后把 flagEl 高度设置为0*/let isFirstFocusFlagElHeight = undefinedconst onInputFocus = () => {isFirstFocusFlagElHeight === undefined && (isFirstFocusFlagElHeight = Math.abs(oFlag.getBoundingClientRect().top))let insetPlaceholderHeight = isFirstFocusFlagElHeight - oContent.offsetHeightif (insetPlaceholderHeight > 0) {setTimeout(() => {oFlag.style.height = insetPlaceholderHeight}, 3e2)} else {if (!containerElTouchstartIsRemove) {containerEl.removeEventListener('touchstart', onContainerElTouchstart)containerElTouchstartIsRemove = true}}}oInput.addEventListener('focus', onInputFocus)/*** 监听这个事件主要是为了隐藏那个占位元素* 防止用户滑动界面把占位元素露出来* 并且很奇怪,如果不这样做,输入框会滚动!并且我已经设置了固定定位!* 如果占位元素没有了,那就可以把这个事件移除了* */const onContainerElTouchstart = () => {if (oFlag.style.height > 0) {oFlag.style.height = 0oInput.blur()}}containerEl.addEventListener('touchstart', onContainerElTouchstart)
}
或者如果你的初始内容足够多,那你也不需要考虑这个问题
这里需要注意的时我的insetPlaceholder
方法时需要一个特定的html结构的,需要这样的结构
<div class="container"><div class="flag"></div><div class="content"></div>
</div>