mirror of
https://github.com/xfgryujk/blivechat.git
synced 2025-01-14 22:30:23 +08:00
添加平滑滚动弹幕
This commit is contained in:
parent
98184e6732
commit
ca7f27d536
@ -3,8 +3,8 @@
|
|||||||
<ticker class="style-scope yt-live-chat-renderer" :messages="paidMessages" :hidden="paidMessages.length === 0"></ticker>
|
<ticker class="style-scope yt-live-chat-renderer" :messages="paidMessages" :hidden="paidMessages.length === 0"></ticker>
|
||||||
<yt-live-chat-item-list-renderer class="style-scope yt-live-chat-renderer" allow-scroll>
|
<yt-live-chat-item-list-renderer class="style-scope yt-live-chat-renderer" allow-scroll>
|
||||||
<div id="item-scroller" ref="scroller" class="style-scope yt-live-chat-item-list-renderer animated" @scroll="onScroll">
|
<div id="item-scroller" ref="scroller" class="style-scope yt-live-chat-item-list-renderer animated" @scroll="onScroll">
|
||||||
<div id="item-offset" class="style-scope yt-live-chat-item-list-renderer" style="height: 1800px;">
|
<div ref="itemOffset" id="item-offset" class="style-scope yt-live-chat-item-list-renderer" style="height: 0px;">
|
||||||
<div id="items" class="style-scope yt-live-chat-item-list-renderer" style="overflow: hidden; transform: translateY(0px);">
|
<div ref="items" id="items" class="style-scope yt-live-chat-item-list-renderer" style="overflow: hidden; transform: translateY(0px);">
|
||||||
<template v-for="message in messages">
|
<template v-for="message in messages">
|
||||||
<text-message :key="message.id" v-if="message.type === MESSAGE_TYPE_TEXT"
|
<text-message :key="message.id" v-if="message.type === MESSAGE_TYPE_TEXT"
|
||||||
class="style-scope yt-live-chat-item-list-renderer"
|
class="style-scope yt-live-chat-item-list-renderer"
|
||||||
@ -38,7 +38,7 @@ import LegacyPaidMessage from './LegacyPaidMessage.vue'
|
|||||||
import PaidMessage from './PaidMessage.vue'
|
import PaidMessage from './PaidMessage.vue'
|
||||||
import * as constants from './constants'
|
import * as constants from './constants'
|
||||||
|
|
||||||
// const CHAT_SMOOTH_ANIMATION_TIME_MS = 84
|
const CHAT_SMOOTH_ANIMATION_TIME_MS = 84
|
||||||
const SCROLLED_TO_BOTTOM_EPSILON = 15
|
const SCROLLED_TO_BOTTOM_EPSILON = 15
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -74,6 +74,16 @@ export default {
|
|||||||
lastEnqueueTime: null, // 上次进队列的时间
|
lastEnqueueTime: null, // 上次进队列的时间
|
||||||
estimatedEnqueueInterval: null, // 估计的下次进队列时间间隔
|
estimatedEnqueueInterval: null, // 估计的下次进队列时间间隔
|
||||||
|
|
||||||
|
messagesBuffer: [], // 暂时未显示的消息,当不能自动滚动时会积压在这
|
||||||
|
preinsertHeight: 0, // 插入新消息之前items的高度
|
||||||
|
isSmoothed: true, // 是否平滑滚动,当消息太快时不平滑滚动
|
||||||
|
chatRateMs: 1000, // 用来计算消息速度
|
||||||
|
scrollPixelsRemaining: 0, // 平滑滚动剩余像素
|
||||||
|
scrollTimeRemainingMs: 0, // 平滑滚动剩余时间
|
||||||
|
lastSmoothChatMessageAddMs: null, // 上次showNewMessages时间
|
||||||
|
smoothScrollRafHandle: null, // 平滑滚动requestAnimationFrame句柄
|
||||||
|
lastSmoothScrollUpdate: null, // 平滑滚动上一帧时间
|
||||||
|
|
||||||
atBottom: true // 滚动到底部,用来判断能否自动滚动
|
atBottom: true // 滚动到底部,用来判断能否自动滚动
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -106,8 +116,10 @@ export default {
|
|||||||
this.enqueueMessages(messages)
|
this.enqueueMessages(messages)
|
||||||
},
|
},
|
||||||
mergeSimilar(content) {
|
mergeSimilar(content) {
|
||||||
for (let i = this.messages.length - 1; i >= 0 && i >= this.messages.length - 5; i--) {
|
let remainNum = 5
|
||||||
let message = this.messages[i]
|
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
|
let longer, shorter
|
||||||
if (message.content.length > content.length) {
|
if (message.content.length > content.length) {
|
||||||
longer = message.content
|
longer = message.content
|
||||||
@ -123,6 +135,7 @@ export default {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
delMessage(id) {
|
delMessage(id) {
|
||||||
@ -239,29 +252,23 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.messages.length > this.maxNumber) {
|
this.maybeResizeScrollContainer(),
|
||||||
// 防止同时添加和删除项目时所有的项目重新渲染 https://github.com/vuejs/vue/issues/6857
|
this.flushMessagesBuffer()
|
||||||
this.$nextTick(() => this.messages.splice(0, this.messages.length - this.maxNumber))
|
this.$nextTick(this.maybeScrollToBottom)
|
||||||
}
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (this.canScrollToBottom) {
|
|
||||||
this.scrollToBottom()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
handleAddMessage(message) {
|
handleAddMessage(message) {
|
||||||
message = {
|
message = {
|
||||||
...message,
|
...message,
|
||||||
addTime: new Date() // 添加一个本地时间给Ticker用,防止本地时间和服务器时间相差很大的情况
|
addTime: new Date() // 添加一个本地时间给Ticker用,防止本地时间和服务器时间相差很大的情况
|
||||||
}
|
}
|
||||||
this.messages.push(message)
|
this.messagesBuffer.push(message)
|
||||||
if (message.type !== constants.MESSAGE_TYPE_TEXT) {
|
if (message.type !== constants.MESSAGE_TYPE_TEXT) {
|
||||||
this.paidMessages.push(message)
|
this.paidMessages.push(message)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleDelMessage(message) {
|
handleDelMessage(message) {
|
||||||
let id = message.id
|
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++) {
|
for (let i = 0; i < arr.length; i++) {
|
||||||
if (arr[i].id === id) {
|
if (arr[i].id === id) {
|
||||||
arr.splice(i, 1)
|
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() {
|
maybeScrollToBottom() {
|
||||||
if (this.canScrollToBottom) {
|
if (this.canScrollToBottom) {
|
||||||
this.scrollToBottom()
|
this.scrollToBottom()
|
||||||
@ -283,7 +396,7 @@ export default {
|
|||||||
onScroll() {
|
onScroll() {
|
||||||
let scroller = this.$refs.scroller
|
let scroller = this.$refs.scroller
|
||||||
this.atBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight < SCROLLED_TO_BOTTOM_EPSILON
|
this.atBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight < SCROLLED_TO_BOTTOM_EPSILON
|
||||||
// this.flushActiveItems()
|
this.flushMessagesBuffer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user