在移动端使用 UniApp 进行逐字手写的功能。用户可以在一个 inputCanvas
上书写单个字,然后在特定时间后将这个字添加到 outputCanvas
上,形成一个逐字的手写效果。用户还可以保存整幅图像或者撤销上一个添加的字。
-
初始化 Canvas:
- 使用
uni.createCanvasContext
创建画布上下文,设置笔触样式和线条属性。
- 使用
-
触摸事件处理:
handleTouchStart
:捕获触摸开始事件,初始化绘图状态。handleTouchMove
:捕获触摸移动事件,实时绘制路径。handleTouchEnd
:捕获触摸结束事件,启动定时器准备添加字。
-
添加字符:
addChar
方法将inputCanvas
的内容绘制到outputCanvas
上,同时保存字符的路径。
-
撤销功能:
undoChar
方法删除上一个字符,并重新绘制outputCanvas
。
-
保存和上传图像:
saveImage
方法将outputCanvas
的内容保存为图片,并调用upload
方法上传。
完整代码:
<template><view class="container"><view class="tip"><view class="">请您在区域内逐字手写以下文字,全部写完后点击保存!</view><u-alert style="margin-bottom: 20upx;" :description="ruleForm.sqcn" type = "primary" ></u-alert></view><view class="canvas-container"><canvas canvas-id="inputCanvas" class="input-canvas" @touchstart="handleTouchStart"@touchmove="handleTouchMove" @touchend="handleTouchEnd"></canvas></view><view class="buttons"><u-button text="撤销上一个字" size="normal" type="error" @click="undoChar"></u-button><u-button text="保存" size="normal" type="primary" @click="saveImage"></u-button></view><canvas :style="{ height: outputHeight }" canvas-id="outputCanvas" class="output-canvas"></canvas></view>
</template><script>import fileService from "@/api/file/fileService.js";import knsService from "@/api/kns/knsService"export default {data() {return {isDrawing: false,startX: 0,startY: 0,strokes: [],canvasWidth: 300,canvasHeight: 300,charObjects: [],timer: null,delay: 1000, // 1秒延迟fj: '',outputHeight: '50px',label: '',ruleForm: {}};},mounted() {this.getData()this.initCanvas('inputCanvas');this.initCanvas('outputCanvas');},onLoad(option) {this.label = option.label;},methods: {// 获取承诺async getData() {const res = await knsService.getSettingData();this.ruleForm = res[0];},initCanvas(canvasId) {const context = uni.createCanvasContext(canvasId, this);context.setStrokeStyle('#000');context.setLineWidth(4);context.setLineCap('round');context.setLineJoin('round');context.draw();},handleTouchStart(e) {e.preventDefault(); // 阻止默认滚动行为if (this.timer) {clearTimeout(this.timer);this.timer = null;}const touch = e.touches[0];this.isDrawing = true;this.startX = touch.x;this.startY = touch.y;this.strokes.push({x: touch.x,y: touch.y});},handleTouchMove(e) {e.preventDefault(); // 阻止默认滚动行为if (!this.isDrawing) return;const touch = e.touches[0];const context = uni.createCanvasContext('inputCanvas', this);context.moveTo(this.startX, this.startY);context.lineTo(touch.x, touch.y);context.stroke();context.draw(true);this.startX = touch.x;this.startY = touch.y;this.strokes.push({x: touch.x,y: touch.y});},handleTouchEnd(e) {e.preventDefault(); // 阻止默认滚动行为this.isDrawing = false;this.timer = setTimeout(this.addChar, this.delay);},addChar() {const inputContext = uni.createCanvasContext('inputCanvas', this);uni.canvasToTempFilePath({canvasId: 'inputCanvas',success: (res) => {// 保存这个字符的路径this.charObjects.push(res.tempFilePath);// 清空 inputCanvas 上的内容inputContext.clearRect(0, 0, this.canvasWidth, this.canvasHeight);inputContext.draw();this.redrawOutputCanvas()},});},undoChar() {if (this.charObjects.length > 0) {this.charObjects.pop();this.redrawOutputCanvas();if (this.charObjects.length === 0) {this.outputHeight = 50; // 如果字符对象为空,则将输出画布高度设置为 50}}},redrawOutputCanvas() {const context = uni.createCanvasContext('outputCanvas', this);const charSize = 50; // 调整字符大小const charSpacing = 48; // 调整字符间距const maxCharsPerRow = Math.floor(this.canvasWidth / charSpacing); // 每行最大字符数// 动态设置高度const numRows = Math.ceil(this.charObjects.length / maxCharsPerRow); // 计算行数this.outputHeight = `${numRows * charSize}px`; // 动态计算输出画布的高度console.log(this.outputHeight, this.charObjects.length, 'outputHeight');// 清除画布并设置高度context.clearRect(0, 0, this.canvasWidth, this.outputHeight);// 绘制字符this.charObjects.forEach((charPath, index) => {const rowIndex = Math.floor(index / maxCharsPerRow); // 当前字符的行索引const colIndex = index % maxCharsPerRow; // 当前字符的列索引context.drawImage(charPath, 10 + colIndex * charSpacing, 10 + rowIndex * charSpacing, charSize,charSize);});this.$nextTick(() => {// 一次性绘制所有字符context.draw();})},saveImage() {if (this.charObjects.length === 0) {uni.showToast({icon: "error",title: '请手写文字!'})return false;}uni.canvasToTempFilePath({canvasId: 'outputCanvas',success: (res) => {// 保存图片console.log(res.tempFilePath, 'res.tempFilePath');this.upload(res.tempFilePath);},});},upload(img) {fileService.upload(img).then((res) => {let pages = getCurrentPages()let currPage = pages[pages.length - 1]; //当前页面let prevPage = pages[pages.length - 2]; //上一个页面//修改前一页数据if (prevPage.inputForm) {prevPage.inputForm[this.label] = res} console.log(res, 'res');//返回上一页uni.navigateBack({delta: 1,})});},},};
</script><style scoped lang="scss">.container {display: flex;flex-direction: column;align-items: center;margin-top: 40upx;.canvas-container {position: relative;width: 600upx;height: 600upx;.input-canvas {position: absolute;top: 0;left: 0;width: 100%;height: 100%;border-radius: 10upx;border: 4upx dashed #dddee1;touch-action: none;/* 禁止默认触摸动作 */}}.output-canvas {width: 600upx;/* 设置高度为原来的一半 */border: 2upx solid #dddee1;margin-top: 40upx;}.buttons {display: flex;justify-content: space-around;width: 100%;padding: 0upx 50upx;}button {margin: 20upx;}.tip {view:nth-child(1){color: #FF6F77;font-size: 24upx;margin-bottom: 20upx;}}}
</style>