You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在app-show-on-event组件及子组件视频海报加载完成前显示加载指示器?

实现视频海报加载完成前显示加载指示器的方案

这个需求很常见,尤其是当组件嵌套深、包含大量媒体资源时,提前展示加载态能避免用户看到空白或布局跳动。下面给你几个实用的实现思路,你可以根据项目结构选择:

方案一:在app-show-on-event组件内部管理加载状态

这种方案适合你有权限修改子组件代码的情况,逻辑更内聚:

1. 子组件添加加载状态输出

在app-show-on-event组件中,添加一个输出事件用来通知父组件所有海报已加载完成;同时在组件初始化后,遍历所有带poster的video元素,监听它们的海报加载状态:

import { Component, Output, EventEmitter, AfterViewInit, ElementRef } from '@angular/core';

@Component({
  selector: 'app-show-on-event',
  templateUrl: './show-on-event.component.html',
  styleUrls: ['./show-on-event.component.css']
})
export class ShowOnEventComponent implements AfterViewInit {
  @Output() allPostersLoaded = new EventEmitter<boolean>();
  private totalVideos = 0;
  private loadedCount = 0;

  constructor(private el: ElementRef) {}

  ngAfterViewInit() {
    this.trackVideoPosters();
  }

  private trackVideoPosters() {
    // 获取所有设置了poster的video元素
    const videos = this.el.nativeElement.querySelectorAll('video[poster]');
    this.totalVideos = videos.length;

    // 如果没有视频,直接通知加载完成
    if (this.totalVideos === 0) {
      this.allPostersLoaded.emit(true);
      return;
    }

    // 逐个监听海报加载
    videos.forEach(video => {
      const posterImg = new Image();
      posterImg.src = video.poster;

      // 处理已缓存的海报
      if (posterImg.complete) {
        this.onPosterReady();
      } else {
        posterImg.onload = () => this.onPosterReady();
        // 海报加载失败也视为"就绪"(可根据需求调整为重试或错误提示)
        posterImg.onerror = () => this.onPosterReady();
      }
    });
  }

  private onPosterReady() {
    this.loadedCount++;
    // 所有海报加载完成后通知父组件
    if (this.loadedCount === this.totalVideos) {
      this.allPostersLoaded.emit(true);
    }
  }
}

2. 父组件控制加载态与组件显示

在父组件中,新增一个isLoading变量,初始为true;当触发显示事件时,重置加载状态,等待子组件的加载完成通知:

// 父组件.ts
showThisComponent = false;
isLoading = true;

// 触发组件显示的事件处理函数
triggerComponentDisplay() {
  this.showThisComponent = true;
  this.isLoading = true; // 每次触发时重置加载状态
}

// 接收子组件的加载完成通知
onAllPostersLoaded() {
  this.isLoading = false;
}

父组件模板中,根据isLoading切换加载指示器和目标组件:

<!-- 父组件模板 -->
<div *ngIf="showThisComponent">
  <!-- 替换成你的加载指示器组件 -->
  <div class="loading-spinner" *ngIf="isLoading">加载中...</div>
  <app-show-on-event 
    *ngIf="!isLoading"
    (allPostersLoaded)="onAllPostersLoaded()">
  </app-show-on-event>
</div>

方案二:用指令解耦(无需修改子组件)

如果你不想改动app-show-on-event的代码,可以用Angular指令来监听DOM中的视频海报加载状态,实现解耦:

1. 创建监听海报加载的指令

import { Directive, ElementRef, Output, EventEmitter, AfterViewInit } from '@angular/core';

@Directive({
  selector: '[waitForVideoPosters]'
})
export class WaitForVideoPostersDirective implements AfterViewInit {
  @Output() allPostersLoaded = new EventEmitter<boolean>();
  private totalVideos = 0;
  private loadedCount = 0;

  constructor(private el: ElementRef) {}

  ngAfterViewInit() {
    this.trackAllPosters();
  }

  private trackAllPosters() {
    const videos = this.el.nativeElement.querySelectorAll('video[poster]');
    this.totalVideos = videos.length;

    if (this.totalVideos === 0) {
      this.allPostersLoaded.emit(true);
      return;
    }

    videos.forEach(video => this.monitorSinglePoster(video));
  }

  private monitorSinglePoster(video: HTMLVideoElement) {
    const img = new Image();
    img.src = video.poster;
    if (img.complete) {
      this.onPosterReady();
    } else {
      img.onload = () => this.onPosterReady();
      img.onerror = () => this.onPosterReady();
    }
  }

  private onPosterReady() {
    this.loadedCount++;
    if (this.loadedCount === this.totalVideos) {
      this.allPostersLoaded.emit(true);
    }
  }
}

2. 父组件中使用指令

父组件模板调整为:

<!-- 父组件模板 -->
<div *ngIf="showThisComponent">
  <div class="loading-spinner" *ngIf="isLoading">加载中...</div>
  <div 
    *ngIf="!isLoading"
    waitForVideoPosters
    (allPostersLoaded)="isLoading = false">
    <app-show-on-event></app-show-on-event>
  </div>
</div>

父组件的TS代码和方案一一致,无需修改子组件即可实现需求。

补充:处理动态生成的视频

如果app-show-on-event中的视频是通过*ngFor或异步数据动态生成的,上面的方案可能会漏掉后期添加的视频。这时候可以用MutationObserver监听DOM变化,自动追踪新添加的视频:

在子组件或指令中添加以下代码(以方案一的子组件为例):

private observer: MutationObserver;

ngAfterViewInit() {
  this.trackVideoPosters();
  // 监听DOM子节点变化,处理动态添加的视频
  this.observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
      mutation.addedNodes.forEach(node => {
        if (node.nodeType === Node.ELEMENT_NODE) {
          const newVideos = (node as Element).querySelectorAll('video[poster]');
          newVideos.forEach(video => {
            this.totalVideos++;
            this.monitorSinglePoster(video); // 复用之前的监听方法
          });
        }
      });
    });
  });

  this.observer.observe(this.el.nativeElement, {
    childList: true,
    subtree: true
  });
}

ngOnDestroy() {
  // 组件销毁时断开监听,避免内存泄漏
  if (this.observer) {
    this.observer.disconnect();
  }
}

// 把之前的trackVideoPosters里的单个视频监听抽成方法
private monitorSinglePoster(video: HTMLVideoElement) {
  const posterImg = new Image();
  posterImg.src = video.poster;
  if (posterImg.complete) {
    this.onPosterReady();
  } else {
    posterImg.onload = () => this.onPosterReady();
    posterImg.onerror = () => this.onPosterReady();
  }
}

这样就能覆盖动态添加的视频元素,确保所有海报都被追踪到。

内容的提问来源于stack exchange,提问作者sanjihan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:26:16