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(假定是页面视频相关的组件)。
- 初始化 HomeVideo
- new HomeVideo(); 在页面上初始化一个视频小部件(具体行为在 home-video.js 中实现)。
- 运行时计数(Runtime Counter)
- 找到 id 为 runTime 的元素,并从其 data-start-time 属性读取开始时间(例如)。
- 如果存在 start-time:
- 将其转换为 Date 对象。
- 若全局 window.runTimeInterval 已存在,先 clearInterval 清除,避免重复计时器。
- 每秒(setInterval 1000)计算现在时间与开始时间的差值,格式化为 天、小时、分钟、秒 并更新 $runTime 的文本为:已运行 X天 Y小时 Z分 S秒。
- 作用:在页面上显示从某个起始时间到现在的运行时长,并保证不会启动多个计时器。
- 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 版本。
评论加载中...