+
= 0 && i >= this.messages.length - 5; i--) {
- let message = this.messages[i]
- let longer, shorter
- if (message.content.length > content.length) {
- longer = message.content
- shorter = content
- } else {
- longer = content
- shorter = message.content
- }
- if (longer.indexOf(shorter) !== -1 // 长的包含短的
- && longer.length - shorter.length < shorter.length // 长度差较小
- ) {
- message.repeated++
- return true
+ let remainNum = 5
+ for (let arr of [this.messagesBuffer, this.messages]) {
+ for (let i = arr.length - 1; i >= 0 && --remainNum > 0; i--) {
+ let message = arr[i]
+ let longer, shorter
+ if (message.content.length > content.length) {
+ longer = message.content
+ shorter = content
+ } else {
+ longer = content
+ shorter = message.content
+ }
+ if (longer.indexOf(shorter) !== -1 // 长的包含短的
+ && longer.length - shorter.length < shorter.length // 长度差较小
+ ) {
+ message.repeated++
+ return true
+ }
}
}
return false
@@ -239,29 +252,23 @@ export default {
}
}
- if (this.messages.length > this.maxNumber) {
- // 防止同时添加和删除项目时所有的项目重新渲染 https://github.com/vuejs/vue/issues/6857
- this.$nextTick(() => this.messages.splice(0, this.messages.length - this.maxNumber))
- }
- this.$nextTick(() => {
- if (this.canScrollToBottom) {
- this.scrollToBottom()
- }
- })
+ this.maybeResizeScrollContainer(),
+ this.flushMessagesBuffer()
+ this.$nextTick(this.maybeScrollToBottom)
},
handleAddMessage(message) {
message = {
...message,
addTime: new Date() // 添加一个本地时间给Ticker用,防止本地时间和服务器时间相差很大的情况
}
- this.messages.push(message)
+ this.messagesBuffer.push(message)
if (message.type !== constants.MESSAGE_TYPE_TEXT) {
this.paidMessages.push(message)
}
},
handleDelMessage(message) {
let id = message.id
- for (let arr of [this.messages, this.paidMessages]) {
+ for (let arr of [this.messages, this.paidMessages, this.messagesBuffer]) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].id === id) {
arr.splice(i, 1)
@@ -271,6 +278,112 @@ export default {
}
},
+ async flushMessagesBuffer() {
+ if (this.messagesBuffer.length <= 0) {
+ return
+ }
+ if (!this.canScrollToBottom) {
+ if (this.messagesBuffer.length > this.maxNumber) {
+ // 未显示消息数 > 最大可显示数,丢弃
+ this.messagesBuffer.splice(0, this.messagesBuffer.length - this.maxNumber)
+ }
+ return
+ }
+
+ let removeNum = Math.max(this.messages.length + this.messagesBuffer.length - this.maxNumber, 0)
+ if (removeNum > 0) {
+ this.messages.splice(0, removeNum)
+ // 防止同时添加和删除项目时所有的项目重新渲染 https://github.com/vuejs/vue/issues/6857
+ await this.$nextTick()
+ }
+
+ this.preinsertHeight = this.$refs.items.clientHeight
+ for (let message of this.messagesBuffer) {
+ this.messages.push(message)
+ }
+ this.messagesBuffer = []
+ // 等items高度变化
+ this.$nextTick(this.showNewMessages)
+ },
+ showNewMessages() {
+ let hasScrollBar = this.$refs.items.clientHeight > this.$refs.scroller.clientHeight
+ this.$refs.itemOffset.style.height = `${this.$refs.items.clientHeight}px`
+ if (!this.canScrollToBottom || !hasScrollBar) {
+ return
+ }
+
+ // 计算剩余像素
+ this.scrollPixelsRemaining += this.$refs.items.clientHeight - this.preinsertHeight
+ this.scrollToBottom()
+ this.$refs.items.style.transform = `translateY(${Math.floor(this.scrollPixelsRemaining)}px)`
+
+ // 计算是否平滑滚动、剩余时间
+ if (!this.lastSmoothChatMessageAddMs) {
+ this.lastSmoothChatMessageAddMs = performance.now()
+ }
+ let interval = performance.now() - this.lastSmoothChatMessageAddMs
+ this.chatRateMs = 0.9 * this.chatRateMs + 0.1 * interval
+ if (this.isSmoothed) {
+ if (this.chatRateMs < 400) {
+ this.isSmoothed = false
+ }
+ } else {
+ if (this.chatRateMs > 450) {
+ this.isSmoothed = true
+ }
+ }
+ this.scrollTimeRemainingMs += this.isSmoothed ? CHAT_SMOOTH_ANIMATION_TIME_MS : 0
+
+ if (!this.smoothScrollRafHandle) {
+ this.smoothScrollRafHandle = window.requestAnimationFrame(this.smoothScroll)
+ }
+ this.lastSmoothChatMessageAddMs = performance.now()
+ },
+ smoothScroll(time) {
+ if (!this.lastSmoothScrollUpdate) {
+ // 第一帧
+ this.lastSmoothScrollUpdate = time
+ this.smoothScrollRafHandle = window.requestAnimationFrame(this.smoothScroll)
+ return
+ }
+
+ let interval = time - this.lastSmoothScrollUpdate
+ if (
+ this.scrollPixelsRemaining <= 0 || this.scrollPixelsRemaining >= 400 // 已经滚动到底部或者离底部太远则结束
+ || interval >= 1000 // 离上一帧时间太久,可能用户切换到其他网页
+ || this.scrollTimeRemainingMs <= 0 // 时间已结束
+ ) {
+ this.resetSmoothScroll()
+ this.$refs.items.style.transform = 'translateY(0px)'
+ return
+ }
+
+ let pixelsToScroll = interval / this.scrollTimeRemainingMs * this.scrollPixelsRemaining
+ this.scrollPixelsRemaining -= pixelsToScroll
+ if (this.scrollPixelsRemaining < 0) {
+ this.scrollPixelsRemaining = 0
+ }
+ this.scrollTimeRemainingMs -= interval
+ if (this.scrollTimeRemainingMs < 0) {
+ this.scrollTimeRemainingMs = 0
+ }
+ this.lastSmoothScrollUpdate = time
+ this.smoothScrollRafHandle = window.requestAnimationFrame(this.smoothScroll)
+ this.$refs.items.style.transform = `translateY(${Math.floor(this.scrollPixelsRemaining)}px)`
+ },
+ resetSmoothScroll() {
+ this.scrollTimeRemainingMs = this.scrollPixelsRemaining = 0
+ this.lastSmoothScrollUpdate = null
+ if (this.smoothScrollRafHandle) {
+ window.cancelAnimationFrame(this.smoothScrollRafHandle)
+ this.smoothScrollRafHandle = null
+ }
+ },
+
+ maybeResizeScrollContainer() {
+ this.$refs.itemOffset.style.height = `${this.$refs.items.clientHeight}px`
+ this.maybeScrollToBottom()
+ },
maybeScrollToBottom() {
if (this.canScrollToBottom) {
this.scrollToBottom()
@@ -283,7 +396,7 @@ export default {
onScroll() {
let scroller = this.$refs.scroller
this.atBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight < SCROLLED_TO_BOTTOM_EPSILON
- // this.flushActiveItems()
+ this.flushMessagesBuffer()
}
}
}