优化歌词在短时间内快速播放时的滚动效果

pull/1583/head
lyswhut 2023-09-02 13:39:46 +08:00
parent ed858bafdc
commit 5e7ef691ae
5 changed files with 150 additions and 75 deletions

View File

@ -17,6 +17,7 @@
- 点击打开歌单弹窗背景将不再自动关闭弹窗,防止选择输入框里的内容时意外关闭弹窗
- 优化数据传输逻辑,列表同步指令使用队列机制,保证列表同步操作的顺序
- 优化桌面歌词在开启 缩放当前播放的歌词 并关闭 延迟歌词滚动 时的歌词滚动位置计算问题,现在歌词滚动应该可以正确滚动到目标位置了
- 优化歌词在短时间内快速播放时的滚动效果,现在遇到这种情况时滚动将更平滑
### 修复

View File

@ -8,15 +8,31 @@ const easeInOutQuad = (t: number, b: number, c: number, d: number): number => {
type Noop = () => void
const noop: Noop = () => {}
type ScrollElement<T> = {
lx_scrollLockKey?: number
lx_scrollNextParams?: [ScrollElement<HTMLElement>, number, number, Noop]
lx_scrollTimeout?: number
lx_scrollDelayTimeout?: number
} & T
const handleScrollY = (element: HTMLElement, to: number, duration = 300, fn = noop): Noop => {
const handleScrollY = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = noop): Noop => {
if (!element) {
fn()
return noop
}
const clean = () => {
element.lx_scrollLockKey = undefined
element.lx_scrollNextParams = undefined
if (element.lx_scrollTimeout) window.clearTimeout(element.lx_scrollTimeout)
element.lx_scrollTimeout = undefined
}
if (element.lx_scrollLockKey) {
element.lx_scrollNextParams = [element, to, duration, fn]
element.lx_scrollLockKey = -1
return clean
}
// @ts-expect-error
const start = element.scrollTop ?? element.scrollY ?? 0
let cancel = false
if (to > start) {
let maxScrollTop = element.scrollHeight - element.clientHeight
if (to > maxScrollTop) to = maxScrollTop
@ -34,9 +50,19 @@ const handleScrollY = (element: HTMLElement, to: number, duration = 300, fn = no
}
let currentTime = 0
let val
let val: number
let key = Math.random()
const animateScroll = () => {
element.lx_scrollTimeout = undefined
// if (element.lx_scrollLockKey != key) {
if (element.lx_scrollNextParams && currentTime > duration * 0.75) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
return
}
currentTime += increment
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
if (element.scrollTo) {
@ -45,19 +71,23 @@ const handleScrollY = (element: HTMLElement, to: number, duration = 300, fn = no
element.scrollTop = val
}
if (currentTime < duration) {
if (cancel) {
fn()
return
}
window.setTimeout(animateScroll, increment)
element.lx_scrollTimeout = window.setTimeout(animateScroll, increment)
} else {
fn()
if (element.lx_scrollNextParams) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
} else {
clean()
fn()
}
}
}
element.lx_scrollLockKey = key
animateScroll()
return () => {
cancel = true
}
return clean
}
/**
*
@ -67,16 +97,24 @@ const handleScrollY = (element: HTMLElement, to: number, duration = 300, fn = no
* @param {*} fn
* @param {*} delay
*/
export const scrollTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
export const scrollTo = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
let cancelFn: () => void
let timeout: number | null
if (element.lx_scrollDelayTimeout != null) {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
if (delay) {
let scrollCancelFn: Noop
cancelFn = () => {
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
if (element.lx_scrollDelayTimeout == null) {
scrollCancelFn?.()
} else {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
}
timeout = window.setTimeout(() => {
timeout = null
element.lx_scrollDelayTimeout = window.setTimeout(() => {
element.lx_scrollDelayTimeout = undefined
scrollCancelFn = handleScrollY(element, to, duration, fn)
}, delay)
} else {
@ -84,14 +122,24 @@ export const scrollTo = (element: HTMLElement, to: number, duration = 300, fn =
}
return cancelFn
}
const handleScrollX = (element: HTMLElement, to: number, duration = 300, fn = () => {}): () => void => {
const handleScrollX = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}): () => void => {
if (!element) {
fn()
return noop
}
const clean = () => {
element.lx_scrollLockKey = undefined
element.lx_scrollNextParams = undefined
if (element.lx_scrollTimeout) window.clearTimeout(element.lx_scrollTimeout)
element.lx_scrollTimeout = undefined
}
if (element.lx_scrollLockKey) {
element.lx_scrollNextParams = [element, to, duration, fn]
element.lx_scrollLockKey = -1
return clean
}
// @ts-expect-error
const start = element.scrollLeft || element.scrollX || 0
let cancel = false
if (to > start) {
let maxScrollLeft = element.scrollWidth - element.clientWidth
if (to > maxScrollLeft) to = maxScrollLeft
@ -109,9 +157,18 @@ const handleScrollX = (element: HTMLElement, to: number, duration = 300, fn = ()
}
let currentTime = 0
let val
let val: number
let key = Math.random()
const animateScroll = () => {
element.lx_scrollTimeout = undefined
if (element.lx_scrollNextParams && currentTime > duration * 0.75) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
return
}
currentTime += increment
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
if (element.scrollTo) {
@ -120,19 +177,21 @@ const handleScrollX = (element: HTMLElement, to: number, duration = 300, fn = ()
element.scrollLeft = val
}
if (currentTime < duration) {
if (cancel) {
fn()
return
}
window.setTimeout(animateScroll, increment)
element.lx_scrollTimeout = window.setTimeout(animateScroll, increment)
} else {
fn()
if (element.lx_scrollNextParams) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
} else {
clean()
fn()
}
}
}
element.lx_scrollLockKey = key
animateScroll()
return () => {
cancel = true
}
return clean
}
/**
*
@ -142,16 +201,24 @@ const handleScrollX = (element: HTMLElement, to: number, duration = 300, fn = ()
* @param {*} fn
* @param {*} delay
*/
export const scrollXTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
export const scrollXTo = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
let cancelFn: Noop
let timeout: number | null
if (element.lx_scrollDelayTimeout != null) {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
if (delay) {
let scrollCancelFn: Noop
cancelFn = () => {
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
if (element.lx_scrollDelayTimeout == null) {
scrollCancelFn?.()
} else {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
}
timeout = window.setTimeout(() => {
timeout = null
element.lx_scrollDelayTimeout = window.setTimeout(() => {
element.lx_scrollDelayTimeout = undefined
scrollCancelFn = handleScrollX(element, to, duration, fn)
}, delay)
} else {
@ -160,14 +227,24 @@ export const scrollXTo = (element: HTMLElement, to: number, duration = 300, fn =
return cancelFn
}
const handleScrollXR = (element: HTMLElement, to: number, duration = 300, fn = () => {}): () => void => {
const handleScrollXR = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}): () => void => {
if (!element) {
fn()
return noop
}
const clean = () => {
element.lx_scrollLockKey = undefined
element.lx_scrollNextParams = undefined
if (element.lx_scrollTimeout) window.clearTimeout(element.lx_scrollTimeout)
element.lx_scrollTimeout = undefined
}
if (element.lx_scrollLockKey) {
element.lx_scrollNextParams = [element, to, duration, fn]
element.lx_scrollLockKey = -1
return clean
}
// @ts-expect-error
const start = element.scrollLeft || element.scrollX as number || 0
let cancel = false
if (to < start) {
let maxScrollLeft = -element.scrollWidth + element.clientWidth
if (to < maxScrollLeft) to = maxScrollLeft
@ -186,9 +263,18 @@ const handleScrollXR = (element: HTMLElement, to: number, duration = 300, fn = (
}
let currentTime = 0
let val
let val: number
let key = Math.random()
const animateScroll = () => {
element.lx_scrollTimeout = undefined
if (element.lx_scrollNextParams && currentTime > duration * 0.75) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
return
}
currentTime += increment
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
@ -198,19 +284,23 @@ const handleScrollXR = (element: HTMLElement, to: number, duration = 300, fn = (
element.scrollLeft = val
}
if (currentTime < duration) {
if (cancel) {
fn()
return
}
window.setTimeout(animateScroll, increment)
element.lx_scrollTimeout = window.setTimeout(animateScroll, increment)
} else {
fn()
if (element.lx_scrollNextParams) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
} else {
clean()
fn()
}
}
}
element.lx_scrollLockKey = key
animateScroll()
return () => {
cancel = true
}
return clean
}
/**
* writing-mode: vertical-rl
@ -220,16 +310,24 @@ const handleScrollXR = (element: HTMLElement, to: number, duration = 300, fn = (
* @param fn
* @param delay
*/
export const scrollXRTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
export const scrollXRTo = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
let cancelFn: Noop
let timeout: number | null
if (element.lx_scrollDelayTimeout != null) {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
if (delay) {
let scrollCancelFn: Noop
cancelFn = () => {
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
if (element.lx_scrollDelayTimeout == null) {
scrollCancelFn?.()
} else {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
}
timeout = window.setTimeout(() => {
timeout = null
element.lx_scrollDelayTimeout = window.setTimeout(() => {
element.lx_scrollDelayTimeout = undefined
scrollCancelFn = handleScrollXR(element, to, duration, fn)
}, delay)
} else {

View File

@ -35,10 +35,6 @@ export default (isComputeHeight) => {
const handleScrollLrc = (duration = 300) => {
if (!dom_lines?.length || !dom_lyric.value) return
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
if (isStopScroll) return
let dom_p = dom_lines[lyric.line]
@ -161,10 +157,6 @@ export default (isComputeHeight) => {
if (lines.length) {
setLyric(lines)
} else {
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
cancelScrollFn = scrollTo(dom_lyric.value, 0, 300, () => {
if (lyric.lines !== lines) return
setLyric(lines)

View File

@ -35,10 +35,6 @@ export default (isComputeWidth) => {
const handleScrollLrc = (duration = 300) => {
if (!dom_lines?.length || !dom_lyric.value) return
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
if (isStopScroll) return
let dom_p = dom_lines[lyric.line]
@ -161,10 +157,6 @@ export default (isComputeWidth) => {
if (lines.length) {
setLyric(lines)
} else {
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
cancelScrollFn = scrollXRTo(dom_lyric.value, 0, 300, () => {
if (lyric.lines !== lines) return
setLyric(lines)

View File

@ -80,10 +80,6 @@ export default ({ isPlay, lyric, playProgress, isShowLyricProgressSetting, offse
const handleScrollLrc = (duration = 300) => {
if (!dom_lines?.length || !dom_lyric.value) return
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
if (isSkipMouseEnter) return
if (isStopScroll.value) return
let dom_p = dom_lines[lyric.line]
@ -179,10 +175,6 @@ export default ({ isPlay, lyric, playProgress, isShowLyricProgressSetting, offse
if (lines.length) {
setLyric(lines)
} else {
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
cancelScrollFn = scrollTo(dom_lyric.value, 0, 300, () => {
if (lyric.lines !== lines) return
setLyric(lines)