仅在加载图像后渲染画布的最佳角度实践

问题描述 投票:0回答:1

我非常了解 Typescript/Javascript,但正在开发我的第一个 Angular 应用程序。它有一个大组件(CAD 绘图板),其中包括一些画布。图像以编程方式加载。

有什么干净、可扩展的方法来确保在尝试使用

ctx.drawImage()
渲染之前加载图像?看起来这应该是一个已解决的问题,但我只找到看起来像拼凑的片段。

有些使用 Promises,但

NgAfterViewInit()
不是异步的。我读过你可以这样声明它,然后在正在加载的 Promise 上调用
await
,但这看起来很难看并且容易出错。

也许像这样的老派东西?

class ImagesLoader {
  private readonly images: Map<string, HTMLImageElement>;
  private readonly pendingActions: ((images: Map<string, HTMLImageElement>) => void)[] = [];
  public readonly errors = new Set<string>();
  private remainingCount: number;

  constructor(urls: string[]) {
    this.remainingCount = urls.length;
    this.images = new Map<string, HTMLImageElement>(
      ((loader) => {
        return urls.map(url => {
          const image = new Image();
          image.onload = () => {
            // On last load, execute pending actions.
            if (--loader.remainingCount == 0) {
              loader.pendingActions.forEach(action => action(loader.images));
              loader.pendingActions.length = 0; // Not necessary, but nice.
            }
          }
          image.onerror = () => loader.errors.add(url);
          image.src = url;
          return [url, image];
        })
      })(this));
  }

  public invokeAfterLoaded(action: (images: Map<string, HTMLImageElement>) => void): void  {
    if (this.remainingCount == 0) {
      action(this.images);
    } else {
      this.pendingActions.push(action);
    }
  }
}

// Start loading the images. Put this in the component constructor.
this.loader = new ImagesLoader([
  'https://www.jqwidgets.com/wp-content/design/i/logo-jqwidgets.svg',
  'https://www.greencastonline.com/assets/img/logo.png']);

// Then we can invoke rendering in NgAfterViewInit, possibly delayed until loaded,
// and re-render as needed with the same loader.
this.loader.invokeAfterLoaded((images) => console.log("first"));
this.loader.invokeAfterLoaded((images) => console.log("second"));

我只是不想选择一个不好的模式,当有更多代码时需要替换它。

angular html5-canvas
1个回答
0
投票

也许使用信号和可观察量?

@Component({
  /* ... */
})
export class MyComponent {
  $urls = input.required<string[]>();
  private images$ = toObservable(this.$urls).pipe(
    map((urls) =>
      urls.map(
        (url) =>
          new Promise((res, rej) => {
            const image = new Image();
            img.onload = () => {
              // Do your thing then
              res(image);
            };
            image.onerror = rej;
            image.src = url;
          }),
      ),
    ),
    map((promises) => Promise.allSettled(promises)),
    switchMap((images) => from(images)),
  );
  $images = toSignal(this.images$, { initialValue: [] });

  constructor() {
    effect(() => {
      const images = this.$images();
      if (!images.length) return;
      console.log('Images loaded', images);
    });
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.