简易Scratch编程工具edit icon

Fork(复制)
下载
嵌入
设置
BUG反馈
index.html
现在支持上传本地图片了!
            
            <!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>简易Scratch编程工具</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #6e8efb, #a777e3);
            color: #333;
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: rgba(255, 255, 255, 0.9);
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
            overflow: hidden;
        }
        
        header {
            background: #4a6cf9;
            color: white;
            padding: 20px;
            text-align: center;
        }
        
        h1 {
            font-size: 2.5rem;
            margin-bottom: 10px;
        }
        
        .subtitle {
            font-size: 1.2rem;
            opacity: 0.9;
        }
        
        .main-content {
            display: flex;
            padding: 20px;
            gap: 20px;
        }
        
        .blocks-panel {
            flex: 1;
            background: #f0f4ff;
            border-radius: 10px;
            padding: 15px;
        }
        
        .blocks-category {
            margin-bottom: 20px;
        }
        
        .category-title {
            font-size: 1.2rem;
            color: #4a6cf9;
            padding: 10px;
            border-bottom: 2px solid #4a6cf9;
            margin-bottom: 10px;
        }
        
        .block {
            background: white;
            padding: 12px;
            margin: 8px 0;
            border-radius: 8px;
            cursor: move;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
            border-left: 5px solid #4a6cf9;
            transition: transform 0.2s, box-shadow 0.2s;
            user-select: none;
        }
        
        .block:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
        }
        
        .motion-block {
            border-left-color: #4a6cf9;
        }
        
        .looks-block {
            border-left-color: #9c56e0;
        }
        
        .control-block {
            border-left-color: #e74c3c;
        }
        
        .workspace {
            flex: 2;
            background: #e6e9ff;
            border-radius: 10px;
            padding: 15px;
            min-height: 400px;
        }
        
        .workspace .block {
            margin: 10px 0;
        }
        
        .stage-container {
            flex: 2;
            background: #d1d9ff;
            border-radius: 10px;
            padding: 15px;
            display: flex;
            flex-direction: column;
        }
        
        .stage {
            background: white;
            border: 3px solid #4a6cf9;
            border-radius: 8px;
            flex-grow: 1;
            display: flex;
            justify-content: center;
            align-items: center;
            overflow: hidden;
        }
        
        canvas {
            background: white;
        }
        
        .controls {
            display: flex;
            justify-content: center;
            gap: 20px;
            padding: 20px;
        }
        
        .btn {
            padding: 12px 25px;
            border: none;
            border-radius: 50px;
            font-size: 1.1rem;
            cursor: pointer;
            transition: all 0.3s;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
        }
        
        .run-btn {
            background: #2ecc71;
            color: white;
        }
        
        .stop-btn {
            background: #e74c3c;
            color: white;
        }
        
        .reset-btn {
            background: #f39c12;
            color: white;
        }
        
        .btn:hover {
            transform: translateY(-3px);
            box-shadow: 0 6px 12px rgba(0, 0, 0, 0.25);
        }
        
        .btn:active {
            transform: translateY(0);
        }
        
        .btn:disabled {
            background: #ccc;
            cursor: not-allowed;
            transform: none;
            box-shadow: none;
        }
        
        .instructions {
            background: #f8f9ff;
            padding: 20px;
            border-radius: 10px;
            margin: 20px;
        }
        
        .instructions h3 {
            color: #4a6cf9;
            margin-bottom: 10px;
        }
        
        .status {
            text-align: center;
            padding: 10px;
            font-weight: bold;
            color: #4a6cf9;
        }
        
        footer {
            text-align: center;
            padding: 20px;
            color: white;
            background: #4a6cf9;
            margin-top: 20px;
        }
        
        @media (max-width: 900px) {
            .main-content {
                flex-direction: column;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>简易Scratch编程工具 </h1>
            <p class="subtitle">通过拖放积木创建程序,并查看运行效果</p>
        </header>
        
        <div class="status" id="status">就绪</div>
        
        <div class="main-content">
            <div class="blocks-panel">
                <div class="blocks-category">
                    <h3 class="category-title">运动</h3>
                    <div class="block motion-block" draggable="true" data-type="move">移动 10 步</div>
                    <div class="block motion-block" draggable="true" data-type="turn">向右旋转 15 度</div>
                    <div class="block motion-block" draggable="true" data-type="goto">移到随机位置</div>
                </div>
                
                <div class="blocks-category">
                    <h3 class="category-title">外观</h3>
                    <div class="block looks-block" draggable="true" data-type="say">说 你好! 2 秒</div>
                    <div class="block looks-block" draggable="true" data-type="size">将大小增加 10</div>
                </div>
                
                <div class="blocks-category">
                    <h3 class="category-title">控制</h3>
                    <div class="block control-block" draggable="true" data-type="repeat">重复执行 3 次</div>
                    <div class="block control-block" draggable="true" data-type="forever">永远重复</div>
                </div>
            </div>
            
            <div class="workspace" id="workspace">
                <h3>拖放积木到这里</h3>
            </div>
            
            <div class="stage-container">
                <h3>舞台</h3>
                <div class="stage">
                    <canvas id="stageCanvas" width="300" height="300"></canvas>
                </div>
            </div>
        </div>
        
        <div class="controls">
            <button class="btn run-btn" id="runBtn">运行</button>
            <button class="btn stop-btn" id="stopBtn" disabled>停止</button>
            <button class="btn reset-btn" id="resetBtn">重置</button>
        </div>
        
        <div class="instructions">
            <h3>使用说明</h3>
            <p>1. 从左侧选择积木并拖放到中间工作区</p>
            <p>2. 点击"运行"按钮执行程序</p>
            <p>3. 在右侧舞台查看程序运行效果</p>
            <p>4. 点击"停止"按钮停止程序执行</p>
            <p>5. 点击"重置"按钮清除工作区和重置舞台</p>
            <p>6. 双击工作区中的积木可以删除它</p>
        </div>
        
        <footer>
            <p>© 2023 简易Scratch编程工具 | 设计用于教育目的</p>
        </footer>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const workspace = document.getElementById('workspace');
            const canvas = document.getElementById('stageCanvas');
            const ctx = canvas.getContext('2d');
            const runBtn = document.getElementById('runBtn');
            const stopBtn = document.getElementById('stopBtn');
            const resetBtn = document.getElementById('resetBtn');
            const status = document.getElementById('status');
            
            // 角色状态
            let character = {
                x: canvas.width / 2,
                y: canvas.height / 2,
                size: 30,
                direction: 90, // 角度,90度向右
                visible: true,
                speech: null,
                speechTime: 0
            };
            
            let animationId = null;
            let isRunning = false;
            let executionQueue = [];
            
            // 初始化舞台
            drawCharacter();
            
            // 设置拖放事件
            const blocks = document.querySelectorAll('.blocks-panel .block');
            blocks.forEach(block => {
                block.addEventListener('dragstart', function(e) {
                    e.dataTransfer.setData('text/plain', block.getAttribute('data-type'));
                });
            });
            
            workspace.addEventListener('dragover', function(e) {
                e.preventDefault();
                workspace.style.backgroundColor = '#d6dcff';
            });
            
            workspace.addEventListener('dragleave', function() {
                workspace.style.backgroundColor = '#e6e9ff';
            });
            
            workspace.addEventListener('drop', function(e) {
                e.preventDefault();
                workspace.style.backgroundColor = '#e6e9ff';
                
                const blockType = e.dataTransfer.getData('text/plain');
                const originalBlock = document.querySelector(`.blocks-panel .block[data-type="${blockType}"]`);
                const newBlock = originalBlock.cloneNode(true);
                
                // 设置可拖动和删除
                newBlock.addEventListener('dragstart', function(ev) {
                    ev.dataTransfer.setData('text/plain', blockType);
                });
                
                newBlock.addEventListener('dblclick', function() {
                    if (!isRunning) {
                        workspace.removeChild(newBlock);
                        updateStatus("积木已删除");
                    } else {
                        updateStatus("请先停止程序再删除积木");
                    }
                });
                
                workspace.appendChild(newBlock);
                updateStatus("积木已添加到工作区");
            });
            
            // 按钮事件
            runBtn.addEventListener('click', function() {
                if (isRunning) return;
                runProgram();
            });
            
            stopBtn.addEventListener('click', function() {
                stopProgram();
            });
            
            resetBtn.addEventListener('click', function() {
                resetProgram();
            });
            
            // 运行程序
            function runProgram() {
                const blocks = workspace.querySelectorAll('.block');
                
                // 修复:正确识别工作区中的积木数量
                if (blocks.length === 0) {
                    updateStatus("请拖放一些积木到工作区!");
                    return;
                }
                
                isRunning = true;
                runBtn.disabled = true;
                stopBtn.disabled = false;
                updateStatus("程序运行中...");
                
                // 清空执行队列
                executionQueue = [];
                
                // 将积木转换为执行指令
                for (let i = 0; i < blocks.length; i++) {
                    const type = blocks[i].getAttribute('data-type');
                    executionQueue.push({
                        type: type,
                        element: blocks[i]
                    });
                }
                
                // 开始执行
                executeNextCommand(0);
            }
            
            function executeNextCommand(index) {
                if (!isRunning || index >= executionQueue.length) {
                    if (isRunning) {
                        updateStatus("程序执行完成");
                    }
                    isRunning = false;
                    runBtn.disabled = false;
                    stopBtn.disabled = true;
                    return;
                }
                
                const command = executionQueue[index];
                highlightBlock(command.element, true);
                
                switch(command.type) {
                    case 'move':
                        moveCharacter(10);
                        setTimeout(() => {
                            highlightBlock(command.element, false);
                            executeNextCommand(index + 1);
                        }, 500);
                        break;
                    case 'turn':
                        turnCharacter(15);
                        setTimeout(() => {
                            highlightBlock(command.element, false);
                            executeNextCommand(index + 1);
                        }, 500);
                        break;
                    case 'goto':
                        gotoRandom();
                        setTimeout(() => {
                            highlightBlock(command.element, false);
                            executeNextCommand(index + 1);
                        }, 500);
                        break;
                    case 'say':
                        saySomething("你好!", 2);
                        setTimeout(() => {
                            highlightBlock(command.element, false);
                            executeNextCommand(index + 1);
                        }, 2000);
                        break;
                    case 'size':
                        changeSize(10);
                        setTimeout(() => {
                            highlightBlock(command.element, false);
                            executeNextCommand(index + 1);
                        }, 500);
                        break;
                    case 'repeat':
                        // 修复:使用递归实现重复执行
                        const repeatCount = 3;
                        highlightBlock(command.element, false);
                        executeRepeatCommands(index + 1, repeatCount, () => {
                            // 找到重复块结束的位置
                            let endIndex = index + 1;
                            while (endIndex < executionQueue.length && 
                                  executionQueue[endIndex].type !== 'repeat' && 
                                  executionQueue[endIndex].type !== 'forever') {
                                endIndex++;
                            }
                            executeNextCommand(endIndex);
                        });
                        break;
                    case 'forever':
                        // 修复:使用递归实现永久重复
                        if (isRunning) {
                            highlightBlock(command.element, false);
                            executeForeverCommands(index + 1, () => {
                                executeNextCommand(index); // 循环回自己
                            });
                        }
                        break;
                    default:
                        setTimeout(() => {
                            highlightBlock(command.element, false);
                            executeNextCommand(index + 1);
                        }, 300);
                }
            }
            
            function executeRepeatCommands(startIndex, count, callback) {
                if (count <= 0 || !isRunning) {
                    callback();
                    return;
                }
                
                // 执行重复块内的命令
                executeCommandSequence(startIndex, () => {
                    // 完成后减少计数并再次执行
                    executeRepeatCommands(startIndex, count - 1, callback);
                });
            }
            
            function executeForeverCommands(startIndex, callback) {
                if (!isRunning) {
                    callback();
                    return;
                }
                
                // 执行永久重复块内的命令
                executeCommandSequence(startIndex, () => {
                    // 完成后再次执行
                    if (isRunning) {
                        executeForeverCommands(startIndex, callback);
                    } else {
                        callback();
                    }
                });
            }
            
            function executeCommandSequence(startIndex, callback) {
                if (!isRunning || startIndex >= executionQueue.length) {
                    callback();
                    return;
                }
                
                const command = executionQueue[startIndex];
                
                // 如果遇到控制块,停止执行
                if (command.type === 'repeat' || command.type === 'forever') {
                    callback();
                    return;
                }
                
                highlightBlock(command.element, true);
                
                switch(command.type) {
                    case 'move':
                        moveCharacter(10);
                        setTimeout(() => {
                            highlightBlock(command.element, false);
                            executeCommandSequence(startIndex + 1, callback);
                        }, 500);
                        break;
                    case 'turn':
                        turnCharacter(15);
                        setTimeout(() => {
                            highlightBlock(command.element, false);
                            executeCommandSequence(startIndex + 1, callback);
                        }, 500);
                        break;
                    case 'goto':
                        gotoRandom();
                        setTimeout(() => {
                            highlightBlock(command.element, false);
                            executeCommandSequence(startIndex + 1, callback);
                        }, 500);
                        break;
                    case 'say':
                        saySomething("你好!", 2);
                        setTimeout(() => {
                            highlightBlock(command.element, false);
                            executeCommandSequence(startIndex + 1, callback);
                        }, 2000);
                        break;
                    case 'size':
                        changeSize(10);
                        setTimeout(() => {
                            highlightBlock(command.element, false);
                            executeCommandSequence(startIndex + 1, callback);
                        }, 500);
                        break;
                    default:
                        setTimeout(() => {
                            highlightBlock(command.element, false);
                            executeCommandSequence(startIndex + 1, callback);
                        }, 300);
                }
            }
            
            function highlightBlock(element, highlight) {
                if (highlight) {
                    element.style.backgroundColor = '#ffeb3b';
                    element.style.boxShadow = '0 0 10px #ffeb3b';
                } else {
                    element.style.backgroundColor = 'white';
                    element.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.1)';
                }
            }
            
            function stopProgram() {
                isRunning = false;
                runBtn.disabled = false;
                stopBtn.disabled = true;
                updateStatus("程序已停止");
                
                // 移除所有高亮
                const blocks = workspace.querySelectorAll('.block');
                blocks.forEach(block => {
                    highlightBlock(block, false);
                });
            }
            
            function resetProgram() {
                stopProgram();
                
                // 重置角色
                character = {
                    x: canvas.width / 2,
                    y: canvas.height / 2,
                    size: 30,
                    direction: 90,
                    visible: true,
                    speech: null,
                    speechTime: 0
                };
                
                // 清空工作区
                while (workspace.childNodes.length > 1) {
                    workspace.removeChild(workspace.lastChild);
                }
                
                drawCharacter();
                updateStatus("已重置");
            }
            
            function updateStatus(message) {
                status.textContent = message;
            }
            
            // 角色动作函数
            function moveCharacter(steps) {
                const rad = character.direction * Math.PI / 180;
                character.x += steps * Math.cos(rad);
                character.y -= steps * Math.sin(rad);
                
                // 边界检查
                character.x = Math.max(0, Math.min(canvas.width, character.x));
                character.y = Math.max(0, Math.min(canvas.height, character.y));
                
                drawCharacter();
            }
            
            function turnCharacter(degrees) {
                character.direction += degrees;
                drawCharacter();
            }
            
            function gotoRandom() {
                character.x = Math.random() * canvas.width;
                character.y = Math.random() * canvas.height;
                drawCharacter();
            }
            
            function saySomething(text, seconds) {
                character.speech = text;
                drawCharacter();
                
                setTimeout(() => {
                    character.speech = null;
                    drawCharacter();
                }, seconds * 1000);
            }
            
            function changeSize(delta) {
                character.size += delta;
                character.size = Math.max(5, Math.min(100, character.size));
                drawCharacter();
            }
            
            // 绘制角色
            function drawCharacter() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                
                // 绘制背景网格
                ctx.strokeStyle = '#eee';
                ctx.lineWidth = 1;
                
                for (let x = 0; x < canvas.width; x += 20) {
                    ctx.beginPath();
                    ctx.moveTo(x, 0);
                    ctx.lineTo(x, canvas.height);
                    ctx.stroke();
                }
                
                for (let y = 0; y < canvas.height; y += 20) {
                    ctx.beginPath();
                    ctx.moveTo(0, y);
                    ctx.lineTo(canvas.width, y);
                    ctx.stroke();
                }
                
                if (!character.visible) return;
                
                // 绘制角色(简单圆形)
                ctx.fillStyle = '#4a6cf9';
                ctx.beginPath();
                ctx.arc(character.x, character.y, character.size / 2, 0, Math.PI * 2);
                ctx.fill();
                
                // 绘制方向指示器
                const rad = character.direction * Math.PI / 180;
                const indicatorX = character.x + (character.size / 2) * Math.cos(rad);
                const indicatorY = character.y - (character.size / 2) * Math.sin(rad);
                
                ctx.strokeStyle = '#ff9900';
                ctx.lineWidth = 3;
                ctx.beginPath();
                ctx.moveTo(character.x, character.y);
                ctx.lineTo(indicatorX, indicatorY);
                ctx.stroke();
                
                // 绘制说话气泡
                if (character.speech) {
                    ctx.fillStyle = 'white';
                    ctx.strokeStyle = '#4a6cf9';
                    ctx.lineWidth = 2;
                    
                    const text = character.speech;
                    ctx.font = '14px Arial';
                    const textWidth = ctx.measureText(text).width;
                    const bubbleWidth = textWidth + 20;
                    const bubbleHeight = 30;
                    const bubbleX = character.x - bubbleWidth / 2;
                    const bubbleY = character.y - character.size / 2 - bubbleHeight - 10;
                    
                    // 绘制气泡
                    ctx.beginPath();
                    ctx.roundRect(bubbleX, bubbleY, bubbleWidth, bubbleHeight, 10);
                    ctx.fill();
                    ctx.stroke();
                    
                    // 绘制指向线
                    ctx.beginPath();
                    ctx.moveTo(character.x, character.y - character.size / 2);
                    ctx.lineTo(character.x, bubbleY + bubbleHeight);
                    ctx.lineTo(character.x + 10, bubbleY + bubbleHeight - 10);
                    ctx.stroke();
                    
                    // 绘制文本
                    ctx.fillStyle = 'black';
                    ctx.textAlign = 'center';
                    ctx.textBaseline = 'middle';
                    ctx.fillText(text, character.x, bubbleY + bubbleHeight / 2);
                }
            }
            
            // 添加roundRect方法如果不支持
            if (!CanvasRenderingContext2D.prototype.roundRect) {
                CanvasRenderingContext2D.prototype.roundRect = function(x, y, width, height, radius) {
                    if (width < 2 * radius) radius = width / 2;
                    if (height < 2 * radius) radius = height / 2;
                    this.beginPath();
                    this.moveTo(x + radius, y);
                    this.arcTo(x + width, y, x + width, y + height, radius);
                    this.arcTo(x + width, y + height, x, y + height, radius);
                    this.arcTo(x, y + height, x, y, radius);
                    this.arcTo(x, y, x + width, y, radius);
                    this.closePath();
                    return this;
                };
            }
        });
    </script>
</body>
</html>
        
预览
控制台