mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
canvas animate inactive cursors.
This commit is contained in:
parent
b621c685c1
commit
6afa3c68de
@ -16,10 +16,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vue, Component, Ref, Prop, Watch } from 'vue-property-decorator'
|
import { Vue, Component, Ref, Prop, Watch } from 'vue-property-decorator'
|
||||||
|
|
||||||
import { Cursor, Session } from './types/state'
|
import { SessionCursors, Cursor, Session } from './types/state'
|
||||||
import { InactiveCursorDrawFunction, Dimension } from './types/cursors'
|
import { InactiveCursorDrawFunction, Dimension } from './types/cursors'
|
||||||
|
import { getMovementXYatPercent } from './utils/canvas-movement'
|
||||||
|
|
||||||
const CANVAS_SCALE = 2
|
const CANVAS_SCALE = 2
|
||||||
|
const POS_INTERVAL_MS = 750
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
name: 'neko-cursors',
|
name: 'neko-cursors',
|
||||||
@ -41,7 +43,7 @@
|
|||||||
private readonly canvasSize!: Dimension
|
private readonly canvasSize!: Dimension
|
||||||
|
|
||||||
@Prop()
|
@Prop()
|
||||||
private readonly cursors!: Cursor[]
|
private readonly cursors!: SessionCursors[]
|
||||||
|
|
||||||
@Prop()
|
@Prop()
|
||||||
private readonly cursorDraw!: InactiveCursorDrawFunction | null
|
private readonly cursorDraw!: InactiveCursorDrawFunction | null
|
||||||
@ -57,49 +59,82 @@
|
|||||||
const { width, height } = this._overlay.getBoundingClientRect()
|
const { width, height } = this._overlay.getBoundingClientRect()
|
||||||
this._overlay.width = width * CANVAS_SCALE
|
this._overlay.width = width * CANVAS_SCALE
|
||||||
this._overlay.height = height * CANVAS_SCALE
|
this._overlay.height = height * CANVAS_SCALE
|
||||||
|
|
||||||
|
this._last_points = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeDestroy() {}
|
beforeDestroy() {}
|
||||||
|
|
||||||
private canvasRequestedFrame = false
|
|
||||||
|
|
||||||
@Watch('canvasSize')
|
@Watch('canvasSize')
|
||||||
onCanvasSizeChange({ width, height }: Dimension) {
|
onCanvasSizeChange({ width, height }: Dimension) {
|
||||||
this._overlay.width = width * CANVAS_SCALE
|
this._overlay.width = width * CANVAS_SCALE
|
||||||
this._overlay.height = height * CANVAS_SCALE
|
this._overlay.height = height * CANVAS_SCALE
|
||||||
this.canvasRequestRedraw()
|
}
|
||||||
|
|
||||||
|
// start as undefined to prevent jumping
|
||||||
|
private _prev_time!: number
|
||||||
|
private _percent!: number
|
||||||
|
private _points!: SessionCursors[]
|
||||||
|
private _last_points!: Record<string, Cursor>
|
||||||
|
|
||||||
|
canvasAnimate(now: number = NaN) {
|
||||||
|
// request another frame
|
||||||
|
if (this._percent <= 1) requestAnimationFrame(this.canvasAnimate)
|
||||||
|
|
||||||
|
// calculate factor
|
||||||
|
const delta = (now - this._prev_time) / POS_INTERVAL_MS
|
||||||
|
this._prev_time = now
|
||||||
|
|
||||||
|
// skip very first delta to prevent jumping
|
||||||
|
if (isNaN(delta)) return
|
||||||
|
|
||||||
|
// set the animation position
|
||||||
|
this._percent += delta
|
||||||
|
|
||||||
|
this.canvasClear()
|
||||||
|
|
||||||
|
// scale
|
||||||
|
this._ctx.setTransform(CANVAS_SCALE, 0, 0, CANVAS_SCALE, 0, 0)
|
||||||
|
|
||||||
|
// draw current position
|
||||||
|
for (const p of this._points) {
|
||||||
|
const { x, y } = getMovementXYatPercent(p.cursors, this._percent)
|
||||||
|
this.canvasRedraw(x, y, p.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Watch('cursors')
|
@Watch('cursors')
|
||||||
@Watch('cursorDraw')
|
canvasSetPosition(e: SessionCursors[]) {
|
||||||
canvasRequestRedraw() {
|
console.log('consuming', e)
|
||||||
// skip rendering if there is already in progress
|
|
||||||
if (this.canvasRequestedFrame) return
|
|
||||||
|
|
||||||
// request animation frame from a browser
|
// clear on no cursor
|
||||||
this.canvasRequestedFrame = true
|
if (e.length == 0) {
|
||||||
window.requestAnimationFrame(() => {
|
this._last_points = {}
|
||||||
this.canvasRedraw()
|
this.canvasClear()
|
||||||
this.canvasRequestedFrame = false
|
return
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canvasRedraw() {
|
// create points for animation
|
||||||
if (this.screenSize == null) return
|
this._points = []
|
||||||
|
for (const { id, cursors } of e) {
|
||||||
|
let pos = { id } as SessionCursors
|
||||||
|
if (id in this._last_points) {
|
||||||
|
pos.cursors = [this._last_points[id], ...cursors]
|
||||||
|
} else {
|
||||||
|
pos.cursors = [...cursors]
|
||||||
|
}
|
||||||
|
this._last_points[id] = cursors[cursors.length - 1]
|
||||||
|
this._points.push(pos)
|
||||||
|
}
|
||||||
|
|
||||||
// clear drawings
|
const startAnimation = this._percent > 1 || this._percent == 0
|
||||||
this.canvasClear()
|
this._percent = 0
|
||||||
|
if (startAnimation) this.canvasAnimate()
|
||||||
|
}
|
||||||
|
|
||||||
|
canvasRedraw(x: number, y: number, id: string) {
|
||||||
// get intrinsic dimensions
|
// get intrinsic dimensions
|
||||||
let { width, height } = this.canvasSize
|
let { width, height } = this.canvasSize
|
||||||
this._ctx.setTransform(CANVAS_SCALE, 0, 0, CANVAS_SCALE, 0, 0)
|
|
||||||
|
|
||||||
// draw cursors
|
|
||||||
for (let { id, x, y } of this.cursors) {
|
|
||||||
// ignore own cursor
|
|
||||||
if (id == this.sessionId) continue
|
|
||||||
|
|
||||||
// get cursor position
|
|
||||||
x = Math.round((x / this.screenSize.width) * width)
|
x = Math.round((x / this.screenSize.width) * width)
|
||||||
y = Math.round((y / this.screenSize.height) * height)
|
y = Math.round((y / this.screenSize.height) * height)
|
||||||
|
|
||||||
@ -109,7 +144,7 @@
|
|||||||
// use custom draw function, if available
|
// use custom draw function, if available
|
||||||
if (this.cursorDraw) {
|
if (this.cursorDraw) {
|
||||||
this.cursorDraw(this._ctx, x, y, cursorTag)
|
this.cursorDraw(this._ctx, x, y, cursorTag)
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this._ctx.save()
|
this._ctx.save()
|
||||||
@ -125,7 +160,6 @@
|
|||||||
this._ctx.fillText(cursorTag, x, y)
|
this._ctx.fillText(cursorTag, x, y)
|
||||||
this._ctx.restore()
|
this._ctx.restore()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
canvasClear() {
|
canvasClear() {
|
||||||
const { width, height } = this._overlay
|
const { width, height } = this._overlay
|
||||||
|
@ -128,11 +128,15 @@ export interface Session {
|
|||||||
|
|
||||||
export interface Cursors {
|
export interface Cursors {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
list: Cursor[]
|
list: SessionCursors[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SessionCursors {
|
||||||
|
id: string
|
||||||
|
cursors: Cursor[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Cursor {
|
export interface Cursor {
|
||||||
id: string
|
|
||||||
x: number
|
x: number
|
||||||
y: number
|
y: number
|
||||||
}
|
}
|
||||||
|
47
src/component/utils/canvas-movement.ts
Normal file
47
src/component/utils/canvas-movement.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
export interface Point {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// movement: percent is 0-1
|
||||||
|
export function getMovementXYatPercent(points: Point[], percent: number): Point {
|
||||||
|
if (points.length == 0) { console.error('no points specified'); return { x:0, y:0 } }
|
||||||
|
if (points.length == 1) return points[0]
|
||||||
|
if (points.length == 2) return getLineXYatPercent(points[0], points[1], percent)
|
||||||
|
if (points.length == 3) return getQuadraticBezierXYatPercent(points[0], points[1], points[2], percent)
|
||||||
|
if (points.length == 4) return getCubicBezierXYatPercent(points[0], points[1], points[2], points[3], percent)
|
||||||
|
console.error('max 4 points supported'); return points[4]
|
||||||
|
}
|
||||||
|
|
||||||
|
// line: percent is 0-1
|
||||||
|
export function getLineXYatPercent(startPt: Point, endPt: Point, percent: number) : Point {
|
||||||
|
return {
|
||||||
|
x: startPt.x + (endPt.x - startPt.x) * percent,
|
||||||
|
y: startPt.y + (endPt.y - startPt.y) * percent,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// quadratic bezier: percent is 0-1
|
||||||
|
export function getQuadraticBezierXYatPercent(startPt: Point, controlPt: Point, endPt: Point, percent: number): Point {
|
||||||
|
return {
|
||||||
|
x: Math.pow(1 - percent, 2) * startPt.x + 2 * (1 - percent) * percent * controlPt.x + Math.pow(percent, 2) * endPt.x,
|
||||||
|
y: Math.pow(1 - percent, 2) * startPt.y + 2 * (1 - percent) * percent * controlPt.y + Math.pow(percent, 2) * endPt.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cubic bezier percent is 0-1
|
||||||
|
export function getCubicBezierXYatPercent(startPt: Point, controlPt1: Point, controlPt2: Point, endPt: Point, percent: number): Point {
|
||||||
|
return {
|
||||||
|
x: cubicN(percent, startPt.x, controlPt1.x, controlPt2.x, endPt.x),
|
||||||
|
y: cubicN(percent, startPt.y, controlPt1.y, controlPt2.y, endPt.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cubic helper formula at percent distance
|
||||||
|
function cubicN(pct: number, a: number, b: number, c: number, d: number): number {
|
||||||
|
var t2 = pct * pct;
|
||||||
|
var t3 = t2 * pct;
|
||||||
|
return a + (-a * 3 + pct * (3 * a - a * pct)) * pct + (3 * b + pct * (-6 * b + b * 3 * pct)) * pct + (c * 3 - c * 3 * pct) * t2 + d * t3;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user