sanyuelanv-turntable
Version:
优化转盘转动效果 1. 缓动加速转动, 2. 均速转动 **(在没有接收到结果的时候一直均速转动)** 3. **(接受到结果)** 缓动减速转动直到停止
184 lines (183 loc) • 6.32 kB
text/typescript
const getRequestAnimationFrame = (): Function => {
const fixRequestAnimationFrame: Function = (callback): void => { window.setTimeout(callback, 1000 / 60) }
return (window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || fixRequestAnimationFrame)
}
export interface Rect {
width: number,
height: number,
}
export class Turntable {
// 开发使用
private fpsDom: HTMLElement
private devFPS: number = 0
private devLastTs: number = Date.now()
// 正常属性
private lastTs: number = 0
private endTs: number = 0
private fristSecDist: number = 0
private canvas: HTMLCanvasElement
private ctx: CanvasRenderingContext2D
private image: HTMLImageElement
private speed: number = 0
private isStart: boolean = false
private endAngle: number = -1
private translatePos: Rect
private rotateAngle: number = 0
private rotateTime: number = 0
private startAngle: number = 0
private callback: Function
private readyCallback: Function
private ready: boolean = false
/**
*
* @param canvas canvas 元素
* @param imageSrc 图片链接
* @param startAngle 开始弧度
* @param readyCallback 图片加载完成会调
* @param rect canvas 的尺寸。置空则采用图片尺寸
* @param fpsDom 展示 FPS 的demo
*/
constructor(canvas: HTMLCanvasElement, imageSrc: string, startAngle: number, readyCallback: Function, rect: Rect, fpsDom: HTMLElement) {
this.canvas = canvas
this.ctx = canvas.getContext('2d')
this.image = new Image()
this.image.src = imageSrc
this.startAngle = startAngle
this.readyCallback = readyCallback
this.fpsDom = fpsDom
this.image.onload = () => { this.load(rect) }
}
/**
* 开始转动转盘
* @param turnByRoundTime 转 1 圈所用时间(ms)
*/
public start(turnByRoundTime: number): void {
if (!this.ready || this.isStart) return
this.reset()
this.speed = 2 * Math.PI / (turnByRoundTime / 1000)
this.isStart = true
}
/**
* stop 并不能马上把转盘停下来,只会慢慢减速停止。但在帧数稳定的情况下,会在 1 s 内结束
* @param pos 期望转到 弧度
* @param callback 停下后的回调
*/
public stop(angle: number, callback: Function): void {
// 已经设置了减速,动画没完成之前则不能再重新设置
if (!this.ready || this.endAngle >= 0) return
this.callback = callback
this.endAngle = angle
}
/**
* 还原旋转角度
*/
public resetCtxRotate(): void {
this.ctx.restore()
this.ctx.save()
this.rotateAngle = 0
}
private load(rect: Rect): void {
if (rect == null) {
rect = { width: 0, height: 0 }
rect.width = this.image.width
rect.height = this.image.height
}
// 初始化尺寸
this.canvas.width = rect.width * 2
this.canvas.height = rect.height * 2
this.canvas.style.width = rect.width + 'px'
this.canvas.style.height = rect.height + 'px'
const widthHalf = this.canvas.width * 0.5
const heightHalf = this.canvas.height * 0.5
this.translatePos = { width: widthHalf, height: heightHalf }
// 定 形变圆心
this.ctx.translate(widthHalf, heightHalf)
// 定 速度 默认:转一圈用时 (1s)
this.speed = 2 * Math.PI / 0.25
const myRequestAnimationFrame = getRequestAnimationFrame()
const step = () => {
const now: number = Date.now()
const dist: number = this.lastTs == null ? 0 : now - this.lastTs
this.lastTs = now
this.render(dist)
myRequestAnimationFrame(step)
if (this.fpsDom) {
this.devFPS += 1
const timeDist: number = now - this.devLastTs
if (timeDist >= 1000) {
this.devLastTs = now
this.fpsDom.innerHTML = this.devFPS > 60 ? '60' : this.devFPS + ''
this.devFPS = 0
}
}
}
myRequestAnimationFrame(step)
this.ctx.rotate(this.startAngle)
this.ctx.save()
if (this.readyCallback) {
this.ready = true
this.readyCallback()
}
}
private allStop(): void {
if (this.callback) this.callback()
this.resetCtxRotate()
this.ctx.rotate(this.endAngle)
this.reset()
}
private reset(): void {
this.endAngle = -1
this.endTs = 0
this.isStart = false
this.callback = null
this.rotateTime = 0
this.fristSecDist = 0
}
private renderImage(): void {
this.ctx.clearRect(-this.translatePos.width, -this.translatePos.height, this.canvas.width, this.canvas.height)
this.ctx.fillStyle = 'rgba(255, 255, 255, 0)'
this.ctx.fillRect(-this.translatePos.width, -this.translatePos.height, this.canvas.width, this.canvas.height)
this.ctx.drawImage(this.image, 0, 0, this.image.width, this.image.height, -this.translatePos.width, -this.translatePos.height, this.canvas.width, this.canvas.height)
}
private render(ts: number): void {
this.renderImage()
if (this.isStart) {
this.rotateTime += ts
let speed = this.speed
// 前 1 S 速度 t * t * speed = nowSpeed
if (this.rotateTime <= 1000) { speed = this.speed * (this.rotateTime / 1000) * (this.rotateTime / 1000) }
else {
if (this.fristSecDist == 0) {
this.fristSecDist = this.rotateAngle
}
// 结束角度确定之后,开始减速
if (this.endAngle >= 0 && this.endTs == 0) {
this.endTs = this.rotateTime + 1000
this.resetCtxRotate()
this.ctx.rotate(this.endAngle - this.fristSecDist)
}
if (this.endTs > 0) {
// 最后 1 S 速度
const distT = (this.endTs - this.rotateTime)
speed = this.speed * (distT / 1000) * (distT / 1000)
}
}
let angle: number = speed * (ts / 1000)
this.rotateAngle += angle
if (this.isStart && this.endTs != 0) {
if (this.rotateTime >= this.endTs || this.rotateAngle >= this.fristSecDist) {
// 判断这最后一下要不要加上
if ((this.rotateAngle - angle) < this.fristSecDist) {
angle = this.fristSecDist - (this.rotateAngle - angle)
this.rotateAngle += angle
}
else {
this.allStop()
}
}
}
if (!this.isStart) return
this.ctx.rotate(angle)
}
}
}