【白板类-03-01】20260402画板01(html+希沃白板)

背景需求

适合幼儿玩的交互HTML小游戏的名称,表格形式

制作html的画板

python 复制代码
def generate_drawing_board():
    """生成幼儿画板HTML代码"""
    return """<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>幼儿魔法画板 - 希沃白板专用</title>
    <style>
        /* 全局重置:锁定屏幕,无滚动条,完全填充 */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none;
        }

        body {
            background: #2b2b3a;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            font-family: 'Segoe UI', 'Comic Neue', 'Comic Sans MS', 'Chalkboard SE', cursive, sans-serif;
            overflow: hidden;
            position: fixed;
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }

        .draw-app {
            width: 100vw;
            height: 100vh;
            background: #1e1f2c;
            display: flex;
            flex-direction: column;
            padding: 12px 20px 20px 20px;
            overflow: hidden;
        }

        .toolbar {
            background: #fdf8e7;
            border-radius: 60px;
            padding: 10px 20px;
            margin-bottom: 18px;
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            justify-content: center;
            gap: 12px 18px;
            box-shadow: 0 6px 0 #c0a67a;
            border: 2px solid #e9cf9e;
        }

        .btn {
            background: #ffdd99;
            border: none;
            font-size: 1.3rem;
            font-weight: bold;
            padding: 8px 18px;
            border-radius: 48px;
            cursor: pointer;
            font-family: inherit;
            transition: 0.1s linear;
            box-shadow: 0 4px 0 #b97f44;
            color: #4a2e1f;
            display: inline-flex;
            align-items: center;
            gap: 8px;
        }
        .btn:active {
            transform: translateY(2px);
            box-shadow: 0 2px 0 #b97f44;
        }
        .btn-save {
            background: #6fbf4c;
            color: white;
            box-shadow: 0 4px 0 #3f6b2c;
        }
        .btn-clear {
            background: #f7b05e;
        }
        .brush-active {
            background: #ffaa66;
            border: 3px solid #f06414;
            box-shadow: 0 2px 0 #b45a2a;
            transform: translateY(2px);
        }
        .color-panel {
            display: flex;
            gap: 12px;
            background: #efe3c9;
            padding: 5px 15px;
            border-radius: 48px;
            align-items: center;
        }
        .color-swatch {
            width: 44px;
            height: 44px;
            border-radius: 50%;
            cursor: pointer;
            border: 3px solid white;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            transition: 0.05s linear;
        }
        .color-swatch:active {
            transform: scale(0.92);
        }
        .selected-color {
            border: 4px solid gold;
            box-shadow: 0 0 0 2px #2b2b2b;
        }
        .brush-size {
            background: #ffe4b5;
            border-radius: 40px;
            padding: 5px 14px;
            display: flex;
            align-items: center;
            gap: 10px;
            font-weight: bold;
            font-size: 1.2rem;
        }
        .size-value {
            background: white;
            padding: 4px 12px;
            border-radius: 30px;
            min-width: 55px;
            text-align: center;
        }
        .size-btn {
            background: #c2a15b;
            border: none;
            font-size: 1.6rem;
            font-weight: bold;
            width: 38px;
            border-radius: 50%;
            color: white;
            cursor: pointer;
            box-shadow: 0 2px 0 #6e4f28;
        }
        .size-btn:active {
            transform: translateY(2px);
        }
        .canvas-container {
            flex: 1;
            background: #ffffff;
            border-radius: 32px;
            box-shadow: 0 8px 20px rgba(0,0,0,0.3);
            overflow: hidden;
            border: 4px solid #ffde9e;
            position: relative;
            cursor: crosshair;
        }
        canvas {
            display: block;
            width: 100%;
            height: 100%;
            background: white;
            cursor: crosshair;
        }
        .info-bar {
            margin-top: 12px;
            text-align: center;
            font-size: 1rem;
            color: #ffecb3;
            background: #2a2a38;
            border-radius: 40px;
            padding: 5px;
            font-weight: bold;
        }
        @media (max-width: 700px) {
            .toolbar { gap: 8px; padding: 8px 12px; }
            .btn { font-size: 1rem; padding: 6px 12px; }
            .color-swatch { width: 36px; height: 36px; }
        }
    </style>
</head>
<body>
<div class="draw-app">
    <div class="toolbar">
        <button id="brushBtn" class="btn brush-active">🖌️ 画笔</button>
        <button id="fillBtn" class="btn">🎨 填色桶</button>
        
        <div class="color-panel">
            <div class="color-swatch" style="background:#000000;" data-color="#000000"></div>
            <div class="color-swatch" style="background:#FF4136;" data-color="#FF4136"></div>
            <div class="color-swatch" style="background:#2ECC40;" data-color="#2ECC40"></div>
            <div class="color-swatch" style="background:#0074D9;" data-color="#0074D9"></div>
            <div class="color-swatch" style="background:#FF851B;" data-color="#FF851B"></div>
            <div class="color-swatch" style="background:#F012BE;" data-color="#F012BE"></div>
            <div class="color-swatch" style="background:#FFDC00;" data-color="#FFDC00"></div>
            <div class="color-swatch" style="background:#39CCCC;" data-color="#39CCCC"></div>
            <div class="color-swatch" style="background:#85144b;" data-color="#85144b"></div>
        </div>

        <div class="brush-size">
            <span>✏️ 画笔大小</span>
            <button id="sizeMinus" class="size-btn">-</button>
            <span id="brushSizeValue" class="size-value">8</span>
            <button id="sizePlus" class="size-btn">+</button>
        </div>

        <button id="clearCanvasBtn" class="btn btn-clear">🧽 清空画板</button>
        <button id="saveCanvasBtn" class="btn btn-save">💾 保存作品</button>
    </div>
    
    <div class="canvas-container">
        <canvas id="mainCanvas"></canvas>
    </div>
    <div class="info-bar">
        🌈 点选颜色 → 画笔颜色 / 填充颜色 | 点填色桶再点击封闭区域就能填充颜色哟!
    </div>
</div>

<script>
    (function() {
        const canvas = document.getElementById('mainCanvas');
        let ctx = canvas.getContext('2d');
        
        let isDrawing = false;
        let currentMode = 'brush';
        let currentColor = '#000000';
        let brushSize = 8;
        
        const brushBtn = document.getElementById('brushBtn');
        const fillBtn = document.getElementById('fillBtn');
        const colorSwatches = document.querySelectorAll('.color-swatch');
        const sizeMinus = document.getElementById('sizeMinus');
        const sizePlus = document.getElementById('sizePlus');
        const brushSizeSpan = document.getElementById('brushSizeValue');
        const clearBtn = document.getElementById('clearCanvasBtn');
        const saveBtn = document.getElementById('saveCanvasBtn');
        
        let lastX = 0, lastY = 0;
        
        function resizeCanvas() {
            const container = canvas.parentElement;
            const rect = container.getBoundingClientRect();
            let savedImageData = null;
            if (canvas.width > 0 && canvas.height > 0) {
                savedImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            }
            canvas.width = rect.width;
            canvas.height = rect.height;
            ctx = canvas.getContext('2d');
            ctx.lineCap = 'round';
            ctx.lineJoin = 'round';
            if (savedImageData) {
                if (savedImageData.width === canvas.width && savedImageData.height === canvas.height) {
                    ctx.putImageData(savedImageData, 0, 0);
                } else {
                    const tempCanvas = document.createElement('canvas');
                    tempCanvas.width = savedImageData.width;
                    tempCanvas.height = savedImageData.height;
                    const tempCtx = tempCanvas.getContext('2d');
                    tempCtx.putImageData(savedImageData, 0, 0);
                    ctx.drawImage(tempCanvas, 0, 0, tempCanvas.width, tempCanvas.height, 0, 0, canvas.width, canvas.height);
                }
            }
            ctx.strokeStyle = currentColor;
            ctx.fillStyle = currentColor;
            ctx.lineWidth = brushSize;
        }
        
        function initBlankCanvas() {
            resizeCanvas();
            ctx.fillStyle = '#FFFFFF';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = currentColor;
            ctx.strokeStyle = currentColor;
        }
        
        function getCanvasCoordinates(e) {
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;
            const scaleY = canvas.height / rect.height;
            let clientX, clientY;
            if (e.touches) {
                clientX = e.touches[0].clientX;
                clientY = e.touches[0].clientY;
            } else {
                clientX = e.clientX;
                clientY = e.clientY;
            }
            let canvasX = (clientX - rect.left) * scaleX;
            let canvasY = (clientY - rect.top) * scaleY;
            canvasX = Math.min(Math.max(0, canvasX), canvas.width);
            canvasY = Math.min(Math.max(0, canvasY), canvas.height);
            return { x: canvasX, y: canvasY };
        }
        
        function startDraw(e) {
            if (currentMode === 'brush') {
                isDrawing = true;
                const pos = getCanvasCoordinates(e);
                lastX = pos.x;
                lastY = pos.y;
                ctx.beginPath();
                ctx.moveTo(lastX, lastY);
                ctx.lineTo(lastX+0.5, lastY+0.5);
                ctx.stroke();
                ctx.beginPath();
            }
        }
        
        function drawMove(e) {
            if (currentMode !== 'brush' || !isDrawing) return;
            e.preventDefault();
            const pos = getCanvasCoordinates(e);
            ctx.lineTo(pos.x, pos.y);
            ctx.stroke();
            ctx.beginPath();
            ctx.moveTo(pos.x, pos.y);
        }
        
        function endDraw() {
            if (currentMode === 'brush') {
                isDrawing = false;
                ctx.beginPath();
            }
        }
        
        function floodFill(startX, startY, targetColor, replaceColor) {
            if (targetColor === replaceColor) return;
            if (startX < 0 || startX >= canvas.width || startY < 0 || startY >= canvas.height) return;
            
            const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            const data = imageData.data;
            const width = canvas.width;
            const height = canvas.height;
            
            const targetR = (targetColor >> 16) & 0xff;
            const targetG = (targetColor >> 8) & 0xff;
            const targetB = targetColor & 0xff;
            
            const startIndex = (startY * width + startX) * 4;
            if (!(data[startIndex] === targetR && data[startIndex+1] === targetG && data[startIndex+2] === targetB)) return;
            
            const replaceR = (replaceColor >> 16) & 0xff;
            const replaceG = (replaceColor >> 8) & 0xff;
            const replaceB = replaceColor & 0xff;
            
            const stack = [{x: startX, y: startY}];
            const visited = new Uint8Array(width * height);
            
            while (stack.length) {
                const {x, y} = stack.pop();
                const idx = (y * width + x) * 4;
                if (x < 0 || x >= width || y < 0 || y >= height) continue;
                if (visited[y * width + x]) continue;
                if (!(data[idx] === targetR && data[idx+1] === targetG && data[idx+2] === targetB)) continue;
                
                data[idx] = replaceR;
                data[idx+1] = replaceG;
                data[idx+2] = replaceB;
                visited[y * width + x] = 1;
                
                stack.push({x: x+1, y});
                stack.push({x: x-1, y});
                stack.push({x, y: y+1});
                stack.push({x, y: y-1});
            }
            ctx.putImageData(imageData, 0, 0);
        }
        
        function handleFillAt(e) {
            if (currentMode !== 'fill') return;
            const pos = getCanvasCoordinates(e);
            const x = Math.floor(pos.x);
            const y = Math.floor(pos.y);
            if (x < 0 || x >= canvas.width || y < 0 || y >= canvas.height) return;
            const pixel = ctx.getImageData(x, y, 1, 1).data;
            const targetColorRGB = (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
            const newColor = parseInt(currentColor.slice(1), 16);
            if (targetColorRGB === newColor) return;
            floodFill(x, y, targetColorRGB, newColor);
        }
        
        function setMode(mode) {
            currentMode = mode;
            if (mode === 'brush') {
                brushBtn.classList.add('brush-active');
                fillBtn.classList.remove('brush-active');
                canvas.style.cursor = 'crosshair';
            } else {
                fillBtn.classList.add('brush-active');
                brushBtn.classList.remove('brush-active');
                canvas.style.cursor = 'pointer';
            }
        }
        
        function updateBrushSize(size) {
            brushSize = Math.min(60, Math.max(2, size));
            brushSizeSpan.innerText = brushSize;
            ctx.lineWidth = brushSize;
        }
        
        function setColor(colorHex) {
            currentColor = colorHex;
            ctx.strokeStyle = currentColor;
            ctx.fillStyle = currentColor;
            colorSwatches.forEach(sw => {
                if (sw.getAttribute('data-color') === colorHex) {
                    sw.classList.add('selected-color');
                } else {
                    sw.classList.remove('selected-color');
                }
            });
        }
        
        function clearCanvas() {
            ctx.fillStyle = '#FFFFFF';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = currentColor;
            ctx.strokeStyle = currentColor;
            ctx.lineWidth = brushSize;
        }
        
        function saveCanvasToDesktop() {
            const link = document.createElement('a');
            const timestamp = new Date().toISOString().slice(0,19).replace(/:/g, '-');
            link.download = `我的画作_${timestamp}.png`;
            link.href = canvas.toDataURL('image/png');
            link.click();
        }
        
        function attachCanvasEvents() {
            canvas.addEventListener('mousedown', startDraw);
            canvas.addEventListener('mousemove', drawMove);
            canvas.addEventListener('mouseup', endDraw);
            canvas.addEventListener('mouseleave', endDraw);
            
            canvas.addEventListener('touchstart', (e) => {
                e.preventDefault();
                const touch = e.touches[0];
                startDraw({ clientX: touch.clientX, clientY: touch.clientY });
            });
            canvas.addEventListener('touchmove', (e) => {
                e.preventDefault();
                const touch = e.touches[0];
                drawMove({ clientX: touch.clientX, clientY: touch.clientY });
            });
            canvas.addEventListener('touchend', (e) => {
                e.preventDefault();
                endDraw();
            });
            canvas.addEventListener('touchcancel', (e) => {
                e.preventDefault();
                endDraw();
            });
            
            canvas.addEventListener('click', (e) => {
                if (currentMode === 'fill') {
                    handleFillAt(e);
                }
            });
        }
        
        function initUI() {
            brushBtn.addEventListener('click', () => setMode('brush'));
            fillBtn.addEventListener('click', () => setMode('fill'));
            sizeMinus.addEventListener('click', () => updateBrushSize(brushSize - 2));
            sizePlus.addEventListener('click', () => updateBrushSize(brushSize + 2));
            colorSwatches.forEach(sw => {
                sw.addEventListener('click', () => {
                    const color = sw.getAttribute('data-color');
                    if (color) setColor(color);
                });
            });
            clearBtn.addEventListener('click', clearCanvas);
            saveBtn.addEventListener('click', saveCanvasToDesktop);
        }
        
        function init() {
            initBlankCanvas();
            updateBrushSize(8);
            setColor('#000000');
            setMode('brush');
            attachCanvasEvents();
            initUI();
            window.addEventListener('resize', () => resizeCanvas());
        }
        
        init();
    })();
</script>
</body>
</html>"""


if __name__ == "__main__":
    # 调用函数获取HTML内容
    html_content = generate_drawing_board()
    
    # 输出到文件(保存到桌面)
    import os
    desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
    output_file = os.path.join(desktop_path, r"D:\test\20桌面素材\20260329画布\01幼儿画板.html")
    
    with open(output_file, "w", encoding="utf-8") as f:
        f.write(html_content)
    
    print(f"✅ 幼儿画板已生成!文件保存在:{output_file}")
    print("💡 双击打开HTML文件即可使用画板")

但是调色板在顶部,如果放到希沃白板上,小班幼儿的身高,肯定点不到最上面的按钮

python 复制代码
'''
h5画板
Deepseek,阿夏
20260402
'''


def generate_drawing_board():
    """生成幼儿画板HTML代码 - 工具栏在右下侧,适合幼儿操作"""
    return """<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>幼儿魔法画板 - 希沃白板专用</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none;
        }

        body {
            background: #2b2b3a;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            font-family: 'Segoe UI', 'Comic Neue', 'Comic Sans MS', 'Chalkboard SE', cursive, sans-serif;
            overflow: hidden;
            position: fixed;
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }

        .draw-app {
            width: 100vw;
            height: 100vh;
            background: #1e1f2c;
            display: flex;
            position: relative;
            overflow: hidden;
        }

        /* 画布区域 - 占据整个左侧和大部分空间 */
        .canvas-container {
            flex: 1;
            background: #ffffff;
            margin: 15px 15px 15px 15px;
            border-radius: 32px;
            box-shadow: 0 8px 20px rgba(0,0,0,0.3);
            overflow: hidden;
            border: 4px solid #ffde9e;
            position: relative;
            cursor: crosshair;
        }
        
        canvas {
            display: block;
            width: 100%;
            height: 100%;
            background: white;
            cursor: crosshair;
        }

        /* 右侧工具栏 - 靠下位置,大按钮,幼儿友好 */
        .toolbar-right {
            position: absolute;
            right: 20px;
            bottom: 20px;
            width: 140px;
            background: rgba(253, 248, 231, 0.95);
            border-radius: 40px;
            padding: 16px 12px;
            display: flex;
            flex-direction: column;
            gap: 15px;
            box-shadow: 0 8px 20px rgba(0,0,0,0.3);
            border: 3px solid #e9cf9e;
            backdrop-filter: blur(5px);
            z-index: 100;
        }

        /* 模式切换按钮组 */
        .mode-buttons {
            display: flex;
            gap: 12px;
            justify-content: center;
        }
        
        .btn {
            background: #ffdd99;
            border: none;
            font-size: 1.6rem;
            font-weight: bold;
            padding: 12px 0;
            border-radius: 60px;
            cursor: pointer;
            font-family: inherit;
            transition: 0.1s linear;
            box-shadow: 0 5px 0 #b97f44;
            color: #4a2e1f;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 5px;
            flex: 1;
        }
        
        .btn:active {
            transform: translateY(2px);
            box-shadow: 0 2px 0 #b97f44;
        }
        
        .btn-save {
            background: #6fbf4c;
            color: white;
            box-shadow: 0 5px 0 #3f6b2c;
        }
        
        .btn-clear {
            background: #f7b05e;
        }
        
        .brush-active {
            background: #ffaa66;
            border: 3px solid #f06414;
            box-shadow: 0 2px 0 #b45a2a;
            transform: translateY(3px);
        }
        
        /* 颜色选择器 - 网格布局 */
        .color-panel {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 10px;
            background: #efe3c9;
            padding: 12px;
            border-radius: 35px;
        }
        
        .color-swatch {
            width: 100%;
            aspect-ratio: 1;
            border-radius: 20px;
            cursor: pointer;
            border: 3px solid white;
            box-shadow: 0 3px 8px rgba(0,0,0,0.2);
            transition: 0.05s linear;
        }
        
        .color-swatch:active {
            transform: scale(0.92);
        }
        
        .selected-color {
            border: 4px solid gold;
            box-shadow: 0 0 0 3px #ffaa00;
            transform: scale(1.02);
        }
        
        /* 画笔大小控制 */
        .brush-size {
            background: #ffe4b5;
            border-radius: 40px;
            padding: 8px 12px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 8px;
            font-weight: bold;
            font-size: 1.1rem;
        }
        
        .brush-size span {
            font-size: 1rem;
        }
        
        .size-value {
            background: white;
            padding: 5px 10px;
            border-radius: 30px;
            min-width: 45px;
            text-align: center;
            font-weight: bold;
            font-size: 1.2rem;
        }
        
        .size-btn {
            background: #c2a15b;
            border: none;
            font-size: 1.5rem;
            font-weight: bold;
            width: 36px;
            height: 36px;
            border-radius: 50%;
            color: white;
            cursor: pointer;
            box-shadow: 0 3px 0 #6e4f28;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        
        .size-btn:active {
            transform: translateY(2px);
            box-shadow: 0 1px 0 #6e4f28;
        }
        
        /* 底部简单提示 */
        .info-bar {
            position: absolute;
            left: 20px;
            bottom: 20px;
            background: rgba(42, 42, 56, 0.85);
            backdrop-filter: blur(4px);
            border-radius: 30px;
            padding: 8px 16px;
            font-size: 0.85rem;
            color: #ffecb3;
            font-weight: bold;
            z-index: 99;
            pointer-events: none;
        }
        
        /* 响应式 - 如果屏幕太窄,工具栏稍微缩小 */
        @media (max-width: 800px) {
            .toolbar-right {
                width: 120px;
                padding: 12px 8px;
                gap: 10px;
            }
            .btn {
                font-size: 1.3rem;
                padding: 8px 0;
            }
            .color-swatch {
                border-radius: 12px;
            }
            .brush-size span {
                font-size: 0.8rem;
            }
            .size-value {
                font-size: 1rem;
                min-width: 35px;
            }
            .size-btn {
                width: 30px;
                height: 30px;
                font-size: 1.2rem;
            }
        }
        
        @media (max-width: 600px) {
            .toolbar-right {
                width: 100px;
                right: 10px;
                bottom: 10px;
            }
            .btn {
                font-size: 1.1rem;
            }
            .color-panel {
                gap: 6px;
                padding: 8px;
            }
        }
        
        /* 触摸优化 */
        .btn, .color-swatch, .size-btn {
            touch-action: manipulation;
        }
    </style>
</head>
<body>
<div class="draw-app">
    <div class="canvas-container">
        <canvas id="mainCanvas"></canvas>
    </div>
    
    <!-- 右侧下方工具栏 - 幼儿触手可及 -->
    <div class="toolbar-right">
        <!-- 画笔/填充模式切换 -->
        <div class="mode-buttons">
            <button id="brushBtn" class="btn brush-active">🖌️</button>
            <button id="fillBtn" class="btn">🎨</button>
        </div>
        
        <!-- 颜色选择器 -->
        <div class="color-panel">
            <div class="color-swatch" style="background:#000000;" data-color="#000000"></div>
            <div class="color-swatch" style="background:#FF4136;" data-color="#FF4136"></div>
            <div class="color-swatch" style="background:#2ECC40;" data-color="#2ECC40"></div>
            <div class="color-swatch" style="background:#0074D9;" data-color="#0074D9"></div>
            <div class="color-swatch" style="background:#FF851B;" data-color="#FF851B"></div>
            <div class="color-swatch" style="background:#F012BE;" data-color="#F012BE"></div>
            <div class="color-swatch" style="background:#FFDC00;" data-color="#FFDC00"></div>
            <div class="color-swatch" style="background:#39CCCC;" data-color="#39CCCC"></div>
            <div class="color-swatch" style="background:#85144b;" data-color="#85144b"></div>
            <div class="color-swatch" style="background:#FFFFFF;" data-color="#FFFFFF"></div>
        </div>
        
        <!-- 画笔大小 -->
        <div class="brush-size">
            <button id="sizeMinus" class="size-btn">-</button>
            <span id="brushSizeValue" class="size-value">8</span>
            <button id="sizePlus" class="size-btn">+</button>
        </div>
        
        <!-- 操作按钮 -->
        <button id="clearCanvasBtn" class="btn btn-clear">🧽</button>
        <button id="saveCanvasBtn" class="btn btn-save">💾</button>
    </div>
    
    <div class="info-bar">
        🎨 点颜色选画笔/填充色 | 点🎨再点图画里填色
    </div>
</div>

<script>
    (function() {
        const canvas = document.getElementById('mainCanvas');
        let ctx = canvas.getContext('2d');
        
        let isDrawing = false;
        let currentMode = 'brush';
        let currentColor = '#000000';
        let brushSize = 8;
        
        const brushBtn = document.getElementById('brushBtn');
        const fillBtn = document.getElementById('fillBtn');
        const colorSwatches = document.querySelectorAll('.color-swatch');
        const sizeMinus = document.getElementById('sizeMinus');
        const sizePlus = document.getElementById('sizePlus');
        const brushSizeSpan = document.getElementById('brushSizeValue');
        const clearBtn = document.getElementById('clearCanvasBtn');
        const saveBtn = document.getElementById('saveCanvasBtn');
        
        function resizeCanvas() {
            const container = canvas.parentElement;
            const rect = container.getBoundingClientRect();
            let savedImageData = null;
            if (canvas.width > 0 && canvas.height > 0) {
                savedImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            }
            canvas.width = rect.width;
            canvas.height = rect.height;
            ctx = canvas.getContext('2d');
            ctx.lineCap = 'round';
            ctx.lineJoin = 'round';
            if (savedImageData) {
                if (savedImageData.width === canvas.width && savedImageData.height === canvas.height) {
                    ctx.putImageData(savedImageData, 0, 0);
                } else {
                    const tempCanvas = document.createElement('canvas');
                    tempCanvas.width = savedImageData.width;
                    tempCanvas.height = savedImageData.height;
                    const tempCtx = tempCanvas.getContext('2d');
                    tempCtx.putImageData(savedImageData, 0, 0);
                    ctx.drawImage(tempCanvas, 0, 0, tempCanvas.width, tempCanvas.height, 0, 0, canvas.width, canvas.height);
                }
            }
            ctx.strokeStyle = currentColor;
            ctx.fillStyle = currentColor;
            ctx.lineWidth = brushSize;
        }
        
        function initBlankCanvas() {
            resizeCanvas();
            ctx.fillStyle = '#FFFFFF';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = currentColor;
            ctx.strokeStyle = currentColor;
        }
        
        function getCanvasCoordinates(e) {
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;
            const scaleY = canvas.height / rect.height;
            let clientX, clientY;
            if (e.touches) {
                clientX = e.touches[0].clientX;
                clientY = e.touches[0].clientY;
            } else {
                clientX = e.clientX;
                clientY = e.clientY;
            }
            let canvasX = (clientX - rect.left) * scaleX;
            let canvasY = (clientY - rect.top) * scaleY;
            canvasX = Math.min(Math.max(0, canvasX), canvas.width);
            canvasY = Math.min(Math.max(0, canvasY), canvas.height);
            return { x: canvasX, y: canvasY };
        }
        
        function startDraw(e) {
            if (currentMode === 'brush') {
                isDrawing = true;
                const pos = getCanvasCoordinates(e);
                ctx.beginPath();
                ctx.moveTo(pos.x, pos.y);
                ctx.lineTo(pos.x+0.5, pos.y+0.5);
                ctx.stroke();
                ctx.beginPath();
            }
        }
        
        function drawMove(e) {
            if (currentMode !== 'brush' || !isDrawing) return;
            e.preventDefault();
            const pos = getCanvasCoordinates(e);
            ctx.lineTo(pos.x, pos.y);
            ctx.stroke();
            ctx.beginPath();
            ctx.moveTo(pos.x, pos.y);
        }
        
        function endDraw() {
            if (currentMode === 'brush') {
                isDrawing = false;
                ctx.beginPath();
            }
        }
        
        function floodFill(startX, startY, targetColor, replaceColor) {
            if (targetColor === replaceColor) return;
            if (startX < 0 || startX >= canvas.width || startY < 0 || startY >= canvas.height) return;
            
            const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            const data = imageData.data;
            const width = canvas.width;
            const height = canvas.height;
            
            const targetR = (targetColor >> 16) & 0xff;
            const targetG = (targetColor >> 8) & 0xff;
            const targetB = targetColor & 0xff;
            
            const startIndex = (startY * width + startX) * 4;
            if (!(data[startIndex] === targetR && data[startIndex+1] === targetG && data[startIndex+2] === targetB)) return;
            
            const replaceR = (replaceColor >> 16) & 0xff;
            const replaceG = (replaceColor >> 8) & 0xff;
            const replaceB = replaceColor & 0xff;
            
            const stack = [{x: startX, y: startY}];
            const visited = new Uint8Array(width * height);
            
            while (stack.length) {
                const {x, y} = stack.pop();
                const idx = (y * width + x) * 4;
                if (x < 0 || x >= width || y < 0 || y >= height) continue;
                if (visited[y * width + x]) continue;
                if (!(data[idx] === targetR && data[idx+1] === targetG && data[idx+2] === targetB)) continue;
                
                data[idx] = replaceR;
                data[idx+1] = replaceG;
                data[idx+2] = replaceB;
                visited[y * width + x] = 1;
                
                stack.push({x: x+1, y});
                stack.push({x: x-1, y});
                stack.push({x, y: y+1});
                stack.push({x, y: y-1});
            }
            ctx.putImageData(imageData, 0, 0);
        }
        
        function handleFillAt(e) {
            if (currentMode !== 'fill') return;
            const pos = getCanvasCoordinates(e);
            const x = Math.floor(pos.x);
            const y = Math.floor(pos.y);
            if (x < 0 || x >= canvas.width || y < 0 || y >= canvas.height) return;
            const pixel = ctx.getImageData(x, y, 1, 1).data;
            const targetColorRGB = (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
            const newColor = parseInt(currentColor.slice(1), 16);
            if (targetColorRGB === newColor) return;
            floodFill(x, y, targetColorRGB, newColor);
        }
        
        function setMode(mode) {
            currentMode = mode;
            if (mode === 'brush') {
                brushBtn.classList.add('brush-active');
                fillBtn.classList.remove('brush-active');
                canvas.style.cursor = 'crosshair';
            } else {
                fillBtn.classList.add('brush-active');
                brushBtn.classList.remove('brush-active');
                canvas.style.cursor = 'pointer';
            }
        }
        
        function updateBrushSize(size) {
            brushSize = Math.min(60, Math.max(2, size));
            brushSizeSpan.innerText = brushSize;
            ctx.lineWidth = brushSize;
        }
        
        function setColor(colorHex) {
            currentColor = colorHex;
            ctx.strokeStyle = currentColor;
            ctx.fillStyle = currentColor;
            colorSwatches.forEach(sw => {
                if (sw.getAttribute('data-color') === colorHex) {
                    sw.classList.add('selected-color');
                } else {
                    sw.classList.remove('selected-color');
                }
            });
        }
        
        function clearCanvas() {
            ctx.fillStyle = '#FFFFFF';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = currentColor;
            ctx.strokeStyle = currentColor;
            ctx.lineWidth = brushSize;
        }
        
        function saveCanvasToDesktop() {
            const link = document.createElement('a');
            const timestamp = new Date().toISOString().slice(0,19).replace(/:/g, '-');
            link.download = `我的画作_${timestamp}.png`;
            link.href = canvas.toDataURL('image/png');
            link.click();
        }
        
        function attachCanvasEvents() {
            canvas.addEventListener('mousedown', startDraw);
            canvas.addEventListener('mousemove', drawMove);
            canvas.addEventListener('mouseup', endDraw);
            canvas.addEventListener('mouseleave', endDraw);
            
            canvas.addEventListener('touchstart', (e) => {
                e.preventDefault();
                const touch = e.touches[0];
                startDraw({ clientX: touch.clientX, clientY: touch.clientY });
            });
            canvas.addEventListener('touchmove', (e) => {
                e.preventDefault();
                const touch = e.touches[0];
                drawMove({ clientX: touch.clientX, clientY: touch.clientY });
            });
            canvas.addEventListener('touchend', (e) => {
                e.preventDefault();
                endDraw();
            });
            canvas.addEventListener('touchcancel', (e) => {
                e.preventDefault();
                endDraw();
            });
            
            canvas.addEventListener('click', (e) => {
                if (currentMode === 'fill') {
                    handleFillAt(e);
                }
            });
        }
        
        function initUI() {
            brushBtn.addEventListener('click', () => setMode('brush'));
            fillBtn.addEventListener('click', () => setMode('fill'));
            sizeMinus.addEventListener('click', () => updateBrushSize(brushSize - 2));
            sizePlus.addEventListener('click', () => updateBrushSize(brushSize + 2));
            colorSwatches.forEach(sw => {
                sw.addEventListener('click', () => {
                    const color = sw.getAttribute('data-color');
                    if (color) setColor(color);
                });
            });
            clearBtn.addEventListener('click', clearCanvas);
            saveBtn.addEventListener('click', saveCanvasToDesktop);
        }
        
        function init() {
            initBlankCanvas();
            updateBrushSize(8);
            setColor('#000000');
            setMode('brush');
            attachCanvasEvents();
            initUI();
            window.addEventListener('resize', () => resizeCanvas());
        }
        
        init();
    })();
</script>
</body>
</html>"""


if __name__ == "__main__":
    # 调用函数获取HTML内容
    html_content = generate_drawing_board()
    
    # 输出到文件(保存到桌面)
    import os
    desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
    output_file = os.path.join(desktop_path, r"D:\test\20桌面素材\20260329画布\02幼儿画板_右侧工具栏.html")
    
    with open(output_file, "w", encoding="utf-8") as f:
        f.write(html_content)
    
    print(f"✅ 幼儿画板已生成!文件保存在:{output_file}")
    print("💡 工具栏已放在右侧下方,幼儿更容易操作")

因为希沃白板作为电视机,它的右侧被柜子遮挡(笔记本电脑的HTML线很短,电视的HTML接口在右侧,所以柜子在电视机右侧),所以调色板应该放在左边

可是我把html放到白板上,发现没有拖拽画线,只能点点点+填色,就是只能点击,不能拖拽连线。

如果能解决划线问题,再考虑把调色板放到画布的左侧

相关推荐
"Wild dream"1 小时前
NodeJs内置的Npm
前端·npm·node.js
光影少年1 小时前
vite 8 发布,双引擎时代结束
前端·javascript·前端框架
kyriewen1110 小时前
你点的“刷新”是假刷新?前端路由的瞒天过海术
开发语言·前端·javascript·ecmascript·html5
skywalk816312 小时前
Kotti Next的tinyfrontend前端模仿Kotti 首页布局还是不太好看,感觉比Kotti差一点
前端
RopenYuan14 小时前
FastAPI -API Router的应用
前端·网络·python
走粥14 小时前
clsx和twMerge解决CSS类名冲突问题
前端·css
Purgatory00115 小时前
layui select重新渲染
前端·layui
weixin1997010801615 小时前
《中国供应商商品详情页前端性能优化实战》
前端·性能优化