ToB企服应用市场:ToB评测及商务社交产业平台
标题:
uniapp之app、微信小程序实现输入框发送消息键盘不收起、解决iOS端键盘弹起
[打印本页]
作者:
立山
时间:
2024-6-19 19:13
标题:
uniapp之app、微信小程序实现输入框发送消息键盘不收起、解决iOS端键盘弹起
1. 效果
微信小程序:
iOS app
2. 配景
公司产品近来提了个bug需求,聊天界面在发送一次消息后,键盘会收起,
渴望
是:
点击发送消息后,键盘不收起
。得到这个需求时,组长就跟我说过这个需求欠好做,真的做了后才发现随处是坑,断断续续做了4天,网上资料也找了并试了一堆,都是按下葫芦起了瓢,就Android端的APP符合要求,第4天预备下班了,突然就找到思路了,终极四个场景都符合要求,至于差别机型差别体系版本是否还存在什么问题,就没那条件去试了。
我们这个uniapp项目是app、微信小程序两头的,聊天界面用的是某IM产品提供uniapp版本的demo代码,不知道是技术问题没做好,还是太久没去更新了,修改时问题不断,iOS原生版本是挺符合需求的。(
最坑的一点是
:消息列表组件用的是
view元素
,但元素里却挂了一堆
scroll-view元素
的属性,界面跳转至最新一条消息也是用scroll-view的属性实现。属实把我整迷糊了,官方文档也翻了好几遍也没找到对应属性,搞得我还在猜疑是不是view的某些埋伏属性,但官方没列出来。让我多进了几个坑,
后面的解决方案也是从这方面入手
)
3. 主要遇到问题及尝试失败的解决
(1)小程序端使用设置focus属性的方式去处理,出现闪动。
这个方案每发送一次消息,都会有
键盘收起再弹起
的情况,这种方案被否定。
(2)iOS键盘遮挡输入框、消息列表最新几条消息。
ios键盘弹起时是符合要求的(输入框跟随弹起、输入框上显示的也是消息列表的最新一条数据),发送一条消息后,输入框(用的是fixed)、消息列表都掉下去被键盘遮住,多发几条消息后,输入框会移上往返到正确位置,消息列表的消息虽然会往上滚动,但最新几条被键盘遮住。
主要就是在
输入框位置显示有问题
、
消息列表最新数据被键盘遮住(显示在手机屏幕底部
)、
发送消息时消息列表中的数据未向上滚动
这三个问题中反复摇摆。
列出部分尝试:
将adjust-position设置为false,通过uni.onKeyboardHeightChange监听键盘弹出得到键盘高度,从而动态给输入框设置bottom属性的值,给消息列表设置底部内边距。(
结果
:输入框位置能正确显示,但键盘弹起时向上滑动消息列表,输入框会跟着界面向上滑动;消息列表顶不上去,只会往下增长空缺页面)
为处理
第1步
中输入框会滑动的问题,尝试通过监听最外层元素的touchmove、touchstart事件,在事件触发时调用uni.hideKeyboard()将键盘收起。(
结果
:键盘弹出后的第1次滑动、点击时,事件没有触发,好像有层膜存在一样,输入框继续跟着上滑;再次滑动、点击,事件触发,键盘关闭)
4. 终极解决
主要是使用scroll-view元向来实现消息列表界面,输入框使用fixed定位
。之前虽然在demo代码的view上看到一堆scroll-view元素的属性,也猜疑对方之前用的是scroll-view,但为了通过onPullDownRefresh实现下拉加载历史消息功能,而改成了view。这我也能理解,毕竟使用scroll-view,onPullDownRefresh就无法被触发,也没去细研究scroll-view中的相关属性,毕竟公司的主要项目是在PC端,app、微信小程序能用就行,会uniapp的根本语法就够用了(吐槽一下,被这些问题折磨的不轻)。
言归正传,主要要实现的是:
1、小程序发送消息后,键盘不收起,也不接收键盘收起再弹起
。
2、iOS键盘弹起时输入框不被遮住、消息列表能看到最新一条
。
目的1解决:
在小程序中将textarea的hold-keyboard设置为true。
此时能实现目的1要求,但还需要保留点击输入框、发送按钮之外的地方,键盘收起功能。
给最外层元素、输入框、发送按钮分别绑定touchend事件,在点击界面中时,最外层的事件会触发(handleTouchEnd),此时调用uni.hideKeyboard()将键盘收起。
假如点击的是输入框、发送按钮,它两的事件的触发会先于最外层元素的事件的触发,在它两事件触发时,通过一个标志holdKeyboardFlag变量,让uni.hideKeyboard()不执行即可。
为什么用touchend事件,而不消touchstart等其他事件,这是因为@touchend.prevent="sendMessage"可以使输入框在点击发送后,输入框重新得到核心并且不会出现闪动(虽然只对app、h5有用,对小程序无效)。为了不再增长额外的处理,就统一用touchend。
目的2解决:
view元素更换成scroll-view元素后就能实现目的要求。但这样会出现无法下拉加载历史消息的问题,所以要再去实现下拉加载功能。实当代码,如下图:
还有界面滚动至最新一条消息时的一个小坑就不写了,直接看示例代码,项目的代码是分了好几个组件,示例代码主要展示的是核心实现。样式什么的能用就行。
以下是重新整理后的示例代码:
<template>
<view class="page" @touchend="handleTouchEnd">
<!-- 消息列表区域 -->
<view class="area-msglist">
<!--
用于替代实现onPullDownRefresh()效果
:refresher-enabled="true"
:refresher-threshold="50"
refresher-default-style="white"
:refresher-triggered="isFresh"
refresher-background="#FAFAFA"
@refresherrestore="onRestore"
@refresherrefresh="scrollRefresh"
:enable-back-to-top="true"
-->
<scroll-view
class="msglist"
:refresher-enabled="true"
:refresher-threshold="50"
refresher-default-style="white"
:refresher-triggered="isFresh"
refresher-background="#FAFAFA"
@refresherrestore="onRestore"
@refresherrefresh="scrollRefresh"
:enable-back-to-top="true"
:scroll-y="true"
upper-threshold="-50"
:scroll-into-view="intoViewId"
:scroll-with-animation="true">
<!-- 具体信息代码 -->
<!-- mid示例:msg1234545 -->
<view class="message" v-for="item in chatMsgList" :key="item.mid" :id="item.mid">
<view>{{item.msg}}</view>
</view>
</scroll-view>
</view>
<!-- 输入框区域 -->
<view class="area-input">
<!-- 发送语音 -->
<view>
<image class="icon-mic" src="/package-user/static/voice.png" />
</view>
<!-- 输入框 -->
<textarea
class="textarea"
type="text"
cursor-spacing="65"
confirm-type='done'
v-model="inputMessage"
@confirm="sendMessage"
@touchend="handleNoHideKeyboard"
:confirm-hold="true"
auto-height
:show-confirm-bar='false'
:hold-keyboard="holdKeyboard"
maxlength="300"
/>
<!-- 表情 -->
<view>
<image class="icon-mic" src="/package-user/static/Emoji.png" />
</view>
<!-- 更多 -->
<view v-show="!inputMessage">
<image class="icon-mic" src="/package-user/static/ad.png" />
</view>
<button
v-show="inputMessage"
class="send-btn"
hover-class='hover'
@touchend.prevent="sendMessage"
>发送</button>
</view>
</view>
</template>
<script>
export default {
name: 'ChatDemo',
data() {
return {
isFresh:false, // 设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发
freshing:false,
intoViewId: "",
chatMsgList: [],
inputMessage: "",
holdKeyboard: false, // focus时,点击页面的时候不收起键盘
holdKeyboardFlag: true, // 是否在键盘弹出,点击界面时关闭键盘
}
},
created() {
// 针对小程序键盘收起问题处理
// #ifdef MP-WEIXIN
this.holdKeyboard = true
// #endif
},
mounted() {
this.init()
},
methods: {
init() {
for (let i = 0; i < 12; i++) {
this.chatMsgList.push({
mid: `msg${i}`,
msg: `消息${i}`
})
}
this.refreshMsg()
},
// 针对小程序键盘收起问题处理
handleTouchEnd() {
// #ifdef MP-WEIXIN
clearTimeout(this.timer)
this.timer = setTimeout(() => {
// 键盘弹出时点击界面则关闭键盘
if (this.holdKeyboardFlag) {
uni.hideKeyboard()
}
this.holdKeyboardFlag = true
}, 50)
// #endif
},
// 自定义下拉刷新被触发
scrollRefresh(){
if (this.freshing) return;
this.freshing = true;
this.isFresh = true;
this.$emit('refresh');
this.getHistoryMsg()
},
// 自定义下拉刷新被复位
onRestore(){
this.isFresh = false
},
// 获取历史数据
getHistoryMsg() {
const mid = ''
this.handleScrollIntoView(mid)
},
// 刷新界面数据
async refreshMsg() {
const mid = this.chatMsgList[this.chatMsgList.length - 1].mid // 目标元素id
// 跳到最后一条
this.handleScrollIntoView(mid)
},
sleep(num = 1, step = 50) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, num * step)
})
},
// 查找目标元素
querySelectEl(markers) {
const that = this
return new Promise((resolve) => {
uni.createSelectorQuery().in(that).select(markers).boundingClientRect((container) => {
console.log(container, 888);
const flag = container ? true : false
resolve(flag)
}).exec()
})
},
// 滚动至目标元素位置
async handleScrollIntoView(intoViewId) {
let flag = false
for (let i = 0; i < 20; i++) {
flag = await this.querySelectEl(`#${intoViewId}`)
if(flag) {
break
}
await this.sleep()
}
// 在查询到目标元素存在后,再进行滚动操作
// 直接用nextTick在iOS端是可以的;但在android端界面重新渲染时,首次可能找不到符合id的元素,导致未能正确滚动
if (!flag) return
this.$nextTick(() => {
this.intoViewId = intoViewId
})
},
// 点击输入框、发送按钮时,不收键盘
handleNoHideKeyboard() {
// #ifdef MP-WEIXIN
// this.$emit('noHideKeyboard')
this.holdKeyboardFlag = false
// #endif
},
// 发送消息
sendMessage() {
this.handleNoHideKeyboard()
// 发送消息代码
this.chatMsgList.push({
mid: `msg${this.chatMsgList.length}`,
msg: `新消息${this.inputMessage}`
})
this.inputMessage = ''
this.refreshMsg()
},
}
}
</script>
<style lang="scss" scoped>
.page {
width: 100%;
height: 100%;
}
.area-msglist {
width: 100vw;
height: 100vh;
.msglist {
background-color: #FAFAFA;
height: calc(100vh - 80rpx);
}
}
.message {
padding: 20rpx;
margin-top: 40rpx;
line-height: 3;
text-align: end;
}
/* 解决小程序和app当前界面滚动条不出现的问题 */
::v-deep ::-webkit-scrollbar {
/*滚动条整体样式*/
width: 5px !important;
height: 1px !important;
overflow: auto !important;
background: #ccc !important;
-webkit-appearance: auto !important;
display: block;
}
::v-deep ::-webkit-scrollbar-thumb {
/*滚动条里面小方块*/
border-radius: 10px !important;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2) !important;
background: #ccc !important;
}
::v-deep ::-webkit-scrollbar-track {
/*滚动条里面轨道*/
background: #FFFFFF !important;
}
.area-input {
width: 100%;
height: auto;
position: fixed; // 用的是fixed布局
bottom: 0;
right: 0;
z-index: 1;
padding: 0;
display:flex;
align-items:center;
background-color: #f2f2f2;
}
.icon-mic{
width: 22px;
height: 22px;
padding: 5px 10px;
position: relative;
top: 2px;
}
.textarea {
width: 100%;
font-size: 14px;
padding: 0 10px;
display: inline-block;
margin: 10rpx;
line-height: 48rpx;
position:relative;
top: 0;
background-color: #fff;
border-radius: 16px;
flex: 1;
max-height: 200rpx;
min-height: 60rpx;
}
.send-btn {
font-size: 10px;
background-color: #2196F3;
color: #fff;
margin-right: 10px;
}
</style>
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4