import { HomeVideo } from './HomeVideo.ts';

/**
 * 首页模块
 */
export class Home {
    private static runTimeInterval: number | null = null;

    constructor() {
        this.init();
    }

    private init(): void {
        // 初始化首页视频模块
        new HomeVideo();

        // 运行时间计数器
        this.initRunTimeCounter();

        // 首页幻灯片
        this.initHeroSlider();
    }

    /**
     * 初始化运行时间计数器
     */
    private initRunTimeCounter(): void {
        const runTimeElement = document.getElementById('runTime');
        const timeStr = runTimeElement?.dataset.startTime;

        if (!timeStr) {
            return;
        }

        const startTime: Date = new Date(timeStr);

        if (Home.runTimeInterval) {
            clearInterval(Home.runTimeInterval);
        }

        Home.runTimeInterval = window.setInterval(() => {
            if (!runTimeElement) {
                return;
            }

            const now: Date = new Date();
            const diff = now.getTime() - startTime.getTime();
            const days = Math.floor(diff / (1000 * 60 * 60 * 24));
            const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
            const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
            const seconds = Math.floor((diff % (1000 * 60)) / 1000);
            runTimeElement.textContent = `已运行 ${days}天 ${hours}小时 ${minutes}分 ${seconds}秒`;
        }, 1000);
    }

    /**
     * 初始化首页幻灯片轮播
     */
    private initHeroSlider(): void {
        const heroBgsElements = document.querySelectorAll<HTMLElement>('.hero__bg');
        if (heroBgsElements.length === 0) {
            return;
        }

        let currentIndex = 0;
        const heroTitle = document.getElementById('hero-title');
        const heroCaption = document.getElementById('hero-caption');
        const indicators = document.getElementById('hero-indicators');

        if (!heroTitle || !heroCaption || !indicators) {
            return;
        }

        const slideDuration = 5000;
        let autoplayTimer: number | null = null;
        let isPaused = false;

        // 创建胶囊指示器
        const createIndicators = (): void => {
            indicators.innerHTML = '';
            heroBgsElements.forEach((_, i) => {
                const span = document.createElement('span');
                span.className = 'hero__indicator';
                span.dataset.index = i.toString();
                indicators.appendChild(span);
            });
        };

        // 更新指示器激活状态
        const updateIndicator = (index: number): void => {
            const dots = indicators.querySelectorAll<HTMLElement>('.hero__indicator');
            dots.forEach((dot) => {
                dot.classList.remove('is-active', 'is-animating');
            });

            const activeDot = dots[index];
            if (activeDot) {
                activeDot.classList.add('is-active');
                // 强制 reflow 确保 animation 重新触发
                void activeDot.offsetWidth;
                activeDot.style.setProperty('--hero-progress-duration', `${slideDuration}ms`);
                activeDot.classList.add('is-animating');
            }
        };

        // 更新图片说明文字
        const updateCaption = (desc: string | null): void => {
            if (desc && desc !== '') {
                heroCaption.textContent = desc;
                heroCaption.classList.add('has-text');
            } else {
                heroCaption.classList.remove('has-text');
            }
        };

        // 更新标题动画
        const updateTitle = (desc: string | null): void => {
            heroTitle.classList.remove('is-entering');
            heroTitle.textContent = desc || '';
            // 强制 reflow 后触发入场动画
            void heroTitle.offsetWidth;
            if (desc) {
                heroTitle.classList.add('is-entering');
            }
        };

        // 切换到指定幻灯片
        const goToSlide = (nextIndex: number): void => {
            const current = heroBgsElements[currentIndex];
            const next = heroBgsElements[nextIndex];

            if (!current || !next) {
                return;
            }

            // 预加载背景图
            const bgUrl = next.dataset.bg;
            const currentBgImage = window.getComputedStyle(next).backgroundImage;

            if (bgUrl && (!currentBgImage || currentBgImage === 'none')) {
                next.style.backgroundImage = `url('${bgUrl}')`;
            }

            current.classList.remove('active');
            next.classList.add('active');

            const nextDesc = next.dataset.desc || null;
            updateTitle(nextDesc);
            updateCaption(nextDesc);
            updateIndicator(nextIndex);
            currentIndex = nextIndex;
        };

        // 初始化幻灯片
        const initSlide = (): void => {
            const first = heroBgsElements[0];
            if (!first) {
                return;
            }

            const bgUrl = first.dataset.bg;
            if (bgUrl) {
                first.style.backgroundImage = `url('${bgUrl}')`;
            }
            first.classList.add('active');

            createIndicators();
            updateIndicator(0);

            const firstDesc = first.dataset.desc || null;
            updateTitle(firstDesc);
            updateCaption(firstDesc);
        };

        // 开始自动轮播
        const startAutoplay = (): void => {
            stopAutoplay();
            if (heroBgsElements.length <= 1) {
                return;
            }

            autoplayTimer = window.setInterval(() => {
                goToSlide((currentIndex + 1) % heroBgsElements.length);
            }, slideDuration);
        };

        // 停止自动轮播
        const stopAutoplay = (): void => {
            if (autoplayTimer !== null) {
                clearInterval(autoplayTimer);
                autoplayTimer = null;
            }
        };

        // 初始化
        initSlide();

        // 指示器点击切换
        indicators.addEventListener('click', (e: MouseEvent) => {
            const target = e.target as HTMLElement;
            if (!target.classList.contains('hero__indicator')) {
                return;
            }

            const idx = parseInt(target.dataset.index || '0', 10);
            if (idx === currentIndex) {
                return;
            }

            goToSlide(idx);
            // 点击后重置自动播放计时器(从头开始计时)
            if (!isPaused) {
                startAutoplay();
            }
        });

        // 指示器悬停暂停
        indicators.addEventListener('mouseenter', () => {
            isPaused = true;
            stopAutoplay();
        });

        indicators.addEventListener('mouseleave', () => {
            isPaused = false;
            startAutoplay();
        });

        // 启动自动轮播
        startAutoplay();
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这是一个 TypeScript 编写的首页模块代码,主要负责首页的交互功能实现。让我详细解释一下这个代码的结构和功能:

类概述

Home 类是首页的主控制器,负责初始化和管理首页的各个功能模块。

主要功能模块

1. 初始化方法 (init)

在构造函数中调用,负责启动三个主要功能:

  • 初始化首页视频模块(通过 HomeVideo 类)
  • 启动运行时间计数器
  • 启动首页轮播图功能

2. 运行时间计数器 (initRunTimeCounter)

功能:显示网站运行时长的实时计数器

实现细节

  • 从 DOM 元素的 data-start-time 属性获取起始时间
  • 使用 setInterval 每秒更新一次显示
  • 计算并显示天、时、分、秒的差值
  • 使用静态属性管理定时器,防止重复创建

3. 首页轮播图 (initHeroSlider)

功能:实现一个功能完整的图片轮播组件

核心特性

  • 自动轮播:每5秒自动切换到下一张
  • 指示器导航:底部圆点指示器,可点击跳转
  • 悬停暂停:鼠标悬停时暂停自动轮播
  • 动画效果:标题入场动画和指示器进度动画
  • 预加载优化:动态加载背景图片

详细实现

指示器系统

const createIndicators = (): void => {
    // 动态创建指示器圆点
    // 每个圆点对应一张幻灯片
}

const updateIndicator = (index: number): void => {
    // 更新指示器的激活状态
    // 添加进度动画效果
}

内容更新

const updateTitle = (desc: string | null): void => {
    // 更新标题并触发入场动画
}

const updateCaption = (desc: string | null): void => {
    // 更新图片描述文字
}

核心切换逻辑

const goToSlide = (nextIndex: number): void => {
    // 1. 预加载背景图片
    // 2. 切换激活状态的CSS类
    // 3. 更新标题、描述和指示器
}

自动播放控制

const startAutoplay = (): void => {
    // 启动自动轮播定时器
}

const stopAutoplay = (): void => {
    // 停止自动轮播
}

设计特点

1. 性能优化

  • 图片懒加载:只在需要时加载背景图
  • 定时器管理:防止内存泄漏
  • 强制 reflow:确保动画正确触发

2. 用户体验

  • 交互式控制:点击指示器直接跳转
  • 智能暂停:悬停时暂停自动播放
  • 平滑动画:CSS动画配合 JavaScript 控制

3. 代码健壮性

  • 空值检查:防止 DOM 元素不存在导致的错误
  • 边界处理:处理只有一张图片的情况
  • 类型安全:完整的 TypeScript 类型注解

4. 模块化设计

  • 单一职责:每个方法只负责一个具体功能
  • 依赖注入:通过导入 HomeVideo 模块
  • 静态资源管理:使用静态属性管理全局状态

这个代码实现了一个现代化的首页交互系统,具备良好的用户体验和代码质量。

评论加载中...