添加平滑滚动弹幕

This commit is contained in:
John Smith 2019-11-28 22:18:27 +08:00
parent 98184e6732
commit ca7f27d536

View File

@ -3,8 +3,8 @@
<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>
<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 id="items" class="style-scope yt-live-chat-item-list-renderer" style="overflow: hidden; transform: translateY(0px);">
<div ref="itemOffset" id="item-offset" class="style-scope yt-live-chat-item-list-renderer" style="height: 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">
<text-message :key="message.id" v-if="message.type === MESSAGE_TYPE_TEXT"
class="style-scope yt-live-chat-item-list-renderer"
@ -38,7 +38,7 @@ import LegacyPaidMessage from './LegacyPaidMessage.vue'
import PaidMessage from './PaidMessage.vue'
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
export default {
@ -74,6 +74,16 @@ export default {
lastEnqueueTime: 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 //
}
},
@ -106,8 +116,10 @@ export default {
this.enqueueMessages(messages)
},
mergeSimilar(content) {
for (let i = this.messages.length - 1; i >= 0 && i >= this.messages.length - 5; i--) {
let message = this.messages[i]
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
@ -123,6 +135,7 @@ export default {
return true
}
}
}
return false
},
delMessage(id) {
@ -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()
}
}
}