From 0174d29bde1be26ea8ae32db7a4ba363448f25a9 Mon Sep 17 00:00:00 2001 From: hehh Date: Thu, 4 Dec 2025 19:25:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E9=87=8D=E6=9E=84=E8=BF=9B?= =?UTF-8?q?=E5=85=A5=E7=95=8C=E9=9D=A2=E4=B8=8E=E4=BA=A4=E4=BA=92=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重新设计进入界面的按钮和倒计时提示样式 - 新增交互模式按钮,支持动态显示和隐藏 - 修改默认进入方式为动画模式而非叙事模式 - 调整摄像头权限申请逻辑,改为用户主动点击触发 - 优化加载文案循环播放逻辑 - 更新多语言字典中的相关提示文本 - 改进页面加载完成后不自动启动摄像头的处理方式 --- me.html | 363 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 232 insertions(+), 131 deletions(-) diff --git a/me.html b/me.html index 0ac0775..69a9912 100644 --- a/me.html +++ b/me.html @@ -104,15 +104,81 @@ transform: translateY(0); } - .direct-enter-container { + /* 进入按钮样式 */ + .enter-container { text-align: center; } - .direct-enter-btn:hover { + .enter-btn:hover { background: var(--accent-color); color: var(--loader-bg); } + /* 交互模式按钮 */ + .interaction-mode-btn { + position: fixed; + bottom: 40px; + right: 40px; + padding: 12px 24px; + background: var(--accent-color); + color: var(--loader-bg); + border: none; + border-radius: 30px; + cursor: pointer; + font-size: 12px; + letter-spacing: 1px; + box-shadow: 0 4px 12px rgba(0, 255, 255, 0.3); + animation: pulse 2s infinite; + z-index: 30; + opacity: 0; + transform: translateY(20px); + transition: all 0.5s ease; + } + + .interaction-mode-btn.visible { + opacity: 1; + transform: translateY(0); + } + + .interaction-mode-btn:hover { + box-shadow: 0 6px 16px rgba(0, 255, 255, 0.5); + transform: scale(1.05); + } + + @keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(0, 255, 255, 0.4); + } + 70% { + box-shadow: 0 0 0 10px rgba(0, 255, 255, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(0, 255, 255, 0); + } + } + + /* 黑夜模式下的特殊样式 */ + [data-theme="night"] .interaction-mode-btn { + background: var(--accent-color); + color: #000; + box-shadow: 0 4px 12px rgba(0, 255, 255, 0.4); + } + + [data-theme="night"] .interaction-mode-btn:hover { + box-shadow: 0 6px 16px rgba(0, 255, 255, 0.6); + } + + /* 白天模式下的特殊样式 */ + [data-theme="day"] .interaction-mode-btn { + background: var(--accent-color); + color: #fff; + box-shadow: 0 4px 12px rgba(53, 103, 255, 0.4); + } + + [data-theme="day"] .interaction-mode-btn:hover { + box-shadow: 0 6px 16px rgba(53, 103, 255, 0.6); + } + /* === 2. 叙事文本层 (DOM覆盖) === */ #narrative-layer { position: absolute; @@ -366,10 +432,9 @@
Honesty
正在唤醒灵感...
-
⚠ 请允许摄像头权限以开启手势交互
-
-
3秒后自动进入
- +
+
5秒后自动进入
+
@@ -388,7 +453,7 @@
-
+
[ 退出动画 ]
@@ -424,10 +489,9 @@ // 加载屏幕相关元素 startScreen: document.getElementById('start-screen'), loaderMsg: document.getElementById('loader-msg'), - permHint: document.getElementById('perm-guide'), - directEnterContainer: document.getElementById('direct-enter-container'), - directEnterHint: document.getElementById('direct-enter-hint'), - directEnterBtn: document.getElementById('direct-enter-btn'), + enterContainer: document.getElementById('enter-container'), + countdownHint: document.getElementById('countdown-hint'), + enterBtn: document.getElementById('enter-btn'), // 叙事层相关元素 narrativeLayer: document.getElementById('narrative-layer'), @@ -443,6 +507,9 @@ subHint: document.getElementById('sub-hint'), exitBtn: document.getElementById('exit-btn'), + // 交互模式按钮 + interactionModeBtn: null, + // 其他元素 canvasContainer: document.getElementById('canvas-container'), inputVideo: document.getElementById('input-video') @@ -489,19 +556,6 @@ lang: getStoredLanguage() }; - // 摄像头权限状态管理 - const CAMERA_STATE = { - enabled: false, // 摄像头是否已启用 - permissionDenied: false, // 用户是否拒绝了权限 - autoEnterTimeout: null, // 自动进入计时器 - retryTimeout: null // 重试计时器 - }; - - // 应用主题 - document.documentElement.setAttribute('data-theme', ENV.theme); - safeUpdateText(DOM_CACHE.themeDisplay, `THEME: ${ENV.lang.toUpperCase() === 'en' ? ENV.theme.toUpperCase() : ENV.theme === 'day' ? '白天' : '黑夜'}`); - safeUpdateText(DOM_CACHE.langDisplay, ENV.lang.toUpperCase() === 'CN' ? '中文' : 'English'); - /** * ============================================================================ * 3. 内容字典 @@ -516,11 +570,10 @@ main: "双手合十 · 解锁档案", sub: "单手·引力牵引|双手·力场排斥", unlocking: "正在识别...", - directEnterHint: "5秒后自动进入", - directEnterBtn: "立即进入", - cameraDisabledMainHint: "点击进入档案馆", - cameraDisabledSubHint: "[ 再次申请摄像头权限 ]", - cameraRetryTimeout: "摄像头授权超时,5秒后取消" + countdownHint: "5秒后自动进入", + enterBtn: "立即进入", + interactionModeBtn: "交互模式", + cameraAccessInfo: "点击启用交互模式" }, slides: [ {t: "初心", s: "在这喧嚣世界中,依然相信纯粹的力量"}, @@ -539,11 +592,10 @@ main: "Click or Namaste", sub: "One Hand Drag · Two Hands Repel", unlocking: "Identifying...", - directEnterHint: "Auto enter in 5 seconds", - directEnterBtn: "Enter Now", - cameraDisabledMainHint: "Click to Enter Archive", - cameraDisabledSubHint: "[ Request Camera Access Again ]", - cameraRetryTimeout: "Camera authorization timeout, canceling in 5 seconds" + countdownHint: "Auto enter in 5 seconds", + enterBtn: "Enter Now", + interactionModeBtn: "Interaction Mode", + cameraAccessInfo: "Click to enable camera interaction" }, slides: [ {t: "Honesty", s: "In a noisy world, still believe in the power of simplicity"}, @@ -558,45 +610,22 @@ const CONTENT = DICTIONARY[ENV.lang]; - // 设置直接进入按钮的文本 - safeUpdateText(DOM_CACHE.directEnterBtn, CONTENT.hints.directEnterBtn); - safeUpdateText(DOM_CACHE.directEnterHint, CONTENT.hints.directEnterHint); + // 设置进入按钮的文本 + safeUpdateText(DOM_CACHE.countdownHint, CONTENT.hints.countdownHint); + safeUpdateText(DOM_CACHE.enterBtn, CONTENT.hints.enterBtn); - // 直接进入按钮事件处理 - DOM_CACHE.directEnterBtn.addEventListener('click', () => { - clearTimeout(CAMERA_STATE.autoEnterTimeout); - enterWithoutCamera(); - }); + // 摄像头权限状态管理 + const CAMERA_STATE = { + enabled: false, // 摄像头是否已启用 + permissionDenied: false, // 用户是否拒绝了权限 + autoEnterTimeout: null, // 自动进入计时器 + countdownInterval: null // 倒计时计时器 + }; - // 循环播放加载文案 - let loadIdx = 0; - const maxIdx = CONTENT.load.length - 1; - const loadTimer = setInterval(() => { - if (APP_STATE.isLoaded) { - clearInterval(loadTimer); - return; - } - safeUpdateText(DOM_CACHE.loaderMsg, CONTENT.load[loadIdx > maxIdx ? maxIdx : loadIdx]); - loadIdx++; - }, 600); - - // 权限超时提示和自动进入功能 - setTimeout(() => { - if (!APP_STATE.isLoaded) { - safeClass(DOM_CACHE.permHint, 'add', 'show'); - - // 显示直接进入选项 - DOM_CACHE.directEnterContainer.style.opacity = '1'; - - // 3秒后自动进入动画模式 - CAMERA_STATE.autoEnterTimeout = setTimeout(() => { - enterWithoutCamera(); - }, 3000); - } - }, 5000); - - safeUpdateText(DOM_CACHE.mainHint, CONTENT.hints.main); - safeUpdateText(DOM_CACHE.subHint, CONTENT.hints.sub); + // 应用主题 + document.documentElement.setAttribute('data-theme', ENV.theme); + safeUpdateText(DOM_CACHE.themeDisplay, `THEME: ${ENV.lang.toUpperCase() === 'en' ? ENV.theme.toUpperCase() : ENV.theme === 'day' ? '白天' : '黑夜'}`); + safeUpdateText(DOM_CACHE.langDisplay, ENV.lang.toUpperCase() === 'CN' ? '中文' : 'English'); /** * ============================================================================ @@ -857,16 +886,15 @@ /** * ============================================================================ - * 6. 逻辑控制 (叙事/退出) - 修复空指针问题 + * 6. 逻辑控制 (动画/退出) * ============================================================================ */ let narrativeTimer = null; - // 无摄像头进入动画模式(不是叙事模式) - function enterWithoutCamera() { + // 进入动画模式 + function enterAnimationMode() { if (APP_STATE.isLoaded) return; APP_STATE.isLoaded = true; - CAMERA_STATE.enabled = false; // 隐藏加载屏幕 const loader = DOM_CACHE.startScreen; @@ -874,27 +902,75 @@ DOM_CACHE.uiLayer.style.opacity = 1; setTimeout(() => loader.style.display = 'none', 1000); - // 更新UI提示 - safeUpdateText(DOM_CACHE.mainHint, CONTENT.hints.cameraDisabledMainHint); - safeUpdateText(DOM_CACHE.subHint, CONTENT.hints.cameraDisabledSubHint); - - // 清除加载文案定时器 - clearInterval(loadTimer); + // 显示交互模式按钮(如果摄像头未启用) + if (!CAMERA_STATE.enabled) { + showInteractionModeButton(); + } + } + + // 显示交互模式按钮 + function showInteractionModeButton() { + // 创建交互模式按钮 + const btn = document.createElement('button'); + btn.className = 'interaction-mode-btn'; + btn.id = 'interaction-mode-btn'; + btn.textContent = CONTENT.hints.interactionModeBtn; + document.body.appendChild(btn); + + // 缓存按钮引用 + DOM_CACHE.interactionModeBtn = btn; + + // 显示按钮 + setTimeout(() => { + btn.classList.add('visible'); + }, 100); + + // 添加点击事件 + btn.addEventListener('click', () => { + requestCameraAccess(); + }); + } + + // 请求摄像头权限 + function requestCameraAccess() { + if (CAMERA_STATE.enabled) { + return; + } + // 隐藏交互模式按钮 + if (DOM_CACHE.interactionModeBtn) { + DOM_CACHE.interactionModeBtn.classList.remove('visible'); + } + + // 更新提示文本 + safeUpdateText(DOM_CACHE.subHint, CONTENT.hints.cameraAccessInfo); + + // 尝试启动摄像头 + cameraUtils.start().then(() => { + // 成功启动,更新状态 + CAMERA_STATE.enabled = true; + CAMERA_STATE.permissionDenied = false; + safeUpdateText(DOM_CACHE.subHint, CONTENT.hints.sub); + setTimeout(() => { + if (DOM_CACHE.interactionModeBtn && DOM_CACHE.interactionModeBtn.parentNode) { + DOM_CACHE.interactionModeBtn.parentNode.removeChild(DOM_CACHE.interactionModeBtn); + DOM_CACHE.interactionModeBtn = null; + } + }, 500); + }).catch(err => { + // 权限被拒绝 + console.log("Camera access error:", err); + CAMERA_STATE.permissionDenied = true; + safeUpdateText(DOM_CACHE.subHint, CONTENT.hints.cameraAccessInfo); + DOM_CACHE.interactionModeBtn.classList.add('visible'); + }); } window.enterArchive = function () { if (APP_STATE.mode === 'UNLOCKED') return; APP_STATE.mode = 'UNLOCKED'; - CAMERA_STATE.enabled = true; - - // 隐藏加载屏幕 - const loader = DOM_CACHE.startScreen; - loader.style.opacity = 0; - DOM_CACHE.uiLayer.style.opacity = 1; - setTimeout(() => loader.style.display = 'none', 1000); // UI - safeClass(DOM_CACHE.mainHint, 'add', 'hidden'); // 隐藏主提示 (CSS需支持或直接display) + safeClass(DOM_CACHE.mainHint, 'add', 'hidden'); DOM_CACHE.mainHint.style.display = 'none'; DOM_CACHE.subHint.style.display = 'none'; safeClass(DOM_CACHE.exitBtn, 'add', 'visible'); @@ -909,25 +985,27 @@ APP_STATE.mode = 'LOCKED'; APP_STATE.exitCooldownUntil = Date.now() + 2000; - // 根据摄像头状态更新UI + DOM_CACHE.mainHint.style.display = 'block'; + DOM_CACHE.subHint.style.display = 'block'; + safeUpdateText(DOM_CACHE.mainHint, CONTENT.hints.main); if (CAMERA_STATE.enabled) { - DOM_CACHE.mainHint.style.display = 'block'; - DOM_CACHE.subHint.style.display = 'block'; - safeUpdateText(DOM_CACHE.mainHint, CONTENT.hints.main); safeUpdateText(DOM_CACHE.subHint, CONTENT.hints.sub); } else { - DOM_CACHE.mainHint.style.display = 'block'; - DOM_CACHE.subHint.style.display = 'block'; - safeUpdateText(DOM_CACHE.mainHint, CONTENT.hints.cameraDisabledMainHint); - safeUpdateText(DOM_CACHE.subHint, CONTENT.hints.cameraDisabledSubHint); + safeUpdateText(DOM_CACHE.subHint, CONTENT.hints.cameraAccessInfo); } + safeClass(DOM_CACHE.exitBtn, 'remove', 'visible'); safeClass(DOM_CACHE.narrativeLayer, 'remove', 'show-text'); APP_STATE.unlockProgress = 0; clearTimeout(narrativeTimer); explode(50); + + // 退出后重新显示交互模式按钮(如果之前已启用摄像头) + if (CAMERA_STATE.enabled) { + showInteractionModeButton(); + } } function explode(f) { @@ -989,9 +1067,6 @@ if (!APP_STATE.isLoaded) { APP_STATE.isLoaded = true; CAMERA_STATE.enabled = true; - - // 清除自动进入定时器 - clearTimeout(CAMERA_STATE.autoEnterTimeout); } // 只有在摄像头启用时才处理手势 @@ -1053,44 +1128,24 @@ } }); - DOM_CACHE.subHint.addEventListener('click', () => { - // 只有在无摄像头模式下才允许重新申请权限 - if (!CAMERA_STATE.enabled && APP_STATE.isLoaded) { - requestCameraAccess(); - } - }); - - // 请求摄像头权限 - function requestCameraAccess() { - // 更新提示文本 - safeUpdateText(DOM_CACHE.mainHint, CONTENT.hints.cameraRetryTimeout); - - // 尝试启动摄像头 - cameraUtils.start().then(() => { - // 成功启动,隐藏提示 - safeUpdateText(DOM_CACHE.mainHint, CONTENT.hints.main); - }).catch(err => { - console.log("Camera access error:", err); - }); - - // 5秒后取消 - CAMERA_STATE.retryTimeout = setTimeout(() => { - cameraUtils.stop(); - safeUpdateText(DOM_CACHE.mainHint, CONTENT.hints.cameraDisabledMainHint); - }, 5000); - } - const videoElement = DOM_CACHE.inputVideo; const cameraUtils = new Camera(videoElement, { onFrame: async () => { - await hands.send({image: videoElement}); + try { + await hands.send({image: videoElement}); + } catch (err) { + // 捕获并隐藏技术性错误信息 + if (err.name !== 'NotAllowedError' && err.message.indexOf('Permission dismissed') === -1) { + console.log("Camera processing error:", err); + } + } }, width: 640, height: 480 }); - // 初始启动摄像头 - cameraUtils.start().catch(err => { - console.log("Initial camera access denied:", err); + // 页面加载完成后不自动启动摄像头 + window.addEventListener('load', () => { + console.log("Page loaded, camera not initialized"); }); window.addEventListener('resize', () => { @@ -1101,6 +1156,52 @@ material.uniforms.scale.value = window.innerHeight / 2; }); + // 循环播放加载文案 + let loadIdx = 0; + const maxIdx = CONTENT.load.length - 1; + const loadTimer = setInterval(() => { + if (APP_STATE.isLoaded) { + clearInterval(loadTimer); + return; + } + safeUpdateText(DOM_CACHE.loaderMsg, CONTENT.load[loadIdx > maxIdx ? maxIdx : loadIdx]); + loadIdx++; + }, 600); + + // 资源加载完成后显示进入按钮 + setTimeout(() => { + if (!APP_STATE.isLoaded) { + // 显示进入选项 + DOM_CACHE.enterContainer.style.opacity = '1'; + + // 开始5秒倒计时 + let countdown = 5; + safeUpdateText(DOM_CACHE.countdownHint, `${countdown}秒后自动进入`); + + CAMERA_STATE.countdownInterval = setInterval(() => { + countdown--; + if (countdown > 0) { + safeUpdateText(DOM_CACHE.countdownHint, `${countdown}秒后自动进入`); + } else { + clearInterval(CAMERA_STATE.countdownInterval); + enterAnimationMode(); + } + }, 1000); + } + }, 3000); + + // 进入按钮事件处理 + DOM_CACHE.enterBtn.addEventListener('click', () => { + clearInterval(CAMERA_STATE.countdownInterval); + enterAnimationMode(); + }); + + safeUpdateText(DOM_CACHE.mainHint, CONTENT.hints.main); + if (CAMERA_STATE.enabled) { + safeUpdateText(DOM_CACHE.subHint, CONTENT.hints.sub); + } else { + safeUpdateText(DOM_CACHE.subHint, CONTENT.hints.cameraAccessInfo); + } animate();