Skip to content

Commit

Permalink
feat: new grammar suggar of parallel
Browse files Browse the repository at this point in the history
  • Loading branch information
sheepbox8646 committed Nov 28, 2024
1 parent ebf16c0 commit 0d3aee1
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 22 deletions.
58 changes: 46 additions & 12 deletions packages/core/src/animation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const linear: TimingFunction = (x) => x
export type Animation<T, A = object> =
(target: T, context: A, progress: number) => void

interface AnimationInstance<T, A> {
export interface AnimationInstance<T, A> {
context: A
animation: Animation<T, A>
startAt?: number
Expand All @@ -18,8 +18,10 @@ interface AnimationInstance<T, A> {

export class AnimationManager<T> {
animations: AnimationInstance<T, any>[] = []
private target: T

constructor(target: T, elapsed: WatchSource<number>) {
this.target = target
watch(elapsed, <A>(elapsed: number) => {
if (this.animations.length === 0) {
return
Expand Down Expand Up @@ -71,6 +73,49 @@ export class AnimationManager<T> {

return this
}

parallel<A>(
...animations: (
| (() => [Animation<T, A>, A])
| ((target: T & { manager: AnimationManager<T> }) => void)
)[]
) {
type ParallelResult = AnimationInstance<T, A> | {
animation: Animation<T, A>
context: A
duration?: number
by?: TimingFunction
}

// Create a non-reactive temporary array
const tempAnimations: AnimationInstance<T, any>[] = []

// Store the original push method
const originalAnimations = this.animations
this.animations = tempAnimations // Temporarily replace the animations array

// Collect all animations
animations.forEach(fn => {
fn({ ...this.target, manager: this } as T & { manager: AnimationManager<T> })

if (tempAnimations.length === 0) {
throw new Error('No animation was registered in parallel callback')
}
})

// Restore original animations array
this.animations = originalAnimations

// Create the parallel animation
this.animate((target, _, progress) => {
// Execute all animations simultaneously with the same progress
tempAnimations.forEach((result: ParallelResult) => {
result.animation(target, result.context, progress)
})
}, { duration: 1 })

return this
}
}

export type AnimationSetup<T, A> = (target: T, context: A) => (progress: number) => void
Expand All @@ -87,17 +132,6 @@ export function defineAnimation<
}
}

export function parallel<
T,
A = object,
>(...animations: Animation<T, A>[]): Animation<T, A> {
return function (target: T, context: A, progress: number) {
for (const animation of animations) {
animation(target, context, progress)
}
}
}

export function registerAnimation<T>(name: string, setup: (...args: any[]) => (animate: AnimationManager<T>) => void): void {
const current = getCurrentInstance()
const { widget } = current?.props as { widget: T & { manager?: AnimationManager<T> } }
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ export * from './player'
export * from './animation'
export * from './widget'
export * from './motion'
export * from './symbols'
1 change: 0 additions & 1 deletion packages/core/src/symbols.ts

This file was deleted.

63 changes: 58 additions & 5 deletions packages/lib/src/widgets/widget.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
import type { AnimationParams, Widget } from '@vue-motion/core'
import type { AnimationManager, AnimationParams, Widget } from '@vue-motion/core'
import { registerAnimation } from '@vue-motion/core'
import { inject } from 'vue'
import { type HasOpacity, type HasOpacityMixin, type HasScale, type HasScaleMixin, type Positional, type PositionalMixin, type Rotatable, type RotatableMixin, type Scalable, type StrokableMixin, moveOnFunction, moveOnPath } from '../animations'
import { fadeIn, fadeOut, fadeTo, move, moveTo, rotate, rotateTo, scale, scaleTo, zoomIn, zoomOut, zoomTo } from '../animations'
import type { Colorable, colorableMixin } from '../animations/color'
import { discolorate } from '../animations/color'
import type { Animation, AnimationInstance, TimingFunction } from '@vue-motion/core'

export type WidgetOptions = Widget & Positional & Scalable & Rotatable & HasOpacity & Colorable
export type WidgetMixin = WidgetOptions & PositionalMixin & RotatableMixin & HasScaleMixin & StrokableMixin & HasOpacityMixin & colorableMixin
export type WidgetOptions = Widget & Positional & Scalable & Rotatable & HasOpacity & Colorable & {
manager?: AnimationManager<any>
}
export type WidgetMixin = WidgetOptions & PositionalMixin & RotatableMixin & HasScaleMixin & StrokableMixin & HasOpacityMixin & colorableMixin & {
parallel: (...animations: ((widget: WidgetMixin & { manager: AnimationManager<any> }) => void)[]) => void
animate: <A>(animation: Animation<Widget, A>, context: {
duration?: AnimationInstance<Widget, A>['duration']
by?: TimingFunction
} & A) => void
once: (animation: (target: Widget) => void) => void
delay: (duration: number) => void
}
export interface Animatable {
animate: <A>(animation: Animation<Widget, A>, context: {
duration?: number
by?: TimingFunction
} & A) => void
once: (animation: (target: Widget) => void) => void
delay: (duration: number) => void
parallel: (...animations: ((widget: WidgetMixin) => void)[]) => void
}

export function widget(options: WidgetOptions) {
const props = {} as {
transform?: string
style?: string
}
} & WidgetMixin

const transform = []
if (options.x || options.y)
transform.push(`translate(${options.x ?? 0},${options.y ?? 0})`)
Expand Down Expand Up @@ -120,8 +141,40 @@ export function widget(options: WidgetOptions) {
duration: params?.duration ?? defaultDuration,
})
})
const animations = inject<(() => void)[]>('ADDITION_ANIMATIONS')
registerAnimation<Widget>('animate', <A>(animation: Animation<Widget, A>, context: {
duration?: AnimationInstance<Widget, A>['duration']
by?: TimingFunction
} & A) => {
return (manager) => manager.animate(animation, context)
})
registerAnimation<Widget>('once', (animation: (target: Widget) => void) => {
return (manager) => manager.once(animation)
})
registerAnimation<Widget>('delay', (duration: number) => {
return (manager) => manager.delay(duration)
})
registerAnimation<WidgetMixin>('parallel',
<A>(...animations: ((widget: WidgetMixin & { manager: AnimationManager<any> }) => void)[]) => {
return (manager: AnimationManager<WidgetMixin>) => {
return manager.parallel(...animations)
}
}
)
const animations = inject<(() => Animation<Widget, any>)[]>('ADDITION_ANIMATIONS')
animations?.forEach((animation) => animation())

return props
}

export interface MoveAnimationContext {
offsetX: number
offsetY: number
duration?: number
by?: TimingFunction
}

export interface RotateAnimationContext {
offset: number
duration?: number
by?: TimingFunction
}
9 changes: 6 additions & 3 deletions test/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { usePlayer, useWidget } from '@vue-motion/core'
import { Motion, Rect, type RectMixin, easeInOutCirc } from '@vue-motion/lib'
import { parallel, usePlayer, useWidget } from '@vue-motion/core'
import { Motion, Rect, type RectMixin, easeInOutCirc, move, rotate } from '@vue-motion/lib'
import { onMounted } from 'vue'
import '@vue-motion/extension-animations'
Expand All @@ -25,7 +25,10 @@ onMounted(() => {
}), {
by: easeInOutCirc,
})
rect.discolorate('border', 'rgba(0, 10, 10, 0.5)')
rect.parallel(
(widget) => widget.move(200, 300),
(widget) => widget.rotate(180),
)
play()
})
</script>
Expand Down

0 comments on commit 0d3aee1

Please sign in to comment.