在使用uniapp实现一个IM即时通讯系统的时候聊天界面是十分重要的,参考微信QQ的界面,决定模仿一个差不多的出来。
对于消息内容,肯定就是使用scroll-view组件了,发送消息的输入框则固定在底部,且输入框使用textarea组件。
页面效果如下(模拟器的原因导致软键盘附近有黑色):
textarea自动增高,当内容输入过多会,输入框会向上增高一点,与QQ的输入是差不多的。
ps: 上面的黑色背景是scroll-view,此处并没有填充消息
对于这种效果,在APP端是比较好实现的
页面:
<template>
<view>
<u-navbar leftIconColor="#FFFFFF" bgColor="#3c9cff" @leftClick="handleLeftClick">
<view slot="center">
<text style="color: #FFFFFF;">聊天界面</text>
</view>
</u-navbar>
<view style="width: 100%;background-color: #000000;">
<scroll-view :style="{height: height+'px'}" scroll-y>
</scroll-view>
</view>
<view class="footer" ref="footer" id="footer">
<view class="content-wrap">
<textarea class="content" v-model="text" maxlength="-1" auto-height />
</view>
<view class="btn-wrap">
<button class="btn" :disabled="disable" :class="{'disabled': disable}" @click="handleSend">发送</button>
</view>
</view>
</view>
</template>
这个页面我是自定义navbar,并且使用的是uview的组件
class,id,ref值为footer的就是固定在底部的输入框与按钮,之所以有class,id,ref主要是用class设置样式,ref,id是用来获取固定在底部的元素的高度,h5支持vue的this.$refs
这种形式但是App端不支持,只能使用uniapp自带的元素选择器
对于样式:
<style>
.footer {
width: 100%;
background-color: #E9EDF4;
display: flex;
position: fixed;
bottom: 0;
}
.footer .content-wrap {
width: 78%;
margin-left: 2%;
}
.footer .content {
width: 100%;
box-sizing: border-box;
margin: 14rpx 0;
background-color: #FFFFFF;
border-radius: 30rpx;
padding: 16rpx;
caret-color: #01B4FE;
}
.footer .btn-wrap {
width: 18%;
margin-right: 2%;
}
.footer .btn {
width: 15%;
height: 65rpx;
font-size: 26rpx;
margin-left: 2%;
background-color: #01B4FE;
color: #FFFFFF;
position: fixed;
bottom: 14rpx;
border: 0;
outline: none;
}
.footer .btn-wrap .disabled {
background-color: #aae8f5;
}
/deep/ .uni-textarea-wrapper {
max-height: 180rpx;
}
</style>
/deep/ .uni-textarea-wrapper
这个才是让textarea自动增高但是会增高到一定高度,不至于让textarea因为内容的增多而一直增高
当输入框获取焦点时会弹出软键盘,在APP端很容易把页面上面的部分顶出去,所以需要在page.json
配置一下:
{
"path" : "pages/test/test-chat/test-chat",
"style" :
{
"navigationBarTitleText": "测试chat",
"enablePullDownRefresh": false,
"navigationStyle":"custom",
//App端的配置
"app-plus":{
"softinputMode":"adjustResize"
}
}
}
并且需要监听键盘高度变化的事件,让scroll-view的高度也随之变化,不然你的软键盘就会遮挡住scroll-view一些内容:
<script>
export default {
data() {
return {
text: '',
height: 0,
pageHeight: 0,
disable: true,
footerHeight: 0,
keyBoardHeight: 0,
messages: [],
}
},
watch: {
//监听text,当他有值时发送按钮才可以点击
text(newVal) {
if (newVal.trim() != '') {
this.disable = false
} else {
this.disable = true
}
}
},
onReady() {
//获取整个页面的高度,从而计算出页面可用的高度,因为使用了自定义的navbar所以this.pageHeight不是单纯的res.windowHeight。(ps: uview组件的navbar高度是固定的44px,不包括statusBarHeight)
uni.getSystemInfo({
success: (res) => {
this.pageHeight = res.windowHeight - res.statusBarHeight - 44
}
})
},
onLoad() {
this.initListener()
},
onUnload() {
this.destoryListener()
},
mounted() {
//这里获取footer元素的高度,根据不同平台用的方式不同,对于uniapp的dom定位方法应该是通用的。特别注意,一定要在this.$nextTick方法里写,不然可能页面还没渲染出footer元素
this.$nextTick(() => {
// #ifdef H5
this.footerHeight = this.$refs.footer.$el.offsetHeight
this.height = this.pageHeight - this.footerHeight
// #endif
// #ifdef APP-PLUS
uni.createSelectorQuery().in(this).select("#footer").boundingClientRect((data) => {
this.footerHeight = data.height
this.height = this.pageHeight - this.footerHeight
}).exec()
// #endif
})
},
methods: {
initListener() {
//监听键盘的高度变化,让sroll-view的高度随之变化
uni.onKeyboardHeightChange(res => {
let keyBoardHeight = res.height
if (this.keyBoardHeight == 0 && keyBoardHeight > 0) {
this.keyBoardHeight = keyBoardHeight
}
if (keyBoardHeight > 0) {
this.height = this.height - this.keyBoardHeight
} else {
this.height = this.height + this.keyBoardHeight
}
})
},
destoryListener() {
uni.offKeyboardHeightChange((res) => {
console.log("offKeyboardHeightChange...")
})
},
handleLeftClick() {
},
handleSend() {
}
}
}
</script>
对于h5端,在uniapp中没有找到如何获取h5软键盘的高度的api,所以在移动端的浏览器访问时,scroll-view的高度暂时无法随软键盘弹出而变化,但是影响也不是很大,就是在输入消息时,无法让scroll-view的最后一条消息显示在输入框的上面。
最后贴出页面的完整代码,以及page.json的配置:
<template>
<view>
<u-navbar leftIconColor="#FFFFFF" bgColor="#3c9cff" @leftClick="handleLeftClick">
<view slot="center">
<text style="color: #FFFFFF;">聊天界面</text>
</view>
</u-navbar>
<view style="width: 100%;background-color: #000000;">
<scroll-view :style="{height: height+'px'}" scroll-y>
</scroll-view>
</view>
<view class="footer" ref="footer" id="footer">
<view class="content-wrap">
<textarea class="content" v-model="text" maxlength="-1" auto-height />
</view>
<view class="btn-wrap">
<button class="btn" :disabled="disable" :class="{'disabled': disable}" @click="handleSend">发送</button>
</view>
</view>
</view>
</template>
<script>
import myMsg from '@/components/my-msg/my-msg.vue'
export default {
data() {
return {
text: '',
height: 0,
pageHeight: 0,
disable: true,
footerHeight: 0,
keyBoardHeight: 0,
messages: [],
}
},
components: {
"my-msg": myMsg
},
watch: {
text(newVal) {
if (newVal.trim() != '') {
this.disable = false
} else {
this.disable = true
}
}
},
onReady() {
uni.getSystemInfo({
success: (res) => {
this.pageHeight = res.windowHeight - res.statusBarHeight - 44
}
})
},
onLoad() {
this.initListener()
},
onUnload() {
this.destoryListener()
},
mounted() {
this.$nextTick(() => {
// #ifdef H5
this.footerHeight = this.$refs.footer.$el.offsetHeight
this.height = this.pageHeight - this.footerHeight
// #endif
// #ifdef APP-PLUS
uni.createSelectorQuery().in(this).select("#footer").boundingClientRect((data) => {
this.footerHeight = data.height
this.height = this.pageHeight - this.footerHeight
}).exec()
// #endif
})
},
methods: {
initListener() {
uni.onKeyboardHeightChange(res => {
let keyBoardHeight = res.height
if (this.keyBoardHeight == 0 && keyBoardHeight > 0) {
this.keyBoardHeight = keyBoardHeight
}
if (keyBoardHeight > 0) {
this.height = this.height - this.keyBoardHeight
} else {
this.height = this.height + this.keyBoardHeight
}
})
},
destoryListener() {
uni.offKeyboardHeightChange((res) => {
console.log("offKeyboardHeightChange...")
})
},
handleLeftClick() {
},
handleSend() {
}
}
}
</script>
<style>
.footer {
width: 100%;
background-color: #E9EDF4;
display: flex;
position: fixed;
bottom: 0;
}
.footer .content-wrap {
width: 78%;
margin-left: 2%;
}
.footer .content {
width: 100%;
box-sizing: border-box;
margin: 14rpx 0;
background-color: #FFFFFF;
border-radius: 30rpx;
padding: 16rpx;
caret-color: #01B4FE;
}
.footer .btn-wrap {
width: 18%;
margin-right: 2%;
}
.footer .btn {
width: 15%;
height: 65rpx;
font-size: 26rpx;
margin-left: 2%;
background-color: #01B4FE;
color: #FFFFFF;
position: fixed;
bottom: 14rpx;
border: 0;
outline: none;
}
.footer .btn-wrap .disabled {
background-color: #aae8f5;
}
/deep/ .uni-textarea-wrapper {
max-height: 180rpx;
}
</style>
{
"path" : "pages/test/test-chat/test-chat",
"style" :
{
"navigationBarTitleText": "测试chat",
"enablePullDownRefresh": false,
"navigationStyle":"custom",
//App端的配置
"app-plus":{
"softinputMode":"adjustResize"
}
}
}