蜡笔小画家鸿蒙PC用Electron框架 - 儿童学画蜡笔画技术实现详解

欢迎加入开源鸿蒙PC社区:

https://harmonypc.csdn.net/

atomgit仓库地址: https://atomgit.com/m0_66062719/ertongxuehualabihua

一、项目概述与设计理念

1.1 应用背景

儿童学画是启蒙教育的重要组成部分,但传统的绘画工具往往不适合年幼的孩子。蜡笔小画家旨在通过生动有趣的蜡笔风格和简单易用的界面,让孩子在愉快的氛围中学习绘画,培养创造力和想象力。

核心问题

  1. 复杂工具:专业绘画软件工具太多,儿童难以使用
  2. 乏味界面:缺乏趣味性,难以吸引孩子注意力
  3. 学习曲线:需要引导和辅助,不能凭空画画

解决方案

复制代码
┌─────────────────────────────────────────────────────┐
│                                                     │
│  儿童友好设计                                       │
│  ├─ 简单直观的工具栏                                │
│  ├─ 鲜艳明亮的配色方案                              │
│  └─ 卡通风格模板引导                                │
│                                                     │
│  真实蜡笔模拟                                       │
│  ├─ 蜡笔抖动效果模拟                                │
│  ├─ 颜色叠加效果                                    │
│  └─ 笔触边缘柔化                                    │
│                                                     │
│  触控优化设计                                       │
│  ├─ 大尺寸按钮                                      │
│  ├─ 触摸事件支持                                    │
│  └─ 响应式布局                                      │
│                                                     │
└─────────────────────────────────────────────────────┘

1.2 技术架构选型

技术方案 优势 适用场景
HTML5 Canvas 高性能绘图,原生支持 实时绘图应用
SVG图形 矢量缩放,任意尺寸 参考模板显示
CSS3动画 流畅过渡,用户友好 UI交互效果
localStorage 本地存储,数据持久化 作品保存

1.3 功能模块划分

复制代码
┌─────────────────────────────────────────────────────┐
│                    功能模块架构                      │
├─────────────────────────────────────────────────────┤
│  🎨 绘画工具 |  📋 模板参考 |  💾 作品管理       │
├─────────────────────────────────────────────────────┤
│  🌈 颜色选择 |  📏 粗细调节 |  🎉 作品画廊       │
└─────────────────────────────────────────────────────┘

二、核心代码实现详解

2.1 Canvas初始化与高清适配

为了支持高清显示,我们采用2倍缩放的方式:

javascript 复制代码
// 初始化画布
function initCanvas() {
    canvas = document.getElementById('drawingCanvas');
    ctx = canvas.getContext('2d');
    
    // 设置画布尺寸
    resizeCanvas();
    window.addEventListener('resize', resizeCanvas);
    
    // 绑定事件
    canvas.addEventListener('mousedown', startDrawing);
    canvas.addEventListener('mousemove', draw);
    canvas.addEventListener('mouseup', stopDrawing);
    canvas.addEventListener('mouseout', stopDrawing);
    
    // 触摸事件支持
    canvas.addEventListener('touchstart', handleTouchStart);
    canvas.addEventListener('touchmove', handleTouchMove);
    canvas.addEventListener('touchend', stopDrawing);
}

// 调整画布尺寸
function resizeCanvas() {
    const container = canvas.parentElement;
    const rect = container.getBoundingClientRect();
    
    // 设置画布尺寸为2倍大小(高清支持)
    canvas.width = rect.width * 2;
    canvas.height = rect.height * 2;
    
    // 缩放上下文以支持高清显示
    ctx.scale(2, 2);
    
    // 设置默认背景
    ctx.fillStyle = 'white';
    ctx.fillRect(0, 0, rect.width, rect.height);
    
    // 加载保存的画作
    loadSavedDrawings();
}

高清适配原理

复制代码
屏幕像素密度 (DPI) × 2 = 画布实际尺寸

- 普通屏幕:1000px → 画布 2000px,缩放 2 倍显示
- Retina屏幕:同样方式,避免模糊

CSS控制显示尺寸:
#drawingCanvas {
    width: 100%;
    height: 500px;
}

JS绘制实际尺寸:
canvas.width = 1000 * 2;
canvas.height = 500 * 2;
ctx.scale(2, 2);

2.2 蜡笔抖动效果实现

这是本应用的核心特色,模拟真实蜡笔的质感:

javascript 复制代码
// 绘制中
function draw(e) {
    if (!isDrawing) return;
    
    const rect = canvas.getBoundingClientRect();
    const currentX = e.clientX - rect.left;
    const currentY = e.clientY - rect.top;
    
    ctx.beginPath();
    ctx.moveTo(lastX, lastY);
    
    switch (currentTool) {
        case 'pen':
            // 蜡笔效果 - 添加一些抖动
            const midX = (lastX + currentX) / 2;
            const midY = (lastY + currentY) / 2;
            ctx.quadraticCurveTo(lastX, lastY, midX, midY);
            break;
        // ... 其他工具
    }
    
    // 设置画笔样式
    ctx.strokeStyle = currentColor;
    ctx.lineWidth = currentSize;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    
    // 添加蜡笔效果 - 轻微抖动
    if (currentTool === 'pen') {
        ctx.stroke();
        // 添加第二层轻微偏移
        ctx.strokeStyle = adjustColor(currentColor, 20);
        ctx.lineWidth = currentSize * 0.8;
        ctx.beginPath();
        ctx.moveTo(lastX + (Math.random() - 0.5) * 2, lastY + (Math.random() - 0.5) * 2);
        ctx.lineTo(currentX + (Math.random() - 0.5) * 2, currentY + (Math.random() - 0.5) * 2);
    }
    
    ctx.stroke();
    
    lastX = currentX;
    lastY = currentY;
}

// 调整颜色亮度
function adjustColor(color, amount) {
    const hex = color.replace('#', '');
    const r = Math.min(255, Math.max(0, parseInt(hex.substring(0, 2), 16) + amount));
    const g = Math.min(255, Math.max(0, parseInt(hex.substring(2, 4), 16) + amount));
    const b = Math.min(255, Math.max(0, parseInt(hex.substring(4, 6), 16) + amount));
    return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
}

蜡笔效果原理

复制代码
三层叠加实现:

第1层:主线条 - 正常颜色,完整粗细
第2层:偏移线 - 稍亮颜色,80%粗细,±1像素随机偏移
第3层:随机抖动 - 每个点都有轻微的上下左右偏移

视觉效果:
- 线条边缘不那么生硬
- 有颗粒感和纹理
- 模拟蜡笔在纸上的质感

2.3 触摸事件支持

为了支持平板电脑和触摸设备,需要完整的触摸事件处理:

javascript 复制代码
// 触摸事件处理
function handleTouchStart(e) {
    e.preventDefault();
    const touch = e.touches[0];
    isDrawing = true;
    const rect = canvas.getBoundingClientRect();
    lastX = touch.clientX - rect.left;
    lastY = touch.clientY - rect.top;
    
    if (currentTool === 'pen') {
        ctx.beginPath();
        ctx.arc(lastX, lastY, currentSize / 2, 0, Math.PI * 2);
        ctx.fillStyle = currentColor;
        ctx.fill();
    }
}

function handleTouchMove(e) {
    e.preventDefault();
    if (!isDrawing) return;
    
    const touch = e.touches[0];
    const rect = canvas.getBoundingClientRect();
    const currentX = touch.clientX - rect.left;
    const currentY = touch.clientY - rect.top;
    
    ctx.beginPath();
    ctx.moveTo(lastX, lastY);
    ctx.lineTo(currentX, currentY);
    ctx.strokeStyle = currentTool === 'eraser' ? 'white' : currentColor;
    ctx.lineWidth = currentTool === 'eraser' ? currentSize * 2 : currentSize;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.stroke();
    
    lastX = currentX;
    lastY = currentY;
}

触摸事件要点

javascript 复制代码
// 1. 防止触摸时页面滚动
e.preventDefault();

// 2. 获取触摸点坐标
const touch = e.touches[0];

// 3. 触摸位置计算(相对于画布)
const rect = canvas.getBoundingClientRect();
const x = touch.clientX - rect.left;
const y = touch.clientY - rect.top;

// 4. 触摸属性设置
canvas {
    touch-action: none; // 禁止默认触摸行为
}

2.4 SVG模板加载与显示

使用SVG作为参考模板,可以任意缩放不失真:

javascript 复制代码
// 应用模板
function applyTemplate(templateId) {
    const template = drawingTemplates.find(t => t.id === templateId);
    if (template) {
        // 显示模板覆盖层
        const overlay = document.getElementById('templateOverlay');
        const img = document.getElementById('templateImage');
        
        // 将SVG转换为Data URL
        const svgBlob = new Blob([template.svg], { type: 'image/svg+xml' });
        const url = URL.createObjectURL(svgBlob);
        img.src = url;
        
        overlay.classList.add('active');
        closeTemplates();
        showToast(`已加载 "${template.name}" 模板`);
    }
}

SVG模板结构

html 复制代码
<!-- 小太阳模板示例 -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
    <!-- 外圈 -->
    <circle cx="100" cy="100" r="40" fill="none" stroke="#feca57" stroke-width="3"/>
    <!-- 内圈 -->
    <circle cx="100" cy="100" r="25" fill="none" stroke="#feca57" stroke-width="2"/>
    <!-- 光线 -->
    <line x1="100" y1="10" x2="100" y2="30" stroke="#feca57" stroke-width="3"/>
    <line x1="100" y1="170" x2="100" y2="190" stroke="#feca57" stroke-width="3"/>
    <line x1="10" y1="100" x2="30" y2="100" stroke="#feca57" stroke-width="3"/>
    <line x1="170" y1="100" x2="190" y2="100" stroke="#feca57" stroke-width="3"/>
</svg>

SVG转Blob原理

javascript 复制代码
// SVG字符串 → Blob对象 → URL对象
const svgString = '<svg>...</svg>';
const blob = new Blob([svgString], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
img.src = url;

// 注意:使用完后释放URL
URL.revokeObjectURL(url);

三、数据存储与管理

3.1 作品数据结构

javascript 复制代码
// 作品数据结构
const drawingData = {
    id: 1622771750000,              // 时间戳ID
    name: '我的小太阳',             // 作品名称
    image: 'data:image/png;base64,...', // 图片DataURL
    date: '2021-06-03T12:35:50.000Z'  // 保存时间
};

3.2 作品保存与加载

javascript 复制代码
// 保存画作
function confirmSave() {
    const name = document.getElementById('drawingName').value.trim();
    
    if (!name) {
        showToast('请输入画作名称');
        return;
    }
    
    const drawingData = {
        id: Date.now(),
        name: name,
        image: canvas.toDataURL(),
        date: new Date().toISOString()
    };
    
    savedDrawings.push(drawingData);
    localStorage.setItem('kids_drawings', JSON.stringify(savedDrawings));
    
    closeModal('saveModal');
    loadSavedDrawings();
    showToast('画作保存成功!');
}

// 加载保存的画作
function loadSavedDrawings() {
    savedDrawings = JSON.parse(localStorage.getItem('kids_drawings') || '[]');
    renderGallery();
}

DataURL格式说明

javascript 复制代码
// canvas.toDataURL() 返回格式
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA...'

// 组成部分
'data:' +                    // 协议
'image/png' +                // MIME类型
';base64,' +                 // 编码方式
'iVBORw0KGgoAAAANSUhEUg...' // 实际数据

// 优点:可以直接赋值给 <img src>
// 缺点:比原始二进制数据大33%左右

3.3 作品画廊渲染

javascript 复制代码
// 渲染作品画廊
function renderGallery() {
    const container = document.getElementById('galleryGrid');
    
    if (savedDrawings.length === 0) {
        container.innerHTML = '<div style="text-align:center;padding:40px;color:#94a3b8;">还没有保存的画作</div>';
        return;
    }
    
    container.innerHTML = savedDrawings.map(drawing => {
        const date = new Date(drawing.date);
        const dateStr = `${date.getMonth() + 1}/${date.getDate()}`;
        
        return `
            <div class="gallery-item">
                <img src="${drawing.image}" alt="${drawing.name}" onclick="viewDrawing(${drawing.id})">
                <div class="gallery-item-info">
                    <div class="gallery-item-name">${drawing.name}</div>
                    <div class="gallery-item-date">${dateStr}</div>
                    <div class="gallery-item-actions">
                        <button class="gallery-item-delete" onclick="deleteDrawing(${drawing.id})">删除</button>
                    </div>
                </div>
            </div>
        `;
    }).join('');
}

// 查看画作
function viewDrawing(id) {
    const drawing = savedDrawings.find(d => d.id === id);
    if (drawing) {
        // 在画布上显示
        const rect = canvas.parentElement.getBoundingClientRect();
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, rect.width, rect.height);
        
        const img = new Image();
        img.onload = () => {
            ctx.drawImage(img, 0, 0, rect.width, rect.height);
        };
        img.src = drawing.image;
        
        showToast(`已加载 "${drawing.name}"`);
    }
}

四、UI设计与交互体验

4.1 色彩方案设计

儿童友好的配色方案:

css 复制代码
:root {
    /* 主色调 - 鲜艳温暖 */
    --primary: #ff6b6b;        /* 红色 - 醒目 */
    --secondary: #feca57;      /* 黄色 - 活泼 */
    
    /* 背景色 - 柔和 */
    --bg-primary: #fff8e7;     /* 暖米色 */
    --bg-secondary: #ffffff;   /* 纯白 */
    
    /* 文字色 - 清晰 */
    --text-primary: #2c3e50;   /* 深灰 */
    --text-secondary: #94a3b8; /* 浅灰 */
    
    /* 辅助色 - 丰富 */
    --cyan: #48dbfb;
    --green: #1dd1a1;
    --purple: #5f27cd;
    --pink: #ff9ff3;
    --blue: #54a0ff;
    --brown: #8b4513;
}

儿童色彩心理学

复制代码
红色 (#ff6b6b) - 兴奋、活力、吸引注意力
黄色 (#feca57) - 愉快、温暖、激发创造力
蓝色 (#54a0ff) - 平静、信任、稳定感
绿色 (#1dd1a1) - 自然、成长、安全
紫色 (#5f27cd) - 神秘、想象、创造力

4.2 工具栏布局设计

html 复制代码
<!-- 工具栏结构 -->
<nav class="toolbar">
    <!-- 第一组:工具选择 -->
    <div class="tool-group">
        <span class="tool-label">画笔</span>
        <button class="tool-btn active" data-tool="pen">✏️</button>
        <button class="tool-btn" data-tool="eraser">🧹</button>
        <button class="tool-btn" data-tool="line">📏</button>
        <button class="tool-btn" data-tool="circle">⭕</button>
        <button class="tool-btn" data-tool="rect">⬜</button>
    </div>
    
    <div class="divider"></div>
    
    <!-- 第二组:颜色选择 -->
    <div class="tool-group">
        <span class="tool-label">颜色</span>
        <div class="color-picker">
            <button class="color-btn active" data-color="#ff6b6b" 
                    style="background: #ff6b6b"></button>
            <!-- 更多颜色按钮... -->
        </div>
    </div>
    
    <!-- 其他组... -->
</nav>

4.3 响应式设计实现

css 复制代码
/* 桌面端 */
.toolbar {
    gap: 15px;
}

.btn-text {
    display: inline;
}

/* 平板端 */
@media (max-width: 1024px) {
    .toolbar {
        gap: 10px;
    }
}

/* 手机端 */
@media (max-width: 768px) {
    .toolbar {
        gap: 8px;
        flex-wrap: wrap;
    }
    
    .btn-text {
        display: none;  /* 只显示图标 */
    }
    
    .color-btn {
        width: 30px;
        height: 30px;
    }
    
    .app-header {
        flex-direction: column;
        text-align: center;
    }
}

五、技术亮点与创新

5.1 蜡笔效果算法

javascript 复制代码
// 蜡笔效果核心算法
function drawWithCrayon(fromX, fromY, toX, toY) {
    // 1. 主线条
    ctx.strokeStyle = currentColor;
    ctx.lineWidth = currentSize;
    ctx.beginPath();
    ctx.moveTo(fromX, fromY);
    ctx.lineTo(toX, toY);
    ctx.stroke();
    
    // 2. 第二层线条(稍亮,稍细)
    ctx.strokeStyle = adjustColor(currentColor, 20);
    ctx.lineWidth = currentSize * 0.8;
    ctx.beginPath();
    ctx.moveTo(fromX + (Math.random() - 0.5) * 1.5, 
               fromY + (Math.random() - 0.5) * 1.5);
    ctx.lineTo(toX + (Math.random() - 0.5) * 1.5, 
               toY + (Math.random() - 0.5) * 1.5);
    ctx.stroke();
    
    // 3. 第三层线条(稍暗,更细)
    ctx.strokeStyle = adjustColor(currentColor, -15);
    ctx.lineWidth = currentSize * 0.6;
    ctx.beginPath();
    ctx.moveTo(fromX + (Math.random() - 0.5) * 1, 
               fromY + (Math.random() - 0.5) * 1);
    ctx.lineTo(toX + (Math.random() - 0.5) * 1, 
               toY + (Math.random() - 0.5) * 1);
    ctx.stroke();
}

算法效果

复制代码
三层叠加:
- 底层:主色,完整粗细
- 中层:亮色,80%粗细,±1.5px随机
- 顶层:暗色,60%粗细,±1px随机

视觉上形成:
- 颗粒质感
- 不完美的边缘
- 类似蜡笔在纸上的摩擦痕迹

5.2 SVG模板动态生成

javascript 复制代码
// 模板SVG数据
const drawingTemplates = [
    {
        id: 1,
        name: '小太阳',
        svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
            <circle cx="100" cy="100" r="40" fill="none" stroke="#feca57" stroke-width="3"/>
            <!-- 更多SVG内容 -->
        </svg>`
    },
    // 更多模板...
];

SVG优势

复制代码
✅ 矢量图形 - 任意缩放不会模糊
✅ 轻量级 - 文本格式,体积小
✅ 可编辑 - 用文本编辑器就能修改
✅ DOM操作 - 可以用JS动态修改
✅ 兼容性好 - 现代浏览器都支持

5.3 双端事件支持

同时支持鼠标和触摸事件:

javascript 复制代码
// 鼠标事件
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);

// 触摸事件
canvas.addEventListener('touchstart', handleTouchStart);
canvas.addEventListener('touchmove', handleTouchMove);
canvas.addEventListener('touchend', stopDrawing);

// 统一处理逻辑
function handleDrawEvent(x, y) {
    // 处理坐标
    // 绘制逻辑
}

六、总结与展望

6.1 项目成果

功能模块 状态 核心特性
画笔工具 蜡笔效果、抖动算法
工具选择 5种工具(笔、橡皮、线、圆、方)
颜色系统 10种预设色、切换动画
粗细调节 4档粗细、直观预览
模板参考 10个SVG模板、半透明显示
作品保存 本地存储、画廊展示
触摸支持 完整触摸事件、优化触控

6.2 未来规划

  1. 更多模板:添加更多主题和难度等级的模板
  2. 动画效果:添加绘图动画回放功能
  3. 分享功能:支持将作品分享给亲友
  4. 云端同步:支持多设备作品同步
  5. 绘画引导:添加分步教学功能

6.3 技术价值

蜡笔小画家应用展示了如何在鸿蒙PC平台上开发面向儿童的教育应用,为开发者提供了以下参考:

  • Canvas高级绘图:蜡笔效果、高清适配、性能优化
  • SVG模板系统:矢量图形、动态生成、任意缩放
  • 触摸交互设计:事件处理、响应式布局、儿童友好
  • 数据本地存储:DataURL格式、localStorage应用
  • 用户体验优化:动画效果、Toast提示、直观操作

通过本项目的实践,开发者可以快速掌握教育类应用开发的核心技术,为构建更多优秀应用奠定基础。

相关推荐
天蓝色的鱼鱼1 小时前
别只拿 Playwright 写测试,这三个野路子用法才是真香
前端
SoaringHeart1 小时前
Flutter进阶|源码修改:DecorationImage 添加网络图片占位图
前端·flutter
小新1101 小时前
vue 实战项目 天气查询
前端·javascript·vue.js
7yue1 小时前
用 TypScript 学习 Claude Code
前端·typescript·claude
Rain5091 小时前
实战:搭建 AI Code Review 自动化流水线
前端·人工智能·git·ci/cd·自动化·ai编程·代码复审
竹林8181 小时前
用 wagmi v2 + viem 监听合约事件踩坑实录:从轮询到实时推送,我终于搞懂了
javascript
Nian_Baikal1 小时前
从零搭建离线地图服务:Nginx + Cesium/Leaflet 实战指南
前端
问心无愧05131 小时前
ctf show web入门99
android·前端·笔记