Bootstrap

uniapp 实现聊天页面 textarea固定在底部且高度自增

在使用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"
		}
    }
    
}
;