107 lines
3.6 KiB
JavaScript
107 lines
3.6 KiB
JavaScript
/**
|
|
* 宇森剧情漫画播放器
|
|
* 用法:
|
|
* await playIntroStory(['assets/story/story_01.jpg', 'assets/story/story_02.jpg']);
|
|
* 或:
|
|
* await playIntroStory(['assets/story/story_03.jpg'], { enterText: '进入世界 ▸', holdMs: 5000 });
|
|
*/
|
|
|
|
// 确保遮罩 DOM 存在(页面只需引入本脚本与 CSS)
|
|
(function ensureOverlay() {
|
|
if (document.getElementById('introOverlay')) return;
|
|
const overlay = document.createElement('div');
|
|
overlay.id = 'introOverlay';
|
|
overlay.innerHTML =
|
|
'<div class="introSkip" id="introSkip">跳过剧情 ▸</div>' +
|
|
'<img class="introImg" id="introImg" alt="剧情" />' +
|
|
'<button class="introEnter" id="introEnter">踏入宇森世界 ▸</button>' +
|
|
'<div class="introDots" id="introDots"></div>' +
|
|
'<div class="introHint" id="introHint">点击 / 空格 / → 继续</div>';
|
|
document.body.appendChild(overlay);
|
|
})();
|
|
|
|
window.playIntroStory = function playIntroStory(panels, options) {
|
|
options = options || {};
|
|
const holdMs = options.holdMs || 5000;
|
|
const enterText = options.enterText || '踏入宇森世界 ▸';
|
|
const onStart = options.onStart || null; // 开始播放前回调,用于隐藏页面原生元素
|
|
|
|
return new Promise((resolve) => {
|
|
const overlay = document.getElementById('introOverlay');
|
|
const img = document.getElementById('introImg');
|
|
const dotsWrap = document.getElementById('introDots');
|
|
const hint = document.getElementById('introHint');
|
|
const skipBtn = document.getElementById('introSkip');
|
|
const enterBtn = document.getElementById('introEnter');
|
|
|
|
if (!Array.isArray(panels) || panels.length === 0) {
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
if (typeof onStart === 'function') onStart();
|
|
enterBtn.textContent = enterText;
|
|
|
|
let idx = 0, autoTimer = null, done = false, dots = [];
|
|
|
|
dotsWrap.innerHTML = '';
|
|
panels.forEach((_, i) => {
|
|
const d = document.createElement('div');
|
|
d.className = 'introDot' + (i === 0 ? ' on' : '');
|
|
dotsWrap.appendChild(d);
|
|
});
|
|
dots = dotsWrap.querySelectorAll('.introDot');
|
|
|
|
function showPanel(i) {
|
|
idx = i;
|
|
dots.forEach((d, k) => d.classList.toggle('on', k === i));
|
|
img.classList.remove('show');
|
|
img.onload = () => img.classList.add('show');
|
|
img.src = panels[i];
|
|
if (img.complete) img.classList.add('show');
|
|
const last = (i === panels.length - 1);
|
|
enterBtn.classList.toggle('show', last);
|
|
hint.style.opacity = last ? '0' : '1';
|
|
clearTimeout(autoTimer);
|
|
if (!last) autoTimer = setTimeout(nextPanel, holdMs);
|
|
}
|
|
|
|
function nextPanel() {
|
|
if (done || idx >= panels.length - 1) return;
|
|
showPanel(idx + 1);
|
|
}
|
|
|
|
function finish() {
|
|
if (done) return;
|
|
done = true;
|
|
clearTimeout(autoTimer);
|
|
document.removeEventListener('keydown', onKey);
|
|
overlay.classList.remove('active');
|
|
setTimeout(() => { overlay.style.display = 'none'; }, 500);
|
|
resolve();
|
|
}
|
|
|
|
function onKey(e) {
|
|
if (!overlay.classList.contains('active')) return;
|
|
if (e.key === ' ' || e.key === 'Enter' || e.key === 'ArrowRight') {
|
|
e.preventDefault();
|
|
if (idx === panels.length - 1) finish(); else nextPanel();
|
|
} else if (e.key === 'Escape') {
|
|
finish();
|
|
}
|
|
}
|
|
|
|
overlay.addEventListener('click', (e) => {
|
|
if (e.target === skipBtn || e.target === enterBtn) return;
|
|
nextPanel();
|
|
});
|
|
skipBtn.addEventListener('click', finish);
|
|
enterBtn.addEventListener('click', finish);
|
|
document.addEventListener('keydown', onKey);
|
|
|
|
overlay.style.display = 'flex';
|
|
requestAnimationFrame(() => overlay.classList.add('active'));
|
|
showPanel(0);
|
|
});
|
|
};
|