2026-05-10 13:12:44 +08:00

537 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 = `<a href="${dataUrl}" target="_blank">${dataUrl.substring(0, 50)}...</a>`;
document.getElementById('copyShareBtn').style.display = 'inline-block';
document.getElementById('copyShareBtn').dataset.url = dataUrl;
}
function generateEmbedCode() {
// 生成iframe嵌入代码
const embedCode = `<iframe src="preview-player.html" width="800" height="450" frameborder="0" allowfullscreen></iframe>`;
const shareUrlDiv = document.getElementById('shareUrl');
shareUrlDiv.innerHTML = `<pre style="white-space: pre-wrap; background:#0a0a14; padding:10px; border-radius:8px;">${embedCode}</pre>`;
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 = `
<span>${scene.name}</span>
<button onclick="deleteScene(${index})" style="background:none; border:none; color:#e94560; cursor:pointer;">✖️</button>
`;
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 = `
<div class="frame-index">${index + 1}</div>
<div>${frame.assets.length}个素材</div>
`;
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;