欢迎加入开源鸿蒙PC社区:
atomgit仓库地址: https://atomgit.com/m0_66062719/ertongxuehualabihua




一、项目概述与设计理念
1.1 应用背景
儿童学画是启蒙教育的重要组成部分,但传统的绘画工具往往不适合年幼的孩子。蜡笔小画家旨在通过生动有趣的蜡笔风格和简单易用的界面,让孩子在愉快的氛围中学习绘画,培养创造力和想象力。
核心问题:
- 复杂工具:专业绘画软件工具太多,儿童难以使用
- 乏味界面:缺乏趣味性,难以吸引孩子注意力
- 学习曲线:需要引导和辅助,不能凭空画画
解决方案:
┌─────────────────────────────────────────────────────┐
│ │
│ 儿童友好设计 │
│ ├─ 简单直观的工具栏 │
│ ├─ 鲜艳明亮的配色方案 │
│ └─ 卡通风格模板引导 │
│ │
│ 真实蜡笔模拟 │
│ ├─ 蜡笔抖动效果模拟 │
│ ├─ 颜色叠加效果 │
│ └─ 笔触边缘柔化 │
│ │
│ 触控优化设计 │
│ ├─ 大尺寸按钮 │
│ ├─ 触摸事件支持 │
│ └─ 响应式布局 │
│ │
└─────────────────────────────────────────────────────┘
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 未来规划
- 更多模板:添加更多主题和难度等级的模板
- 动画效果:添加绘图动画回放功能
- 分享功能:支持将作品分享给亲友
- 云端同步:支持多设备作品同步
- 绘画引导:添加分步教学功能
6.3 技术价值
蜡笔小画家应用展示了如何在鸿蒙PC平台上开发面向儿童的教育应用,为开发者提供了以下参考:
- Canvas高级绘图:蜡笔效果、高清适配、性能优化
- SVG模板系统:矢量图形、动态生成、任意缩放
- 触摸交互设计:事件处理、响应式布局、儿童友好
- 数据本地存储:DataURL格式、localStorage应用
- 用户体验优化:动画效果、Toast提示、直观操作
通过本项目的实践,开发者可以快速掌握教育类应用开发的核心技术,为构建更多优秀应用奠定基础。