Motion Canvas 事件系统全攻略:EventDispatcher 与异步处理

Motion Canvas 事件系统全攻略:EventDispatcher 与异步处理

【免费下载链接】motion-canvas Visualize Your Ideas With Code 【免费下载链接】motion-canvas 项目地址: https://gitcode.com/gh_mirrors/mo/motion-canvas

Motion Canvas 作为一款基于代码的动画创作工具,其事件系统是实现交互逻辑和状态管理的核心组件。本文将深入解析 EventDispatcher(事件调度器)与异步事件处理机制,帮助开发者构建响应式动画场景。

事件系统核心架构

Motion Canvas 事件系统采用分层设计,核心类位于 packages/core/src/events/ 目录下,主要包含三大基础组件:

  • EventDispatcherBase:抽象基类,定义事件订阅与通知的核心接口
  • EventDispatcher:同步事件调度器,用于处理即时响应的事件流
  • AsyncEventDispatcher:异步事件调度器,支持基于 Promise 的异步处理流程

THE 0TH POSITION OF THE ORIGINAL IMAGE

同步事件处理:EventDispatcher 实战

EventDispatcher 是处理同步事件的基础类,通过 dispatch 方法触发事件并立即通知所有订阅者。其核心实现位于 packages/core/src/events/EventDispatcher.ts,关键代码如下:

export class EventDispatcher<T> extends EventDispatcherBase<T> {
  public dispatch(value: T) {
    this.notifySubscribers(value);
  }
}

基础用法:创建与订阅事件

创建自定义事件的标准流程包含三个步骤:定义事件调度器、暴露订阅接口、触发事件通知:

class AnimationPlayer {
  // 创建私有事件调度器
  private frameUpdated = new EventDispatcher<number>();
  
  // 暴露订阅接口
  public get onFrameUpdated(): SubscribableEvent<number> {
    return this.frameUpdated.subscribable;
  }
  
  // 触发事件
  private update(currentFrame: number) {
    this.frameUpdated.dispatch(currentFrame);
  }
}

// 使用示例
const player = new AnimationPlayer();
player.onFrameUpdated.subscribe(frame => {
  console.log(`当前帧: ${frame}`);
});

事件生命周期管理

EventDispatcher 提供完整的订阅管理机制,支持单次订阅、取消订阅等高级操作:

// 单次订阅(自动取消)
player.onFrameUpdated.once(frame => {
  console.log(`初始帧: ${frame}`);
});

// 带标识符的订阅
const subscription = player.onFrameUpdated.subscribe(frame => {
  console.log(`带标识订阅: ${frame}`);
});

// 取消订阅
subscription.unsubscribe();

异步事件处理:AsyncEventDispatcher

对于需要异步处理的场景(如网络请求、文件读写),AsyncEventDispatcher 提供基于 Promise 的事件流控制,其定义位于 packages/core/src/events/AsyncEventDispatcher.ts

核心特性

  • 异步通知:所有订阅者函数返回 Promise
  • 顺序执行:通过 Promise.all 等待所有处理完成
  • 类型安全:支持泛型参数的异步事件处理

基础实现

export class AsyncEventDispatcher<T> extends EventDispatcherBase<
  T,
  AsyncEventHandler<T>
> {
  public async dispatch(value: T): Promise<void> {
    await Promise.all(this.notifySubscribers(value));
  }
}

异步事件应用场景

场景1:动画资源预加载

class AssetLoader {
  private assetsLoaded = new AsyncEventDispatcher<string[]>();
  
  public get onAssetsLoaded(): SubscribableAsyncEvent<string[]> {
    return this.assetsLoaded.subscribable;
  }
  
  public async loadAssets(paths: string[]): Promise<void> {
    const loadedAssets = await Promise.all(
      paths.map(path => loadResource(path))
    );
    // 等待所有订阅者处理完成后再继续
    await this.assetsLoaded.dispatch(loadedAssets);
    console.log("所有资源处理完成");
  }
}

场景2:多步骤动画序列

// 订阅异步事件
player.onAnimationComplete.subscribe(async (result) => {
  await saveAnimationState(result);
  await analytics.trackEvent("animation_completed");
});

// 调度异步事件
await player.playAnimation(sequence);
// 确保动画完成且所有后续处理完成
console.log("动画流程完全结束");

高级应用模式

事件转发与组合

通过事件转发可以实现复杂的事件流控制,例如将多个组件的事件汇总到统一的事件总线:

class EventBus {
  private buttonClicks = new EventDispatcher<MouseEvent>();
  
  constructor(buttons: Button[]) {
    buttons.forEach(button => {
      // 转发按钮点击事件
      button.onClick.subscribe(event => {
        this.buttonClicks.dispatch(event);
      });
    });
  }
}

类型安全的事件设计

利用 TypeScript 泛型特性,可以构建类型安全的事件系统:

// 定义事件类型
type ApplicationEvents = {
  "user.login": { username: string };
  "data.loaded": { timestamp: number };
};

// 类型安全的事件调度器
class TypedEventDispatcher<T extends Record<string, unknown>> {
  private dispatchers = new Map<keyof T, EventDispatcher<T[keyof T]>>();
  
  on<K extends keyof T>(event: K, handler: (data: T[K]) => void) {
    const dispatcher = this.getDispatcher(event);
    return dispatcher.subscribe(handler);
  }
  
  dispatch<K extends keyof T>(event: K, data: T[K]) {
    this.getDispatcher(event).dispatch(data);
  }
  
  private getDispatcher<K extends keyof T>(event: K) {
    if (!this.dispatchers.has(event)) {
      this.dispatchers.set(event, new EventDispatcher<T[K]>());
    }
    return this.dispatchers.get(event)!;
  }
}

性能优化策略

  1. 事件节流:对于高频事件(如鼠标移动),可通过时间戳控制触发频率
class ThrottledEventDispatcher<T> extends EventDispatcher<T> {
  private lastDispatch = 0;
  private interval: number;
  
  constructor(intervalMs: number) {
    super();
    this.interval = intervalMs;
  }
  
  public dispatch(value: T) {
    const now = Date.now();
    if (now - this.lastDispatch > this.interval) {
      this.lastDispatch = now;
      super.dispatch(value);
    }
  }
}
  1. 事件池化:对于大量相似事件,可复用事件对象减少内存分配

常见问题与解决方案

循环引用导致的内存泄漏

问题:事件订阅者持有调度器对象引用,导致无法被垃圾回收。

解决方案:使用弱引用或提供显式的取消订阅机制:

// 推荐模式:使用订阅对象取消订阅
const subscription = dispatcher.subscribe(handler);
// 不再需要时取消订阅
subscription.unsubscribe();

异步错误处理

问题:AsyncEventDispatcher 中单个订阅者抛出的错误会影响整个事件流。

解决方案:实现错误隔离机制:

public async dispatch(value: T): Promise<void> {
  const handlers = this.notifySubscribers(value);
  await Promise.all(handlers.map(p => 
    p.catch(error => {
      console.error("事件处理错误:", error);
    })
  ));
}

总结与最佳实践

Motion Canvas 事件系统通过分层设计提供了灵活的事件处理能力,在实际开发中建议遵循以下最佳实践:

  1. 接口隔离:始终通过 subscribable 属性暴露事件,而非直接暴露调度器

    // 推荐
    public get onValueChanged() { return this.dispatcher.subscribable; }
    
    // 不推荐
    public dispatcher = new EventDispatcher();
    
  2. 类型安全:为事件数据定义明确的 TypeScript 接口

  3. 异步边界:明确区分同步/异步事件场景,避免混用导致的逻辑混乱

  4. 资源管理:对于临时订阅,确保在组件销毁时取消订阅

完整的事件系统源码可参考 packages/core/src/events/ 目录,更多高级用法可研究官方示例中的 交互场景实现。

通过掌握 EventDispatcher 与 AsyncEventDispatcher 的使用技巧,开发者可以构建出响应式强、可维护性高的动画交互系统,为 Motion Canvas 项目增添丰富的交互体验。

【免费下载链接】motion-canvas Visualize Your Ideas With Code 【免费下载链接】motion-canvas 项目地址: https://gitcode.com/gh_mirrors/mo/motion-canvas

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值