Skip to content

Commit

Permalink
refactor(30%): animation system.
Browse files Browse the repository at this point in the history
  • Loading branch information
sheepbox8646 committed Nov 16, 2024
1 parent f68082f commit 80c1fd4
Show file tree
Hide file tree
Showing 19 changed files with 169 additions and 104 deletions.
42 changes: 41 additions & 1 deletion packages/core/src/animation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,47 @@ export type TimingFunction = (x: number) => number
export const linear: TimingFunction = x => x

export type Animation<T extends object, A extends object = object> =
(target: T, context: A, progress: number) => void
(target: T, context: A, progress: number) => void;

type BuildParams<T, Keys extends Array<keyof T>, Optional extends boolean = false> = Keys extends []
? [parameters?: Record<string, any>]
: Keys extends [infer First extends keyof T]
? [Optional extends true ? T[First] | undefined : T[First], parameters?: Record<string, any>]
: Keys extends [infer First extends keyof T, ...infer Rest extends Array<keyof T>]
? [Optional extends true ? T[First] | undefined : T[First], ...BuildParams<T, Rest, Optional>, parameters?: Record<string, any>]
: never

/**
* If
* ```ts
* interface A {
* x: number
* y: number
* z: number
* }
* ```
* Then
* ```ts
* type B = Methodize<A, {
* move: ['x', 'y'],
* move3d: ['x', 'y', 'z']
* }>
* ```
* is
* ```ts
* interface B {
* move: (x: number, y: number, parameters?: Record<string, any>) => void
* move3d: (x: number, y: number, z: number, parameters?: Record<string, any>) => void
* }
* ```
*/
export type Methodize<
T,
Methods extends Record<string, Array<keyof T>>,
Optional extends boolean = false
> = {
[K in keyof Methods]: (...args: BuildParams<T, Methods[K], Optional>) => void
}

interface AnimationInstance<T extends object, A extends object> {
context: A
Expand Down
30 changes: 18 additions & 12 deletions packages/core/src/widget.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import type { InjectionKey, Reactive, Ref, Slots } from 'vue'
import { getCurrentInstance, inject, onMounted, provide, reactive, ref, useSlots, watchEffect } from 'vue'
import type { InjectionKey, Ref, Slots } from 'vue'
import { getCurrentInstance, inject, onMounted, provide, ref, useSlots, watchEffect } from 'vue'

const childWidgets: InjectionKey<Ref<Widget[]>> = Symbol('child-widgets')

export interface Widget<T extends Widget = any> {
ref?: string | InjectionKey<Reactive<T>>
export interface Widget<T = any> {
ref?: string | InjectionKey<T>
element?: SVGElement
range?: DOMRect
slots?: Slots
children?: Widget[]
children?: Widget<T>[]
}

export function useWidget<T extends Widget>(ref: string | InjectionKey<T>): T {
const widget = reactive({})
const widget = {}
provide(ref, widget)
return widget as T
}
Expand All @@ -23,19 +23,25 @@ export function useChildren(): Ref<Widget[]> {
return children
}

export function defineWidget<T extends Widget>(props: T, root?: SVGElement): T {
export function defineWidget<T extends Widget>(
props: T,
root?: SVGElement,
): T {
if (props.ref) {
const widget = inject<Reactive<T>>(props.ref)
const widget = inject<T>(props.ref)
if (widget) {
watchEffect(() => Object.assign(widget, props))
watchEffect(() => Object.assign(
widget,
props,
))
onMounted(() => {
widget.element = root ?? getCurrentInstance()?.proxy?.$el.parentElement
widget.range = widget.element?.getBoundingClientRect()
widget.slots = useSlots()
inject(childWidgets)?.value.push(widget)
inject(childWidgets)!.value?.push(widget as T)
})
return widget
return widget
}
}
return props as Reactive<T>
return props as T
}
48 changes: 25 additions & 23 deletions packages/lib/src/animations/circle-indcate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,33 @@ import { defineAnimation } from '@vue-motion/core'
import { interpolator } from '../interpolator'
import type { Positional } from './move'

export const circleIndicate = (defineAnimation<{
export const circleIndicate = (defineAnimation<Positional & Widget, {
color?: string
scale?: number
circle?: SVGCircleElement
}, Positional & Widget>((context, progress) => {
if (progress === 0) {
context.circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle')
context.circle.setAttribute('fill', 'none')
context.circle.setAttribute('stroke', context.color || 'yellow')
context.circle.setAttribute('stroke-width', '5')
context.circle.setAttribute('cx', (context.target.x ?? 0).toString())
context.circle.setAttribute('cy', (context.target.y ?? 0).toString())
const bounds = context.target.element!.getBoundingClientRect()
context.circle.setAttribute('r', Math.max(bounds.width / 2, bounds.height / 2).toString())
context.target.element!.appendChild(context.circle)
}
if (progress >= 0 && progress <= 0.3) {
context.circle!.style.opacity = interpolator(0, 1, 1 - progress).toString()
context.circle!.removeAttribute('transform')
context.circle!.setAttribute('transform', `scale(${interpolator(0.5, context.scale || 1.5, 1 - progress)})`)
//
}
if (progress >= 0.7 && progress <= 1) {
context.circle!.style.opacity = interpolator(0, 1, 1 - progress).toString()
context.circle!.removeAttribute('transform')
context.circle!.setAttribute('transform', `scale(${interpolator(0.5, context.scale || 1.5, progress)})`)
}>((target, context) => {
context.circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle')
context.circle.setAttribute('fill', 'none')
context.circle.setAttribute('stroke', context.color || 'yellow')
context.circle.setAttribute('stroke-width', '5')
context.circle.setAttribute('cx', (target.x ?? 0).toString())
context.circle.setAttribute('cy', (target.y ?? 0).toString())
const bounds = target.element!.getBoundingClientRect()
context.circle.setAttribute('r', Math.max(bounds.width / 2, bounds.height / 2).toString())
target.element!.appendChild(context.circle)
return (progress: number) => {
if (progress >= 0 && progress <= 0.3) {
context.circle!.style.opacity = interpolator(0, 1, 1 - progress).toString()
context.circle!.removeAttribute('transform')
context.circle!.setAttribute('transform', `scale(${interpolator(0.5, context.scale || 1.5, 1 - progress)})`)
//
}
if (progress >= 0.7 && progress <= 1) {
context.circle!.style.opacity = interpolator(0, 1, 1 - progress).toString()
context.circle!.removeAttribute('transform')
context.circle!.setAttribute('transform', `scale(${interpolator(0.5, context.scale || 1.5, progress)})`)
}
}
}, (target, context) => {
target.element!.removeChild(context.circle!)
}))
8 changes: 5 additions & 3 deletions packages/lib/src/animations/fade.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import type { Methodize } from '@vue-motion/core'
import { defineAnimation, withDefaults } from '@vue-motion/core'
import { interpolator } from '../interpolator'

export interface HasOpacity {
opacity?: number
}
export type HasOpacityMethods = Methodize<HasOpacity, { fadeTo: ['opacity'] }>

export const fadeTo = defineAnimation<{
from?: number
to?: number
}, HasOpacity>((context, progress) => {
if (progress === 0) {
context.from ??= context.target.opacity ?? 1
context.to ??= context.target.opacity ?? 1
context.from ??= target.opacity ?? 1
context.to ??= target.opacity ?? 1
}
context.target.opacity = interpolator(context.from!, context.to!, progress)
target.opacity = interpolator(context.from!, context.to!, progress)
})

export const fadeOut = withDefaults(fadeTo, { to: 0 })
Expand Down
2 changes: 1 addition & 1 deletion packages/lib/src/animations/flash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const flash = defineAnimation<FlashConfig, Widget & FlashConfig>((context
line.setAttribute('stroke', color)
line.setAttribute('stroke-width', lineStrokeWidth.toString())
line.setAttribute('transform', ` translate(${x}, ${y}) rotate(${(angle * 180) / Math.PI}, ${0}, ${0}) translate(${flashRadius}, 0)`)
context.target.element?.appendChild(line)
target.element?.appendChild(line)
context.lines?.push(line)
}
}
Expand Down
6 changes: 3 additions & 3 deletions packages/lib/src/animations/focus-on.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ export const focusOn = defineAnimation<{
}, Widget & Positional>((context, progress) => {
if (progress === 0) {
context.circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle')
context.target.element?.appendChild(context.circle)
target.element?.appendChild(context.circle)

context.circle.setAttribute('cx', context.target.x!.toString())
context.circle.setAttribute('cy', context.target.y!.toString())
context.circle.setAttribute('cx', target.x!.toString())
context.circle.setAttribute('cy', target.y!.toString())
context.circle.setAttribute('fill', 'rgba(255,255,255,0.5)')
}
context.circle!.setAttribute('r', interpolator(context.offset ?? 1000, 0, progress).toString())
Expand Down
4 changes: 2 additions & 2 deletions packages/lib/src/animations/grow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ export interface Growable {

export const grow = defineAnimation<object, Growable>(
(context, progress) => {
context.target.progress = progress
target.progress = progress
},
)

export const destory = defineAnimation<object, Growable>(
(context, progress) => {
context.target.progress = 1 - progress
target.progress = 1 - progress
},
)
8 changes: 4 additions & 4 deletions packages/lib/src/animations/indicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import type { Scalable } from './scale'

export const indicate = defineAnimation<object, Scalable>((context, progress) => {
if (progress >= 0 && progress <= 0.3) {
context.target.scaleX = interpolator(1, 1.3, progress / 0.3)
context.target.scaleY = interpolator(1, 1.3, progress / 0.3)
target.scaleX = interpolator(1, 1.3, progress / 0.3)
target.scaleY = interpolator(1, 1.3, progress / 0.3)
}
if (progress >= 0.7 && progress <= 1) {
context.target.scaleX = interpolator(1.3, 1, (progress - 0.7) / 0.3)
context.target.scaleY = interpolator(1.3, 1, (progress - 0.7) / 0.3)
target.scaleX = interpolator(1.3, 1, (progress - 0.7) / 0.3)
target.scaleY = interpolator(1.3, 1, (progress - 0.7) / 0.3)
}
})
22 changes: 12 additions & 10 deletions packages/lib/src/animations/move.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { Methodize } from '@vue-motion/core'
import { defineAnimation } from '@vue-motion/core'
import { interpolator } from '../interpolator'

export interface Positional {
x?: number
y?: number
}
export type PositionalMethods = Methodize<Positional, { moveTo: ['x', 'y'] }>

export const move = defineAnimation<{
fromX?: number
Expand All @@ -13,13 +15,13 @@ export const move = defineAnimation<{
offsetY?: number
}, Positional>((context, progress) => {
if (progress === 0) {
context.fromX ??= context.target.x ?? 0
context.fromY ??= context.target.y ?? 0
context.fromX ??= target.x ?? 0
context.fromY ??= target.y ?? 0
context.offsetX ??= 0
context.offsetX ??= 0
}
context.target.x = context.fromX! + interpolator(0, context.offsetX!, progress)
context.target.y = context.fromY! + interpolator(0, context.offsetY!, progress)
target.x = context.fromX! + interpolator(0, context.offsetX!, progress)
target.y = context.fromY! + interpolator(0, context.offsetY!, progress)
})

export const moveTo = defineAnimation<{
Expand All @@ -29,11 +31,11 @@ export const moveTo = defineAnimation<{
toY?: number
}, Positional>((context, progress) => {
if (progress === 0) {
context.fromX ??= context.target.x ?? 0
context.fromY ??= context.target.y ?? 0
context.toX ??= context.target.x ?? 0
context.toY ??= context.target.y ?? 0
context.fromX ??= target.x ?? 0
context.fromY ??= target.y ?? 0
context.toX ??= target.x ?? 0
context.toY ??= target.y ?? 0
}
context.target.x = interpolator(context.fromX!, context.toX!, progress)
context.target.y = interpolator(context.fromY!, context.toY!, progress)
target.x = interpolator(context.fromX!, context.toX!, progress)
target.y = interpolator(context.fromY!, context.toY!, progress)
})
6 changes: 3 additions & 3 deletions packages/lib/src/animations/ripple-out.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ export const rippleOut = defineAnimation<{
let circle
if (progress === 0) {
circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle')
context.target.element?.appendChild(circle)
target.element?.appendChild(circle)

circle.setAttribute('cx', context.target.x!.toString())
circle.setAttribute('cy', context.target.y!.toString())
circle.setAttribute('cx', target.x!.toString())
circle.setAttribute('cy', target.y!.toString())
circle.setAttribute('fill', 'rgba(255,255,255,0.5)')
}
circle!.setAttribute('r', interpolator(0, context.offset ?? 1000, progress).toString())
Expand Down
10 changes: 6 additions & 4 deletions packages/lib/src/animations/rotate.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import type { Methodize } from '@vue-motion/core'
import { defineAnimation } from '@vue-motion/core'
import { interpolator } from '../interpolator'

export interface Rotatable {
rotation?: number
}
export type RotatableMethods = Methodize<Rotatable, { rotateTo: ['rotation'] }>

export const rotate = defineAnimation<{
from?: number
offset?: number
}, Rotatable>((context, progress) => {
if (progress === 0) {
context.from ??= context.target.rotation ?? 0
context.from ??= target.rotation ?? 0
}
context.target.rotation = context.from! + interpolator(0, context.offset!, progress)
target.rotation = context.from! + interpolator(0, context.offset!, progress)
})

export const rotateTo = defineAnimation<{
from?: number
to?: number
}, Rotatable>((context, progress) => {
if (progress === 0) {
context.from ??= context.target.rotation ?? 0
context.from ??= target.rotation ?? 0
}
context.target.rotation = interpolator(context.from!, context.to!, progress)
target.rotation = interpolator(context.from!, context.to!, progress)
})
26 changes: 14 additions & 12 deletions packages/lib/src/animations/scale.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import type { Methodize } from '@vue-motion/core'
import { defineAnimation } from '@vue-motion/core'
import { interpolator } from '../interpolator'

export interface Scalable {
scaleX?: number
scaleY?: number
}
export type ScalableMethods = Methodize<Scalable, { scaleTo: ['scaleX', 'scaleY'] }>

export const scale = defineAnimation<{
export const scale = defineAnimation<Scalable,{
fromX?: number
fromY?: number
offsetX?: number
offsetY?: number
}, Scalable>((context, progress) => {
}>((context, progress) => {
if (progress === 0) {
context.fromX ??= context.target.scaleX ?? 1
context.fromY ??= context.target.scaleY ?? 1
context.fromX ??= target.scaleX ?? 1
context.fromY ??= target.scaleY ?? 1
context.offsetX ??= 0
context.offsetX ??= 0
}
context.target.scaleX = context.fromX! * interpolator(1, context.offsetX!, progress)
context.target.scaleY = context.fromY! * interpolator(1, context.offsetY!, progress)
target.scaleX = context.fromX! * interpolator(1, context.offsetX!, progress)
target.scaleY = context.fromY! * interpolator(1, context.offsetY!, progress)
})

export const scaleTo = defineAnimation<{
Expand All @@ -29,11 +31,11 @@ export const scaleTo = defineAnimation<{
toY?: number
}, Scalable>((context, progress) => {
if (progress === 0) {
context.fromX ??= context.target.scaleX ?? 1
context.fromY ??= context.target.scaleY ?? 1
context.toX ??= context.target.scaleX ?? 1
context.toY ??= context.target.scaleY ?? 1
context.fromX ??= target.scaleX ?? 1
context.fromY ??= target.scaleY ?? 1
context.toX ??= target.scaleX ?? 1
context.toY ??= target.scaleY ?? 1
}
context.target.scaleX = interpolator(context.fromX!, context.toX!, progress)
context.target.scaleY = interpolator(context.fromY!, context.toY!, progress)
target.scaleX = interpolator(context.fromX!, context.toX!, progress)
target.scaleY = interpolator(context.fromY!, context.toY!, progress)
})
Loading

0 comments on commit 80c1fd4

Please sign in to comment.