本文介绍了如何使用 uni-app + Vue3 + uview-plus 开发一个汉字书写练习应用。该应用支持笔画演示、书写练习、进度保存等功能,可以帮助用户学习汉字书写。
演示地址: http://demo.xiyueta.com/case/web20250222/#/pagesa/xiehaizi/index
测试账号: demo
测试密码: 123456
首先创建 uni-app 项目并集成 uview-plus:
# 创建项目
vue create -p dcloudio/uni-preset-vue hanzi-practice
# 安装依赖
npm install uview-plus
页面主要分为三个部分:
使用 Canvas 绘制汉字,通过定时器实现动画效果:
const startStrokeAnimation = () => {
let currentStroke = 0
let currentPoint = 0
const animateStroke = () => {
if (!isAnimating.value) return
if (currentStroke < currentHanziData.strokes.length) {
const median = currentHanziData.medians[currentStroke]
if (currentPoint < median.length) {
drawPartialStroke(currentStroke, currentPoint)
currentPoint++
animationTimer = setTimeout(animateStroke, animationSpeed)
} else {
// 完成当前笔画,进入下一个
drawCompleteStroke(currentStroke)
currentStroke++
currentPoint = 0
if (currentStroke < currentHanziData.strokes.length) {
animationTimer = setTimeout(animateStroke, 500)
}
}
}
}
animateStroke()
}
实现手写输入和笔画匹配:
const checkUserStroke = () => {
if (tempStroke.length < 2) return
const currentMedian = currentHanziData.medians[currentStrokeIndex.value]
const transformedUserStroke = transformUserStroke(tempStroke)
const isMatch = simpleStrokeMatch(transformedUserStroke, currentMedian)
if (isMatch) {
currentStrokeIndex.value++
if (currentStrokeIndex.value < currentHanziData.strokes.length) {
drawHanziForPractice()
} else {
showCompleteCharacter()
}
}
}
使用 localStorage 保存练习进度:
const STORAGE_KEY = 'CURRENT_LEVEL'
const saveCurrentLevel = (level) => {
try {
uni.setStorageSync(STORAGE_KEY, level)
} catch (e) {
console.error('保存关卡失败:', e)
}
}
const restoreLevel = () => {
try {
const savedLevel = uni.getStorageSync(STORAGE_KEY)
if (savedLevel) {
currentLevel.value = parseInt(savedLevel)
return true
}
} catch (e) {
console.error('恢复关卡失败:', e)
}
return false
}
问题:用户手写输入的坐标系与汉字数据的坐标系不一致。
解决方案:实现坐标转换函数:
const transformUserStroke = (userStroke) => {
const centerX = canvasWidth.value / 2
const centerY = canvasHeight.value / 2 - 35
const size = Math.min(canvasWidth.value, canvasHeight.value) * 0.9
return userStroke.map(point => ({
x: ((point.x - centerX) / (size / 1024)) + 512,
y: 1024 - (((point.y - centerY) / (size / 1024)) + 512)
}))
}
问题:动画状态管理复杂,需要处理暂停、继续等状态。
解决方案:使用 Vue3 的 ref 实现响应式状态管理:
const isAnimating = ref(false)
const toggleStrokeAnimation = () => {
if (isAnimating.value) {
clearTimeout(animationTimer)
animationTimer = null
isAnimating.value = false
drawHanzi(currentHanziData)
} else {
isAnimating.value = true
startStrokeAnimation()
}
}
问题:不同设备上画布大小和按钮布局需要适配。
解决方案:使用 rpx 单位和 flex 布局:
.hanzi-canvas {
width: calc(100% - 60rpx);
height: 60vh;
margin: 30rpx auto;
}
.bottom-section {
width: calc(100% - 60rpx);
margin: 0 auto;
.control-buttons {
display: flex;
justify-content: space-between;
gap: 30rpx;
}
}
组件化设计:将功能模块拆分为独立组件,提高代码复用性
状态管理:使用 Vue3 的 Composition API 管理状态
性能优化:
用户体验:
本项目展示了如何使用现代前端技术栈开发一个实用的教育类应用。通过合理的架构设计和性能优化,实现了流畅的用户体验。项目中的许多技术点和解决方案都可以应用到其他同类项目中。
如果本文章对您有所帮助,欢迎交流和探讨技术问题。
QQ: 313801120
更多文章: www.xiyueta.com
希望能一起成长,共同探索更多开发技巧!