前几天做了一个类似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>javascript">
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.value
if (!inputValue) return
let oDiv1 = document.createElement('p')
let oDiv2 = document.createElement('p')
oDiv1.className = 'right'
oDiv1.innerText = inputValue
oDiv2.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代码如下
javascript">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 = undefined
const onInputFocus = () => {
isFirstFocusFlagElHeight === undefined && (isFirstFocusFlagElHeight = Math.abs(oFlag.getBoundingClientRect().top))
let insetPlaceholderHeight = isFirstFocusFlagElHeight - oContent.offsetHeight
if (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 = 0
oInput.blur()
}
}
containerEl.addEventListener('touchstart', onContainerElTouchstart)
}
或者如果你的初始内容足够多,那你也不需要考虑这个问题
这里需要注意的时我的insetPlaceholder
方法时需要一个特定的html结构的,需要这样的结构
<div class="container">
<div class="flag"></div>
<div class="content"></div>
</div>