// HoloLake 动态漫制作系统 - 环节6 (预览与分享版) // 基于环节5增强,新增预览模式、分享链接、嵌入代码功能 // ==================== 全局变量 ==================== let scenes = [ { id: 'scene-1', name: '场景1', frames: [ { id: 'frame-1-1', assets: [] // 存储素材 { id, type, emoji, x, y, width, height } } ] } ]; let currentSceneIndex = 0; let currentFrameIndex = 0; let animationInterval = null; let isPlaying = false; let fps = 4; let canvas = document.getElementById('mainCanvas'); let ctx = canvas.getContext('2d'); // ==================== 初始化 ==================== function init() { console.log('HoloLake 动态漫制作系统 v6.0 初始化...'); updateUI(); setupEventListeners(); drawCurrentFrame(); } // ==================== 事件监听设置 ==================== function setupEventListeners() { // FPS滑块 const fpsSlider = document.getElementById('fpsSlider'); if (fpsSlider) { fpsSlider.addEventListener('input', function(e) { fps = parseInt(e.target.value); document.getElementById('fpsValue').textContent = fps + ' FPS'; if (isPlaying) { stopAnimation(); startAnimation(); } }); } // 预览按钮 const previewBtn = document.getElementById('previewBtn'); if (previewBtn) { previewBtn.addEventListener('click', enterPreviewMode); } // 预览模式按钮 const playPreviewBtn = document.getElementById('playPreviewBtn'); if (playPreviewBtn) { playPreviewBtn.addEventListener('click', function() { if (!isPlaying) { startAnimation(); } }); } const pausePreviewBtn = document.getElementById('pausePreviewBtn'); if (pausePreviewBtn) { pausePreviewBtn.addEventListener('click', function() { if (isPlaying) { stopAnimation(); } }); } const stopPreviewBtn = document.getElementById('stopPreviewBtn'); if (stopPreviewBtn) { stopPreviewBtn.addEventListener('click', function() { stopAnimation(); currentFrameIndex = 0; updateUI(); drawCurrentFrame(); }); } const exitPreviewBtn = document.getElementById('exitPreviewBtn'); if (exitPreviewBtn) { exitPreviewBtn.addEventListener('click', exitPreviewMode); } // 分享按钮 const shareBtn = document.getElementById('shareBtn'); if (shareBtn) { shareBtn.addEventListener('click', showSharePanel); } // 分享面板按钮 const generateUrlBtn = document.getElementById('generateUrlBtn'); if (generateUrlBtn) { generateUrlBtn.addEventListener('click', generateShareUrl); } const generateEmbedBtn = document.getElementById('generateEmbedBtn'); if (generateEmbedBtn) { generateEmbedBtn.addEventListener('click', generateEmbedCode); } const closeShareBtn = document.getElementById('closeShareBtn'); if (closeShareBtn) { closeShareBtn.addEventListener('click', hideSharePanel); } const copyShareBtn = document.getElementById('copyShareBtn'); if (copyShareBtn) { copyShareBtn.addEventListener('click', copyToClipboard); } // 素材拖拽 const materialItems = document.querySelectorAll('.material-item'); materialItems.forEach(item => { item.addEventListener('dragstart', handleDragStart); }); // 画布放置 canvas.addEventListener('dragover', (e) => e.preventDefault()); canvas.addEventListener('drop', handleDrop); // 画布点击选择素材 canvas.addEventListener('click', handleCanvasClick); } // ==================== 素材拖拽 ==================== function handleDragStart(e) { const type = e.target.dataset.type; const emoji = e.target.dataset.emoji; e.dataTransfer.setData('text/plain', JSON.stringify({ type: type, emoji: emoji })); } // ==================== 素材放置 ==================== function handleDrop(e) { e.preventDefault(); const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; const x = (e.clientX - rect.left) * scaleX; const y = (e.clientY - rect.top) * scaleY; try { const data = JSON.parse(e.dataTransfer.getData('text/plain')); const currentScene = scenes[currentSceneIndex]; const currentFrame = currentScene.frames[currentFrameIndex]; const newAsset = { id: 'asset-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9), type: data.type, emoji: data.emoji, x: x - 25, y: y - 25, width: 50, height: 50 }; currentFrame.assets.push(newAsset); drawCurrentFrame(); updateUI(); } catch (error) { console.error('放置素材失败:', error); } } // ==================== 画布点击选择 ==================== function handleCanvasClick(e) { const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; const clickX = (e.clientX - rect.left) * scaleX; const clickY = (e.clientY - rect.top) * scaleY; const currentScene = scenes[currentSceneIndex]; const currentFrame = currentScene.frames[currentFrameIndex]; // 从后往前遍历,以便选中上层的素材 let selectedAsset = null; for (let i = currentFrame.assets.length - 1; i >= 0; i--) { const asset = currentFrame.assets[i]; if (clickX >= asset.x && clickX <= asset.x + asset.width && clickY >= asset.y && clickY <= asset.y + asset.height) { selectedAsset = asset; break; } } // 移除所有选中状态 currentFrame.assets.forEach(a => delete a.selected); // 设置新的选中状态 if (selectedAsset) { selectedAsset.selected = true; } drawCurrentFrame(); } // ==================== 绘制当前帧 ==================== function drawCurrentFrame() { ctx.clearRect(0, 0, canvas.width, canvas.height); const currentScene = scenes[currentSceneIndex]; if (!currentScene) return; const currentFrame = currentScene.frames[currentFrameIndex]; if (!currentFrame) return; // 绘制背景色 ctx.fillStyle = '#0a0a14'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 绘制所有素材 currentFrame.assets.forEach(asset => { ctx.font = '40px "Segoe UI Emoji", "Apple Color Emoji", sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(asset.emoji, asset.x + asset.width/2, asset.y + asset.height/2); // 如果被选中,绘制边框 if (asset.selected) { ctx.strokeStyle = '#e94560'; ctx.lineWidth = 2; ctx.setLineDash([5, 5]); ctx.strokeRect(asset.x, asset.y, asset.width, asset.height); ctx.setLineDash([]); } }); } // ==================== 场景管理 ==================== function addScene() { const newScene = { id: 'scene-' + Date.now(), name: '场景' + (scenes.length + 1), frames: [ { id: 'frame-' + Date.now() + '-1', assets: [] } ] }; scenes.push(newScene); currentSceneIndex = scenes.length - 1; currentFrameIndex = 0; updateUI(); drawCurrentFrame(); } // ==================== 帧管理 ==================== function addFrame() { const currentScene = scenes[currentSceneIndex]; const currentFrame = currentScene.frames[currentFrameIndex]; // 深拷贝当前帧的素材 const newAssets = JSON.parse(JSON.stringify(currentFrame.assets)); // 移除选中状态 newAssets.forEach(a => delete a.selected); const newFrame = { id: 'frame-' + Date.now(), assets: newAssets }; currentScene.frames.splice(currentFrameIndex + 1, 0, newFrame); currentFrameIndex++; updateUI(); drawCurrentFrame(); } function deleteFrame() { const currentScene = scenes[currentSceneIndex]; if (currentScene.frames.length <= 1) { alert('每个场景至少保留一帧'); return; } currentScene.frames.splice(currentFrameIndex, 1); if (currentFrameIndex >= currentScene.frames.length) { currentFrameIndex = currentScene.frames.length - 1; } updateUI(); drawCurrentFrame(); } // ==================== 动画控制 ==================== function togglePlayback() { if (isPlaying) { stopAnimation(); } else { startAnimation(); } } function startAnimation() { if (isPlaying) return; isPlaying = true; updatePlayButton(); animationInterval = setInterval(() => { const currentScene = scenes[currentSceneIndex]; if (currentFrameIndex < currentScene.frames.length - 1) { currentFrameIndex++; } else { currentFrameIndex = 0; } updateUI(); drawCurrentFrame(); }, 1000 / fps); } function stopAnimation() { if (animationInterval) { clearInterval(animationInterval); animationInterval = null; } isPlaying = false; updatePlayButton(); } function updatePlayButton() { const playBtn = document.getElementById('timelinePlayBtn'); if (playBtn) { playBtn.textContent = isPlaying ? '⏸️ 暂停' : '▶️ 播放'; } const previewPlayBtn = document.getElementById('playPreviewBtn'); if (previewPlayBtn) { // 预览模式的播放按钮文字不变 } } // ==================== 预览模式 ==================== function enterPreviewMode() { document.body.classList.add('preview-mode'); // 如果正在播放动画,继续播放 } function exitPreviewMode() { document.body.classList.remove('preview-mode'); } // ==================== 分享功能 ==================== function showSharePanel() { document.getElementById('shareOverlay').classList.remove('hidden'); document.getElementById('sharePanel').classList.remove('hidden'); } function hideSharePanel() { document.getElementById('shareOverlay').classList.add('hidden'); document.getElementById('sharePanel').classList.add('hidden'); document.getElementById('shareUrl').innerHTML = ''; document.getElementById('copyShareBtn').style.display = 'none'; } function generateShareUrl() { // 序列化作品数据 const workData = { scenes: scenes, version: '1.0', timestamp: Date.now() }; const jsonStr = JSON.stringify(workData); // 使用encodeURIComponent编码,然后生成Data URL const encodedData = encodeURIComponent(jsonStr); const dataUrl = 'data:text/json;charset=utf-8,' + encodedData; // 显示分享链接 const shareUrlDiv = document.getElementById('shareUrl'); shareUrlDiv.innerHTML = `${dataUrl.substring(0, 50)}...`; document.getElementById('copyShareBtn').style.display = 'inline-block'; document.getElementById('copyShareBtn').dataset.url = dataUrl; } function generateEmbedCode() { // 生成iframe嵌入代码 const embedCode = ``; const shareUrlDiv = document.getElementById('shareUrl'); shareUrlDiv.innerHTML = `
${embedCode}
`; document.getElementById('copyShareBtn').style.display = 'inline-block'; document.getElementById('copyShareBtn').dataset.code = embedCode; } function copyToClipboard(e) { const btn = e.target; if (btn.dataset.url) { navigator.clipboard.writeText(btn.dataset.url); } else if (btn.dataset.code) { navigator.clipboard.writeText(btn.dataset.code); } alert('已复制到剪贴板!'); } // ==================== 导入导出 ==================== function exportScene() { const dataStr = JSON.stringify(scenes, null, 2); const blob = new Blob([dataStr], {type: 'application/json'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'hololake-scene.json'; a.click(); URL.revokeObjectURL(url); } function importScene(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { try { const importedScenes = JSON.parse(e.target.result); if (Array.isArray(importedScenes) && importedScenes.length > 0) { scenes = importedScenes; currentSceneIndex = 0; currentFrameIndex = 0; updateUI(); drawCurrentFrame(); alert('导入成功!'); } else { alert('无效的场景数据'); } } catch (error) { alert('导入失败:' + error.message); } }; reader.readAsText(file); // 清空input,以便再次导入同一个文件 event.target.value = ''; } function exportScreenshot() { const dataUrl = canvas.toDataURL('image/png'); const a = document.createElement('a'); a.href = dataUrl; a.download = 'hololake-screenshot-' + Date.now() + '.png'; a.click(); } // ==================== 更新UI ==================== function updateUI() { // 更新场景列表 const sceneList = document.getElementById('sceneList'); if (sceneList) { sceneList.innerHTML = ''; scenes.forEach((scene, index) => { const sceneDiv = document.createElement('div'); sceneDiv.className = 'scene-item' + (index === currentSceneIndex ? ' active' : ''); sceneDiv.innerHTML = ` ${scene.name} `; sceneDiv.addEventListener('click', (e) => { if (e.target.tagName !== 'BUTTON') { switchScene(index); } }); sceneList.appendChild(sceneDiv); }); } // 更新帧列表 const framesContainer = document.getElementById('framesContainer'); if (framesContainer && scenes[currentSceneIndex]) { framesContainer.innerHTML = ''; scenes[currentSceneIndex].frames.forEach((frame, index) => { const frameDiv = document.createElement('div'); frameDiv.className = 'frame-thumb' + (index === currentFrameIndex ? ' active' : ''); frameDiv.innerHTML = `
${index + 1}
${frame.assets.length}个素材
`; frameDiv.addEventListener('click', () => { currentFrameIndex = index; drawCurrentFrame(); updateUI(); }); framesContainer.appendChild(frameDiv); }); } // 更新帧计数器 const frameCounter = document.getElementById('frame-counter'); if (frameCounter && scenes[currentSceneIndex]) { frameCounter.textContent = `帧 ${currentFrameIndex + 1}/${scenes[currentSceneIndex].frames.length}`; } } function switchScene(index) { if (index >= 0 && index < scenes.length) { currentSceneIndex = index; currentFrameIndex = 0; if (isPlaying) { stopAnimation(); } updateUI(); drawCurrentFrame(); } } function deleteScene(index) { if (scenes.length <= 1) { alert('至少保留一个场景'); return; } scenes.splice(index, 1); if (currentSceneIndex >= scenes.length) { currentSceneIndex = scenes.length - 1; } currentFrameIndex = 0; if (isPlaying) { stopAnimation(); } updateUI(); drawCurrentFrame(); } // ==================== 启动初始化 ==================== window.onload = init;