ScreenfullFullscreenAPISkill screenfull-fullscreen-api

这个技能提供了使用 screenfull 库在 Angular 项目中实现全屏功能的详细指南,包括基本全屏切换、特定元素全屏、图片画廊全屏和演示模式的实现,以及 API 参考和浏览器兼容性信息。

前端开发 0 次安装 1 次浏览 更新于 2/28/2026

Screenfull Fullscreen API 技能

这份指南指导如何在 ng-events 建筑工地管理系统中使用 screenfull 库实现全屏功能。

何时使用这项技能

触发词: “fullscreen”, “screenfull”, “全屏模式”, “切换全屏”, “退出全屏”

当以下情况时使用这项技能:

  • 添加全屏切换按钮
  • 创建全屏查看器(图片、视频、仪表板)
  • 实施演示模式
  • 构建沉浸式 UI 体验
  • 为特定组件提供全屏功能

安装 & 设置

# 已在 package.json 中安装
yarn add screenfull@^6.0.2

核心模式

1. 基本全屏切换

import { Component, inject, signal } from '@angular/core';
import screenfull from 'screenfull';

@Component({
  selector: 'app-fullscreen-toggle',
  standalone: true,
  template: `
    <button (click)="toggleFullscreen()" class="fullscreen-btn">
      @if (isFullscreen()) {
        <i nz-icon nzType="fullscreen-exit" nzTheme="outline"></i>
        退出全屏
      } @else {
        <i nz-icon nzType="fullscreen" nzTheme="outline"></i>
        进入全屏
      }
    </button>
  `
})
export class FullscreenToggleComponent {
  isFullscreen = signal(false);
  
  toggleFullscreen(): void {
    if (screenfull.isEnabled) {
      screenfull.toggle();
      this.updateFullscreenState();
      
      // 监听全屏变化
      screenfull.on('change', () => {
        this.updateFullscreenState();
      });
    } else {
      console.warn('全屏 API 不受支持');
    }
  }
  
  private updateFullscreenState(): void {
    this.isFullscreen.set(screenfull.isFullscreen);
  }
}

2. 特定元素全屏

import { Component, ElementRef, ViewChild, signal } from '@angular/core';
import screenfull from 'screenfull';

@Component({
  selector: 'app-dashboard-fullscreen',
  standalone: true,
  template: `
    <div #dashboardContainer class="dashboard-container">
      <div class="dashboard-header">
        <h2>建设进度仪表板</h2>
        <button (click)="toggleFullscreen()" class="btn-fullscreen">
          @if (isFullscreen()) {
            <i nz-icon nzType="fullscreen-exit"></i>
          } @else {
            <i nz-icon nzType="fullscreen"></i>
          }
        </button>
      </div>
      
      <div class="dashboard-content">
        <app-progress-charts />
        <app-task-summary />
        <app-recent-activities />
      </div>
    </div>
  `,
  styles: [`
    .dashboard-container {
      position: relative;
      background: white;
      padding: 20px;
    }
    
    .dashboard-container:-webkit-full-screen {
      background: #1f1f1f;
      color: white;
    }
    
    .dashboard-container:fullscreen {
      background: #1f1f1f;
      color: white;
    }
    
    .btn-fullscreen {
      position: absolute;
      top: 20px;
      right: 20px;
    }
  `]
})
export class DashboardFullscreenComponent {
  @ViewChild('dashboardContainer', { static: true }) container!: ElementRef;
  isFullscreen = signal(false);
  
  toggleFullscreen(): void {
    if (screenfull.isEnabled) {
      screenfull.toggle(this.container.nativeElement);
      
      screenfull.on('change', () => {
        this.isFullscreen.set(screenfull.isFullscreen);
      });
    }
  }
}

3. 图片画廊全屏

import { Component, signal } from '@angular/core';
import screenfull from 'screenfull';
import { NzImageModule } from 'ng-zorro-antd/image';

@Component({
  selector: 'app-construction-photos',
  standalone: true,
  imports: [NzImageModule],
  template: `
    <div class="photo-gallery">
      <h3>建筑工地照片</h3>
      
      <div class="photo-grid">
        @for (photo of photos(); track photo.id) {
          <div class="photo-item" (click)="viewFullscreen(photo)">
            <img [src]="photo.thumbnailUrl" [alt]="photo.title" />
            <div class="photo-overlay">
              <i nz-icon nzType="fullscreen" nzTheme="outline"></i>
            </div>
          </div>
        }
      </div>
    </div>
    
    @if (currentPhoto()) {
      <div class="fullscreen-viewer" #viewer>
        <button class="close-btn" (click)="exitFullscreen()">
          <i nz-icon nzType="close"></i>
        </button>
        <img [src]="currentPhoto()!.fullUrl" [alt]="currentPhoto()!.title" />
        <div class="photo-info">
          <h4>{{ currentPhoto()!.title }}</h4>
          <p>{{ currentPhoto()!.description }}</p>
          <span>{{ currentPhoto()!.date | date }}</span>
        </div>
      </div>
    }
  `,
  styles: [`
    .photo-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
      gap: 16px;
    }
    
    .photo-item {
      position: relative;
      cursor: pointer;
      overflow: hidden;
      border-radius: 4px;
    }
    
    .photo-item img {
      width: 100%;
      height: 200px;
      object-fit: cover;
      transition: transform 0.3s;
    }
    
    .photo-item:hover img {
      transform: scale(1.1);
    }
    
    .photo-overlay {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(0, 0, 0, 0.5);
      display: flex;
      align-items: center;
      justify-content: center;
      opacity: 0;
      transition: opacity 0.3s;
    }
    
    .photo-item:hover .photo-overlay {
      opacity: 1;
    }
    
    .fullscreen-viewer {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(0, 0, 0, 0.95);
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 9999;
    }
    
    .fullscreen-viewer img {
      max-width: 90%;
      max-height: 90%;
      object-fit: contain;
    }
    
    .close-btn {
      position: absolute;
      top: 20px;
      right: 20px;
      background: rgba(255, 255, 255, 0.2);
      border: none;
      color: white;
      font-size: 24px;
      cursor: pointer;
      padding: 10px;
      border-radius: 4px;
    }
    
    .photo-info {
      position: absolute;
      bottom: 20px;
      left: 20px;
      color: white;
      text-align: left;
    }
  `]
})
export class ConstructionPhotosComponent {
  photos = signal([
    {
      id: '1',
      title: '基础工作',
      description: '基础浇筑混凝土',
      thumbnailUrl: 'assets/photos/thumb1.jpg',
      fullUrl: 'assets/photos/full1.jpg',
      date: new Date()
    }
    // ... 更多照片
  ]);
  
  currentPhoto = signal<Photo | null>(null);
  
  viewFullscreen(photo: Photo): void {
    this.currentPhoto.set(photo);
    
    if (screenfull.isEnabled) {
      const viewer = document.querySelector('.fullscreen-viewer');
      if (viewer) {
        screenfull.request(viewer as HTMLElement);
        
        screenfull.on('change', () => {
          if (!screenfull.isFullscreen) {
            this.currentPhoto.set(null);
          }
        });
      }
    }
  }
  
  exitFullscreen(): void {
    if (screenfull.isEnabled && screenfull.isFullscreen) {
      screenfull.exit();
    }
    this.currentPhoto.set(null);
  }
}

4. 演示模式

import { Component, signal, OnInit, OnDestroy } from '@angular/core';
import screenfull from 'screenfull';

@Component({
  selector: 'app-presentation-mode',
  standalone: true,
  template: `
    <div class="presentation-container" [class.presentation-active]="isPresenting()">
      <div class="presentation-header">
        <h2>{{ currentSlide().title }}</h2>
        <div class="controls">
          <button (click)="previousSlide()" [disabled]="currentSlideIndex() === 0">
            <i nz-icon nzType="left"></i> 上一个
          </button>
          <span>{{ currentSlideIndex() + 1 }} / {{ slides().length }}</span>
          <button (click)="nextSlide()" [disabled]="currentSlideIndex() === slides().length - 1">
            下一个 <i nz-icon nzType="right"></i>
          </button>
          <button (click)="togglePresentation()" class="btn-present">
            @if (isPresenting()) {
              <i nz-icon nzType="fullscreen-exit"></i> 退出
            } @else {
              <i nz-icon nzType="fullscreen"></i> 演示
            }
          </button>
        </div>
      </div>
      
      <div class="slide-content">
        <div [innerHTML]="currentSlide().content"></div>
      </div>
    </div>
  `,
  styles: [`
    .presentation-container {
      background: white;
      padding: 20px;
    }
    
    .presentation-active {
      background: #1a1a1a;
      color: white;
      padding: 40px;
    }
    
    .presentation-active .slide-content {
      font-size: 1.5em;
    }
  `]
})
export class PresentationModeComponent implements OnInit, OnDestroy {
  slides = signal([
    {
      title: '项目概览',
      content: '<h1>建筑工地进度</h1><p>2025年Q4更新</p>'
    },
    {
      title: '达成的里程碑',
      content: '<ul><li>基础完成</li><li>结构工作75%</li></ul>'
    }
    // ... 更多幻灯片
  ]);
  
  currentSlideIndex = signal(0);
  isPresenting = signal(false);
  
  currentSlide = computed(() => this.slides()[this.currentSlideIndex()]);
  
  ngOnInit(): void {
    // 监听键盘快捷方式
    document.addEventListener('keydown', this.handleKeyPress.bind(this));
  }
  
  ngOnDestroy(): void {
    document.removeEventListener('keydown', this.handleKeyPress.bind(this));
    
    if (screenfull.isEnabled) {
      screenfull.off('change');
    }
  }
  
  togglePresentation(): void {
    if (screenfull.isEnabled) {
      screenfull.toggle();
      
      screenfull.on('change', () => {
        this.isPresenting.set(screenfull.isFullscreen);
      });
    }
  }
  
  nextSlide(): void {
    if (this.currentSlideIndex() < this.slides().length - 1) {
      this.currentSlideIndex.update(i => i + 1);
    }
  }
  
  previousSlide(): void {
    if (this.currentSlideIndex() > 0) {
      this.currentSlideIndex.update(i => i - 1);
    }
  }
  
  private handleKeyPress(event: KeyboardEvent): void {
    if (!this.isPresenting()) return;
    
    switch (event.key) {
      case 'ArrowRight':
      case 'PageDown':
        this.nextSlide();
        break;
      case 'ArrowLeft':
      case 'PageUp':
        this.previousSlide();
        break;
      case 'Escape':
        if (screenfull.isEnabled && screenfull.isFullscreen) {
          screenfull.exit();
        }
        break;
    }
  }
}

API 参考

screenfull 方法

// 检查全屏 API 是否支持
screenfull.isEnabled // boolean

// 切换全屏
screenfull.toggle() // 切换文档
screenfull.toggle(element) // 切换特定元素

// 请求全屏
screenfull.request() // 请求文档
screenfull.request(element) // 请求特定元素

// 退出全屏
screenfull.exit()

// 检查当前是否全屏
screenfull.isFullscreen // boolean

// 获取全屏元素
screenfull.element // Element | null

// 事件监听器
screenfull.on('change', () => {})
screenfull.on('error', (event) => {})
screenfull.off('change', handler)

集成检查表

使用 screenfull 时:

  • [ ] 检查 screenfull.isEnabled 后再使用 API
  • [ ] 优雅地处理浏览器兼容性
  • [ ] 在组件销毁时清理事件监听器
  • [ ] 提供退出全屏按钮
  • [ ] 在不同浏览器上测试(Chrome、Firefox、Safari)
  • [ ] 处理键盘快捷键(Esc 退出)
  • [ ] 适当地为全屏模式设置样式
  • [ ] 考虑移动浏览器(支持有限)

最佳实践

应该做 ✅

  • 总是检查 screenfull.isEnabled 后再使用
  • 提供清晰的 UI 退出全屏
  • 使用 Esc 键退出全屏
  • 适当地为全屏元素设置样式
  • 清理事件监听器
  • 优雅地处理错误

不应该做 ❌

  • 假设全屏 API 总是可用的
  • 未经用户交互强制全屏
  • 忘记移除事件监听器
  • 忽视移动浏览器限制
  • 将全屏用于敏感操作

浏览器兼容性

浏览器 支持
Chrome ✅ 全面支持
Firefox ✅ 全面支持
Safari ✅ 全面支持(iOS 有限)
Edge ✅ 全面支持
iOS Safari ⚠️ 有限(仅限视频)
Android Chrome ✅ 全面支持

参考资料


版本: 1.0.0
兼容于: screenfull 6.0.x
最后更新: 2025-12-25