mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
194 lines
6.0 KiB
TypeScript
194 lines
6.0 KiB
TypeScript
/*
|
|
============
|
|
祖 传 代 码
|
|
============
|
|
*/
|
|
|
|
import { getComponentSettings } from '@/core/settings'
|
|
import { ProgressSeekMode } from './gesture-preview'
|
|
|
|
type Position = { x: number; y: number; width: number; height: number }
|
|
const getPosition = (element: HTMLElement) => {
|
|
let x = 0
|
|
let y = 0
|
|
let e = element
|
|
while (e && !Number.isNaN(e.offsetLeft) && !Number.isNaN(e.offsetTop)) {
|
|
x += e.offsetLeft - e.scrollLeft
|
|
y += e.offsetTop - e.scrollTop
|
|
e = e.offsetParent as HTMLElement
|
|
}
|
|
return { x, y }
|
|
}
|
|
const createPosition = (e: TouchEvent, element: HTMLElement) => {
|
|
const elementPosition = getPosition(element)
|
|
const position = {
|
|
x: (e.touches[0].pageX - elementPosition.x) / element.clientWidth,
|
|
y: (e.touches[0].pageY - elementPosition.y) / element.clientHeight,
|
|
width: element.clientWidth,
|
|
height: element.clientHeight,
|
|
}
|
|
return position
|
|
}
|
|
|
|
/** 从运动轨迹中判断手势, 并触发对应的事件: (有的事件里带有参数`detail`)
|
|
* - `start`: 手势开始
|
|
* - `end`: 手势结束, 参数为手势信息, 参见`lastAction`的类型
|
|
* - `brightness`: 亮度调整, 参数为亮度的变化值
|
|
* - `volume`: 音量调整, 参数为音量的变化值
|
|
* - `progress`: 进度调整, 参数为进度的变化值和灵敏度 (`{ progress: number; mode: ProgressSeekMode }`)
|
|
* - `cancel`: 请求取消调整
|
|
*/
|
|
export class SwipeAction extends EventTarget {
|
|
minSwipeDistance = getComponentSettings('touchPlayerGestures').options.swiperDistance as number
|
|
startPosition: Position = null
|
|
lastAction: {
|
|
type: 'brightness' | 'volume' | 'progress'
|
|
brightness?: number
|
|
volume?: number
|
|
seconds?: number
|
|
} = null
|
|
constructor(public element: HTMLElement) {
|
|
super()
|
|
element.addEventListener('touchstart', () => {
|
|
this.dispatchEvent(new CustomEvent('start'))
|
|
})
|
|
element.addEventListener('touchend', () => {
|
|
this.dispatchEvent(
|
|
new CustomEvent('end', {
|
|
detail: this.lastAction,
|
|
}),
|
|
)
|
|
this.lastAction = null
|
|
})
|
|
}
|
|
|
|
startAction(direction: 'vertical' | 'horizontal', distance: number, position: Position) {
|
|
if (direction === 'vertical') {
|
|
if (this.startPosition.x < 1 / 2) {
|
|
const brightnessChange =
|
|
Math.round((200 * (distance - this.minSwipeDistance)) / (1.5 * position.height)) / 100
|
|
this.dispatchEvent(
|
|
new CustomEvent('brightness', {
|
|
detail: brightnessChange,
|
|
}),
|
|
)
|
|
this.lastAction = {
|
|
type: 'brightness',
|
|
brightness: brightnessChange,
|
|
}
|
|
} else {
|
|
const volumeChange =
|
|
Math.round((200 * (distance - this.minSwipeDistance)) / (1.5 * position.height)) / 100
|
|
this.dispatchEvent(
|
|
new CustomEvent('volume', {
|
|
detail: volumeChange,
|
|
}),
|
|
)
|
|
this.lastAction = {
|
|
type: 'volume',
|
|
volume: volumeChange,
|
|
}
|
|
}
|
|
} else if (direction === 'horizontal') {
|
|
if (position.y < 1 / 3 && (position.x < 0.1 || position.x > 0.9)) {
|
|
this.dispatchEvent(new CustomEvent('cancel'))
|
|
this.lastAction = null
|
|
} else {
|
|
let speedFactor = 0
|
|
if (this.startPosition.y < 1 / 3) {
|
|
speedFactor = 0.05
|
|
} else if (this.startPosition.y >= 1 / 3 && this.startPosition.y <= 2 / 3) {
|
|
speedFactor = 0.2
|
|
} else {
|
|
speedFactor = 1
|
|
}
|
|
const modeMap = {
|
|
0.05: ProgressSeekMode.Slow,
|
|
0.2: ProgressSeekMode.Medium,
|
|
1: ProgressSeekMode.Fast,
|
|
}
|
|
|
|
if (distance > 0) {
|
|
const seconds = (distance - this.minSwipeDistance) * speedFactor
|
|
// forwardHandler && forwardHandler(seconds)
|
|
this.dispatchEvent(
|
|
new CustomEvent('progress', {
|
|
detail: {
|
|
mode: modeMap[speedFactor],
|
|
progress: seconds,
|
|
},
|
|
}),
|
|
)
|
|
this.lastAction = {
|
|
type: 'progress',
|
|
seconds,
|
|
}
|
|
} else {
|
|
const seconds = (distance + this.minSwipeDistance) * speedFactor
|
|
this.dispatchEvent(
|
|
new CustomEvent('progress', {
|
|
detail: {
|
|
mode: modeMap[speedFactor],
|
|
progress: seconds,
|
|
},
|
|
}),
|
|
)
|
|
this.lastAction = {
|
|
type: 'progress',
|
|
seconds,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/** 封装触摸手势的监听, 会通知`SwipeAction`触发相应的事件, 事件应绑定到`this.action`中 */
|
|
export class Swiper {
|
|
onTouchStart: (e: TouchEvent) => void = null
|
|
onTouchEnd: (e: TouchEvent) => void = null
|
|
direction: 'vertical' | 'horizontal' = null
|
|
action: SwipeAction
|
|
xDown: number
|
|
yDown: number
|
|
constructor(element: HTMLElement) {
|
|
this.action = new SwipeAction(element)
|
|
element.addEventListener('touchstart', e => {
|
|
this.xDown = e.touches[0].clientX
|
|
this.yDown = e.touches[0].clientY
|
|
this.onTouchStart?.(e)
|
|
this.action.startPosition = createPosition(e, element)
|
|
})
|
|
element.addEventListener(
|
|
'touchmove',
|
|
e => {
|
|
if (!this.xDown || !this.yDown || !e.cancelable) {
|
|
return
|
|
}
|
|
const xUp = e.touches[0].clientX
|
|
const yUp = e.touches[0].clientY
|
|
const position = createPosition(e, element)
|
|
|
|
const xDiff = this.xDown - xUp
|
|
const yDiff = this.yDown - yUp
|
|
|
|
if (!this.direction) {
|
|
this.direction = Math.abs(xDiff) > Math.abs(yDiff) ? 'horizontal' : 'vertical'
|
|
} else if (this.direction === 'vertical') {
|
|
this.action.startAction(this.direction, yDiff, position)
|
|
} else if (this.direction === 'horizontal') {
|
|
this.action.startAction(this.direction, -xDiff, position)
|
|
}
|
|
e.preventDefault()
|
|
},
|
|
{ passive: false },
|
|
)
|
|
element.addEventListener('touchend', e => {
|
|
this.xDown = null
|
|
this.yDown = null
|
|
this.direction = null
|
|
this.onTouchEnd?.(e)
|
|
this.action.startPosition = null
|
|
})
|
|
}
|
|
}
|