import { HomeVideo } from './home-video.js';


/*
* Home Page Scripts
* */
export class Home {
    constructor() {
        this.init();
    }

    init() {
        // Init Video Widget
        new HomeVideo();

        // Runtime Counter
        const $runTime = $("#runTime");
        const timeStr = $runTime.data("start-time");

        if (timeStr) {
            const startTime = new Date(timeStr);

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

            window.runTimeInterval = setInterval(function () {
                const now = new Date();
                const diff = now - startTime;
                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);
                $runTime.text(`已运行 ${days}天 ${hours}小时 ${minutes}分 ${seconds}秒`);
            }, 1000);
        }

        // Hero Slider
        const $heroBgs = $('.hero__bg');
        if ($heroBgs.length > 0) {
            let currentIndex = 0;
            const $heroTitle = $('#hero-title');
            const $heroCaption = $('#hero-caption');
            const $indicators = $('#hero-indicators');
            const slideDuration = 5000;

            // 胶囊指示器
            const createIndicators = () => {
                $indicators.empty();
                $heroBgs.each(function (i) {
                    $indicators.append($('<span class="hero__indicator">').data('index', i));
                });
            };

            const updateIndicator = (index) => {
                const $dots = $indicators.find('.hero__indicator');
                $dots.removeClass('is-active is-animating');

                const $dot = $dots.eq(index);
                $dot.addClass('is-active');

                // 强制 reflow 确保 animation 重新触发
                $dot[0].offsetWidth;
                $dot.css('--hero-progress-duration', `${slideDuration}ms`).addClass('is-animating');
            };

            // 图片说明文字
            const updateCaption = (desc) => {
                if (desc && desc !== '') {
                    $heroCaption.text(desc).addClass('has-text');
                } else {
                    $heroCaption.removeClass('has-text');
                }
            };

            // 标题动画
            const updateTitle = (desc) => {
                $heroTitle.removeClass('is-entering');
                // 切换文字
                $heroTitle.text(desc || '');
                // 强制 reflow 后触发入场动画
                $heroTitle[0].offsetWidth;
                if (desc) $heroTitle.addClass('is-entering');
            };

            // 切换到指定幻灯片
            const goToSlide = (nextIndex) => {
                const $current = $heroBgs.eq(currentIndex);
                const $next = $heroBgs.eq(nextIndex);

                // 预加载背景图
                if (!$next.css('background-image') || $next.css('background-image') === 'none') {
                    $next.css('background-image', `url('${$next.data('bg')}')`);
                }

                $current.removeClass('active');
                $next.addClass('active');

                updateTitle($next.data('desc'));
                updateCaption($next.data('desc'));
                updateIndicator(nextIndex);
                currentIndex = nextIndex;
            };

            // 初始化
            const initSlide = () => {
                const $first = $heroBgs.eq(0);
                $first.css('background-image', `url('${$first.data('bg')}')`).addClass('active');

                createIndicators();
                updateIndicator(0);

                const firstDesc = $first.data('desc');
                updateTitle(firstDesc);
                updateCaption(firstDesc);
            };

            // 自动轮播控制
            let _autoplayTimer = null;
            let _isPaused = false;

            const startAutoplay = () => {
                stopAutoplay();
                if ($heroBgs.length <= 1) return;
                _autoplayTimer = setInterval(() => {
                    goToSlide((currentIndex + 1) % $heroBgs.length);
                }, slideDuration);
            };

            const stopAutoplay = () => {
                if (_autoplayTimer) {
                    clearInterval(_autoplayTimer);
                    _autoplayTimer = null;
                }
            };

            initSlide();

            // 指示器点击切换 & 悬停暂停
            $indicators
                .on('click', '.hero__indicator', function () {
                    const idx = $(this).data('index');
                    if (idx === currentIndex) {
                        return;
                    }
                    goToSlide(idx);
                    // 点击后重置自动播放计时器(从头开始计 5s)
                    if (!_isPaused) {
                        startAutoplay();
                    }
                })
                .on('mouseenter', () => {
                    _isPaused = true;
                    stopAutoplay();
                })
                .on('mouseleave', () => {
                    _isPaused = false;
                    startAutoplay();
                });

            // 兼容旧 window.heroSliderInterval 清理
            if (window.heroSliderInterval) {
                clearInterval(window.heroSliderInterval);
                window.heroSliderInterval = null;
            }

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

下面按模块与流程把这段代码的功能和实现要点逐项解释清楚:

总体

  • 这是一个 ES 模块,导出一个 Home 类。构造函数中调用 init(),用于初始化首页的一些交互(视频组件、运行时计时器、Hero 轮播等)。
  • 依赖 jQuery(通过 $ 使用),并从同目录导入了一个 HomeVideo(假定是页面视频相关的组件)。
  1. 初始化 HomeVideo
  • new HomeVideo(); 在页面上初始化一个视频小部件(具体行为在 home-video.js 中实现)。
  1. 运行时计数(Runtime Counter)
  • 找到 id 为 runTime 的元素,并从其 data-start-time 属性读取开始时间(例如
    )。
  • 如果存在 start-time:
    • 将其转换为 Date 对象。
    • 若全局 window.runTimeInterval 已存在,先 clearInterval 清除,避免重复计时器。
    • 每秒(setInterval 1000)计算现在时间与开始时间的差值,格式化为 天、小时、分钟、秒 并更新 $runTime 的文本为:已运行 X天 Y小时 Z分 S秒。
  • 作用:在页面上显示从某个起始时间到现在的运行时长,并保证不会启动多个计时器。
  1. Hero Slider(大图/背景轮播)
  • 选择 .hero__bg 元素集,若无则不继续。

  • 主要变量:

    • currentIndex:当前激活幻灯片索引
    • $heroTitle(#hero-title):标题元素
    • $heroCaption(#hero-caption):图片说明元素
    • $indicators(#hero-indicators):指示器容器
    • slideDuration:每张幻灯片停留时间(5000 ms)
  • 数据来源(依赖 HTML data-*):

    • 每个 .hero__bg 上应该有 data-bg(背景图 URL)和 data-desc(图片描述/标题文字)等。
  • 指示器 createIndicators()

    • 清空指示器容器,然后为每个幻灯片 append 一个 并用 jQuery .data('index', i) 记录索引。
  • updateIndicator(index)

    • 找到所有指示器,移除 is-active 和 is-animating 类。
    • 给目标指示器加上 is-active。
    • 强制 reflow(访问 $dot[0].offsetWidth)以确保后续的 CSS 动画能重新触发。
    • 通过设置 CSS 自定义属性 --hero-progress-duration(CSS 里可用它做进度动画时长)并添加 is-animating 类来触发进度动画。
  • updateCaption(desc)

    • 若 desc 存在并非空字符串,设置 $heroCaption 的文本并加 has-text 类,否则移除 has-text。
  • updateTitle(desc)

    • 先移除标题的 is-entering(入场动画类),更换文本,强制 reflow,然后若有 desc 添加 is-entering 触发进场动画。
  • goToSlide(nextIndex)

    • 取得当前和目标 .hero__bg 元素。
    • 预加载目标背景:如果目标元素当前没有 background-image(或者为 none),则从 data-bg 设置 background-image。
    • 切换 active 类:当前 removeClass('active'),目标 addClass('active')。
    • 更新标题、说明和指示器;最后更新 currentIndex。
  • initSlide()

    • 对第一个幻灯片进行初始化:设置其 background-image 并加 active。
    • 创建指示器并激活第 0 个指示器。
    • 用第一个幻灯片的 data-desc 初始化标题与说明。
  • 自动轮播控制

    • 使用 _autoplayTimer 与 _isPaused 控制自动轮播与暂停状态。
    • startAutoplay(): 先 stopAutoplay() 再根据幻灯片数量启动 setInterval,每 slideDuration 切到下一张。
    • stopAutoplay(): 清除计时器。
    • initSlide() 后调用 startAutoplay() 启动自动轮播。
  • 指示器交互(事件绑定)

    • 点击指示器:读取点击元素的 data('index'),若与 currentIndex 不同则 goToSlide(idx)。点击后若当前非暂停状态则重启自动播放(相当于把计时器重置,从头开始计时 slideDuration)。
    • 悬停(mouseenter)在指示器上:设置 _isPaused=true 并 stopAutoplay()(即鼠标悬停时暂停自动播放)。
    • 鼠标离开(mouseleave):取消暂停并 startAutoplay()。
  • 兼容与全局计时器清理

    • 在创建新的 hero 轮播前,如果全局 window.heroSliderInterval 存在,则 clearInterval 清除并置为 null(与上面 runtime 的处理类似,防止重复的全局计时器)。

实现细节与设计意图要点

  • 使用 data-* 属性(data-start-time、data-bg、data-desc)使得 HTML 能够承载数据,JS 根据这些数据控制展示和动画。
  • 通过添加/移除 CSS 类(active、is-entering、has-text、is-active、is-animating)与 CSS 中的过渡/动画规则配合完成视觉效果。
  • 强制 reflow(读取 offsetWidth)是常见手法,用于在移除/添加类之间强制浏览器应用样式更改,从而能重新触发 CSS 动画。
  • 对可能存在的旧全局计时器做清理,避免在多次初始化或热重载场景中产生重复计时器。

可能的改进(可选)

  • 把全局 window.* 计时器改为类实例变量以减少对全局命名的依赖。
  • 针对 data('bg') 的预加载可以做更完善的错误处理(例如图片加载失败回退)。
  • 如果在不存在 jQuery 环境下使用,需要改写为原生 DOM 代码或引入兼容层。
  • 可在销毁组件时提供 destroy 方法,清理所有定时器与事件绑定,便于 SPA 场景下卸载。

如果你想,我可以:

  • 给出对应的 HTML 示例结构(必须具备哪些 data-* 属性、类名等);
  • 或者把这段代码改写成不依赖 jQuery 的原生 JS 版本。
评论加载中...