uniapp微信小程序仿聊天 输入框在软键盘隐藏显示时与底部距离,以及保证聊天消息滚动到底部 的问题以及解决办法
uniapp 仿微信聊天,输入框与键盘的问题,以及保证聊天一直滚动到底部
(本文最后边有完整代码,想先看效果的,可以直接粘贴不会报错)
一、先说输入框
1、输入框和键盘紧贴着,也就是计算 输入框在键盘隐藏显示时 距离底部的距离,我让输入框外边的盒子固定定位,给bottom一个变量,给盒子绑定行间 :style="{ bottom: bottom }",简易代码如下,主要是 css 代码和 js 代码
<view class="intelligent_input" :style="{ bottom: bottom }">
// <input placeholder="请输入…" :adjust-position="false" v-model="value"></input> 输入框下边有详细介绍的代码,这里输入框的内容先省略,主要看外边盒子的代码
// <view> <text>发送</text> </view> 简易模拟,在本篇文章下边有输入框以及template的详细代码,主要看外边盒子代码以及布局格式
</view>
js代码
onLoad() {
uni.onKeyboardHeightChange((res) => { //监听键盘高度变化
const res_keyboard = uni.getSystemInfoSync();
let key_height = res.height - (res_keyboard.screenHeight - res_keyboard.windowHeight + res_keyboard.safeAreaInsets.bottom)
this.bottom = `${ key_height ? key_height : 0}px`;
})
},
onHide() {
uni.offKeyboardHeightChange(); //取消监听键盘高度变化事件,避免内存消耗
},
css代码
.intelligent_input {
width: 100%;
height: 100rpx;
min-height: 98rpx;
background: #F7F7F7;
position: fixed;
bottom: 0; // 这里可写可不写,上边盒子上的变量给的初始值是0px,因为等会计算的高度是px,所以就给的px单位
padding-bottom: constant(safe-area-inset-bottom); // 苹果底部的安全距离
padding-bottom: env(safe-area-inset-bottom); // 苹果底部的安全距离
display: flex;
box-sizing: content-box;
}
总结 上边表达的 核心代码
1、// 这个加在输入框外边包的元素上 :style="{ bottom: bottom }",bottom的初始值给 ‘0px’
:style="{ bottom: bottom }"
2、 js 的核心代码
onLoad() {
uni.onKeyboardHeightChange((res) => { //监听键盘高度变化
const res_keyboard = uni.getSystemInfoSync();// res_keyboard.safeAreaInsets.bottom 底部的安全距离,是个负数
// 这个key_height 是我计算出的底部高度,你自己的可以看着计算,只要凑上就可以,我这也是凑上的,具体每一项我也不是很懂
let key_height = res.height - (res_keyboard.screenHeight - res_keyboard.windowHeight + res_keyboard.safeAreaInsets.bottom)// px是因为每个数字本来就是px单位,不用担心跟rpx相违背之类的
this.bottom = `${ key_height ? key_height : 0}px`;
})
},
onHide() {
uni.offKeyboardHeightChange(); // 取消监听键盘高度变化事件,避免内存消耗
},3、 css的核心代码
position: fixed;
bottom: 0;// 下边这两行:设置苹果手机底部安全距离
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
这里写完输入框和键盘已经保证距离差不多了
2、如果你想像微信一样,保证点击发送之后,软键盘不收起,这三个有用
// 现在输入框写一个聚焦的变量fouse,fouse初始值给false,在聚焦事件里赋值 true
:focus="fouse"
// 绑定聚焦事件 @focus="i_focus",
@focus="i_focus"
// 这个加在输入框上边,hold_keyboard 初始值给false,在聚焦事件里赋值 true
:hold-keyboard="hold_keyboard"
// 这个把外边发送按钮按钮的@click事件换@touchend.prevent="input_send"
@touchend.prevent="input_send"
3、如果你的软键盘右下角和我的一样是发送功能,保证点击右下角键盘之后,软键盘不收起,这两个有用(跟上边类似,所以我绑定的都是同一个变量)
// 这个加在输入框上边,hold_keyboard 初始值给false,在聚焦事件里赋值 true
:confirm-hold="hold_keyboard"
// 同理,把input上的这个事件这样写 @confirm.prevent="input_send"
@confirm.prevent="input_send"
4、这样写完之后你会发现,点击软键盘以外的地方,键盘也不收起了,所以我给scroll-view标签加了两个事件,点击事件和滑动事件,绑定同一个方法,在方法里隐藏键盘这行码
uni.hideKeyboard()
// 点击事件 @click
@click="other_click"
// 滑动事件 @dragging
@dragging="other_click"
5、如果你想像微信一样,保证点击发送之后,软键盘不收起,这两个有用
// 这个加在输入框上边
:hold-keyboard="hold_keyboard"
// 这个把外边发送按钮按钮的@click事件换@touchend.prevent="input_send"
@touchend.prevent="input_send"
下边是上边的完整代码
template部分:
<view class="intelligent_input" :style="{ bottom: bottom }">//bottom输入框距底部的距离,计算出的
<input
type="text"
confirm-type="发送" // 软键盘右下角改文字,配合 type="text" 才有效
placeholder-style="font-size: 30rpx;color: #C8C8C8;" // 输入框placeholder样式
placeholder="请输入…"
:adjust-position="true " // true 禁止键盘显示时上推内容,默认是true
v-model="value"
@confirm.prevent="input_send" // 软键盘右下角事件,这里我绑定的和发送一样的功能
:focus="fouse" // 控制输入框聚焦,布尔
:hold-keyboard="hold_keyboard" // 保证点击发送之后,键盘不收起,布尔
:confirm-hold="hold_keyboard" // 保证点击软键盘右下角发送之后,键盘不收起,布尔
@focus="i_focus" // 输入框聚焦事件
@blur="blur" // 输入框失焦事件
></input>
<view class="input_send"
@touchend.prevent="input_send" //这里说明一下,一定要用@touchend.prevent 这个事件,要不键盘会收起,上面尽管写了也不起效
>
<image src="https://yiben01.oss-cn- hangzhou.aliyuncs.com/img/921c55e21e95bf68a5d32f4aeab37bc64d2317d0_100.png" mode=""> </image>
</view>
</view>
script部分
data里输入框用到的变量:{
value: '', // 输入框value
bottom: '0px', // 输入框距底部高度(键盘显示隐藏高度变化)
fouse: false, // 输入框聚焦
hold_keyboard: true, // 键盘不收起,
msg_list: [
{
left: true,
msg: '嗨~我是小伊,您身边的小助手~',
data: null
},
{
left: true,
msg: '请问有什么可以帮助您的?',
data: null
}
],
}
onShow,onHide的键盘监听和取消监听我就不重复写了,上边就有,css最主要代码也写了,这里就不写了
method:{
// 点击键盘以外的地方收起键盘 在 scroll-view 标签绑定的事件
other_click() {
uni.hideKeyboard()
},
// 输入框聚焦事件
i_focus() {
this.fouse = true;
this.hold_keyboard = true;
},
// 输入框失焦事件
blur() {
// 换id
// this.controlId = false //等会scroll-view要用到 这里先注释掉
},
// input发送事件
async input_send() {
if (!this.value.toString()) {
return
}
// 增加数组,将自己的话放进去,再将对方的回答放进去,这里模拟两个人对话,等会在scroll-view 里 v-for 出来 left代表左右对话,msg代表话,data是我的项目有网络强求,返回的数个数组,所以,数组没有的时候就置空,有就赋值
this.msg_list = [
...this.msg_list,
{
left: false,
msg: this.value,
data: null
},
{
left: true,
msg: '没有找到该问题答案',
data: null
}
]
}
}
二、接下里说容器scroll-view的一些问题,以及保证聊天输入时一直滚动到底部的问题
如果你用的view标签,就把view标签换成scroll-view标签,如果你样式已经写好了,换了之后也不影响(原因:scroll-view标签有它自己的好处哈哈,其实是用到scroll-into-view这个属性很关键)
先说一下保证滚动到底部的简单代码
<scroll-view :scroll-y="true" style="height: 600rpx;"
:scroll-into-view="bottomId" :scroll-with-animation="true">
<view v-for="(item,index) in msg_list">
<view>{{item.msg}}</view>
</view>
<view :id="'dade'+num">底部</view>
</scroll-view>
setTimeout(()=>{
this.num = this.msg_list.length
this.bottomId = 'dade'+ (this.num)
},500)
光写上边的代码,会出现一些问题,所以我在下图中才有了 controlId 来判断,具体可以往下看
下面可以先看着图片
说说上边图片里的变量
// 容器高度,设置成变量是因为这个是后期计算出来的
key_height: '0px', // 初始值// 容器滚动底部定位id,有了这个id就可以保证聊天滚动到底部
bottomId: '',// 定位id,设置这个是因为,我发现在键盘弹起隐藏的时候聊天可以保证滚动到最后面,但是只要我在页面上滑动,再在输入框输入的时候就不能保证聊天滚动到最后了,所以我替换了id,等输入框聚焦的时候就换成上边的bottomId,等输入框失去焦点的时候就换成这个别的id,所以在输入框的聚焦和失焦时间里控制一下这个变量的布尔,聚焦为true,失焦为false
controlId: false, // 用来控制切换id
// 数组长度,用到定位id
num: 2,// 防连点
boolen: true,
在created或者onLoad生命周期里,先给容器一个高度,也就是变量 key_height 给一个值
const res_s = uni.getSystemInfoSync();
this.key_height = `${res_s.windowHeight - 52}px` // 52 是我输入框的高度,单位记着是px,如果你输入框时104rpx,那就是52px。(注:输入框的高度是可以获取元素高度来计算的,我这里简写了,具体可以看本篇文章最后边的完整代码里)res_s.windowHeight是屏幕的可用总高度,就是除了NavigationBar的高度,所以拿上屏幕的可用总高度,减去输入框的高度,就是盒子的高度,因为刚开始输入框的软按键盘还没弹起,所以不用减键盘的高度
然后在监听键盘高度变化的函数里写上这三行代码
this.key_height = `${res_keyboard.windowHeight - 52 - key_height}px`; // key_height是上边输入框距离底部的高度(也就是软键盘的高度),在文章开头的时候中有
this.num = this.msg_list.length; //数组的length,实时更新length,保证scroll-into-view找到最后一个id
this.bottomId = 'dade' + (this.num); //最后的id scroll-into-view就是找对应的id,定位到找到的当前id,就保证了聊天滚动到最后
下边是整个页面的完整代码
直接粘贴不会报错
<template>
<view class="pages_height">
<view class="intelligent_main">
<scroll-view :scroll-y="true" :style="{height:key_height}"
:scroll-into-view="controlId ? bottomId : 'msg'+controlId" :scroll-with-animation="true"
class="intelligent_chat" @click="other_click" @dragging="other_click" :show-scrollbar="false">
<view v-for="(item, index) in msg_list" :key="index" :class="'intelligent_chat_view'+index">
<view :class="item.left ? 'robot_word' : 'patient_word'">
<view v-if="item.msg">
<view>
{{item.msg}}
</view>
</view>
<view v-else>
<view>
{{item.title}}
</view>
<view v-if="item.data">
<view v-for="(data, data_index) in item.data" :key="data.id">
<view class="keyHighlight" v-html="data.keyHighlight" v-if="data.keyHighlight"
@click="keyHighlight(data,data_index)"></view>
</view>
</view>
</view>
</view>
</view>
<view :id="'dade'+num" v-if="controlId"></view>
<view :id="'msg'+controlId" v-else></view>
</scroll-view>
<view class="intelligent_input" :style="{ bottom: bottom }">
<input type="text" placeholder-style="font-size: 30rpx;color: #C8C8C8;" placeholder="请输入…"
:adjust-position="false" v-model="value" @confirm.prevent="input_send" :focus="fouse"
:hold-keyboard="hold_keyboard" :confirm-hold="hold_keyboard" @focus="i_focus" @blur="blur"
confirm-type="发送"></input>
<view class="input_send" @touchend.prevent="input_send">
<image
src="https://yiben01.oss-cn-hangzhou.aliyuncs.com/img/921c55e21e95bf68a5d32f4aeab37bc64d2317d0_100.png"
mode=""></image>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
// 输入框value
value: '',
// 输入框距底部高度(键盘显示隐藏高度变化)
bottom: '0px',
// 对话框数组
msg_list: [{
left: true,
msg: '嗨~我是小助,您身边的智能助手~',
data: null
}, {
left: true,
msg: '请问有什么可以帮助您的?',
data: null
}],
// 容器高度
key_height: '0px',
// 容器滚动底部定位id
bottomId: '',
// 数组长度,用到定位id
num: 2,
// 输入框聚焦
fouse: false,
// 键盘不收起
hold_keyboard: true,
// 防抖
boolen: true,
// 定位id
controlId: false,
inputHeight: 0,
}
},
onLoad() {
},
watch: {
inputHeight(n, o) {
this.inputHeight = n
const res_s = uni.getSystemInfoSync();
this.key_height =
`${res_s.windowHeight - n}px`;
},
},
onShow() {
this.keyboardHeightChange()
this.compute_input_height()
const res_s = uni.getSystemInfoSync();
this.key_height =
`${res_s.windowHeight - this.inputHeight}px`;
},
onHide() {
},
onUnload() {
uni.offKeyboardHeightChange(); //取消监听键盘高度变化事件,避免内存消耗
},
methods: {
compute_input_height() {
const query = uni.createSelectorQuery().in(this); //这样写就只会选择本页面组件的类名box的
query.selectAll('.intelligent_input').boundingClientRect(
data => {
console.log(data[0].height);
this.inputHeight = data[0].height
}).exec();
},
// 点击键盘以外的地方收起键盘
other_click() {
uni.hideKeyboard()
},
// 监听键盘高度事件
keyboardHeightChange() {
uni.onKeyboardHeightChange((res) => { //监听键盘高度变化
const res_keyboard = uni.getSystemInfoSync();
let key_height = res.height - (res_keyboard.screenHeight - res_keyboard.windowHeight +
res_keyboard.safeAreaInsets.bottom)
this.bottom = `${res.height ? key_height : 0 }px`;
this.compute_input_height()
if (res.height) {
this.key_height =
`${res_keyboard.windowHeight - this.inputHeight - key_height }px`;
} else {
this.key_height =
`${res_keyboard.windowHeight - this.inputHeight}px`;
}
console.log(this.key_height);
this.num = this.msg_list.length;
this.bottomId = 'dade' + (this.num);
})
},
// 点击回答的高亮,跳转页面
keyHighlight(data, data_index) {
// uni.navigateTo({
// url: `./problems?data=${encodeURIComponent(JSON.stringify(data))}`
// })
},
// 输入框聚焦事件
i_focus() {
this.fouse = true;
this.hold_keyboard = true;
// 换id
this.controlId = true
},
blur() {
// 换id
this.controlId = false
},
// input发送事件
async input_send() {
if (!this.value.toString()) {
return
}
let timer
if (timer) clearTimeout(timer)
if (this.boolen) {
this.boolen = false
// 增加回答,自己的话
this.msg_list = [...this.msg_list, {
left: false,
msg: this.value,
data: null
}]
// 如果你们没有网络请求,----------------
if (this.value.match(/你好|您好/)) {
this.msg_list = [...this.msg_list, {
left: true,
msg: '您好,我是小助,您有什么问题可以直接问我哦~',
data: null
}]
} else {
this.msg_list = [...this.msg_list, {
left: true,
msg: '没有找到该问题答案,请换种问法试一试。',
data: null
}]
}
this.value = '';
this.num = this.msg_list.length;
this.bottomId = 'dade' + (this.num);
timer = setTimeout(() => {
this.boolen = true
}, 300)
// 如果你们有网络请求,把下边的注开----------------------------------------------
// // 注释掉的是网络请求,如果直接粘贴会报错,所以我就注掉了这就不会报错了
// let res = await this.$api.disease_keyword({
// keyword: this.value
// })
// console.log(res);
// if (res.code == 200) {
// if (Array.isArray(res.data) && res.data.length > 0) {
// // 替换高亮,增加样式
// res.data.forEach((v, i) => {
// if (v.keyHighlight && v.keyHighlight.match(/em/)) {
// v.keyHighlight = v.keyHighlight.replace(/<em>/,
// '<strong style="color: #F07828;">')
// v.keyHighlight = v.keyHighlight.replace(/<\/em>/, '</strong>')
// }
// })
// // 增加回答,机器人回答的话
// this.msg_list = [...this.msg_list, {
// left: true,
// title: `以下是关于“${this.value}”的解答:`,
// data: res.data
// }]
// } else {
// // 提示,没有找到该问题答案
// if (this.value.match(/你好|您好/)) {
// this.msg_list = [...this.msg_list, {
// left: true,
// msg: '您好,我是小助,您有什么问题可以直接问我哦~',
// data: null
// }]
// } else {
// this.msg_list = [...this.msg_list, {
// left: true,
// msg: '没有找到该问题答案,请换种问法试一试。',
// data: null
// }]
// }
// }
// this.value = '';
// this.num = this.msg_list.length;
// this.bottomId = 'dade' + (this.num);
// } else {
// // 出什么问题,toast出来
// }
// timer = setTimeout(() => {
// this.boolen = true
// }, 300)
}
}
}
}
</script>
<style scoped>
.pages_height {
width: 100%;
color: #191919;
background: #F0F0F0;
font-family: PingFangSC-Regular, PingFang SC;
}
.intelligent_main {
height: 100vh;
}
.intelligent_chat {
height: 90vh;
padding: 0 30rpx;
box-sizing: border-box;
}
/* 取消滚动条 */
.intelligent_chat ::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
display: none;
}
/* 最上边两句话 */
.intelligent_chat_view0 {
padding-top: 30rpx;
}
.intelligent_chat>view {
width: 100%;
background-color: red;
}
.intelligent_chat>view>view {
width: 100%;
}
.robot_word {
color: #191919;
display: flex;
flex-direction: column;
align-items: flex-start;
}
.patient_word {
color: #fff;
display: flex;
flex-direction: column;
align-items: flex-end;
}
.patient_word>view,
.robot_word>view {
max-width: 600rpx;
margin-bottom: 20rpx;
line-height: 42rpx;
font-size: 30rpx;
padding: 15rpx 20rpx;
box-sizing: border-box;
white-space: pre-wrap;
}
/* 患者问题 */
.patient_word>view {
background: #F07828;
border-radius: 15rpx 0 15rpx 15rpx;
}
/* 机器人回答 */
.robot_word>view {
background: #FFFFFF;
border-radius: 0 15rpx 15rpx 15rpx;
}
.keyHighlight {
font-size: 30rpx;
font-family: PingFangSC-Medium, PingFang SC;
line-height: 42rpx;
margin-top: 10rpx;
}
/* 输入框 */
.intelligent_input {
width: 100%;
height: 100rpx;
min-height: 98rpx;
background: #F7F7F7;
position: fixed;
bottom: 0px;
display: flex;
box-sizing: content-box;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.intelligent_input>input {
width: 80%;
background: #FFF;
margin: 15rpx 30rpx;
padding: 13rpx 20rpx;
box-sizing: border-box;
height: 70%;
min-height: 68rpx;
border-radius: 10rpx;
}
.intelligent_input>input::placeholder {
font-size: 30rpx;
color: #C8C8C8;
}
.input_send {
margin-right: 20rpx;
display: flex;
justify-content: center;
flex-direction: column;
align-items: flex-end;
}
.input_send>image {
width: 42rpx;
height: 42rpx;
padding: 10rpx;
}
</style>
更多推荐
所有评论(0)