本文最后更新于 2024-11-11T03:04:04+00:00
先放出小程序码:
功能
先列出目前小程序已完成了功能:
- 笔记绘制;
- 颜色和宽度;
- 背景;
- 撤销;
- 恢复撤销;
- 清空;
- 保存本地;
- 笔记播放;
- 分享/口令分享;
下面简单介绍几个重要的功能实现
画布的实现
由于一开始使用了uni + vite + vue3
来进行小程序的开发,遇到的第一个坑就是当前版本的uni不支持canvas响应touch事件,从而直接导致无法进行正常的绘制操作。于是就给uni-app提了一个issue,为了不影响开发进度,于是先自己搞了一个解决方案:
其实也比较简单,就是在canvas
上面覆盖了一层view
,将touch
事件绑定在view
上。
然后就是创建上下文,由于目前uni
没有跟上微信官方的api,所以就直接使用了微信官方提供 的api来获取上下文:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| export default function usePaint(selector: string) { const paint = ref<Paint>();
const initCanvas = (canvas: any) => { const { windowWidth, windowHeight, pixelRatio } = uni.getSystemInfoSync();
canvas.width = windowWidth * pixelRatio; canvas.height = windowHeight * pixelRatio;
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; ctx.translate(windowWidth * pixelRatio / 2, windowHeight * pixelRatio / 2);
ctx.scale(pixelRatio, pixelRatio);
paint.value = new Paint(ctx); };
onReady(() => { uni .createSelectorQuery() .select('#' + selector) .node() .exec(([{ node: canvas }]) => { initCanvas(canvas); }) .fields( { node: true, size: true, }, ({ node: canvas }: any) => { initCanvas(canvas); } ).exec(); const { windowWidth, windowHeight } = uni.getSystemInfoSync(); const ctx = uni.createCanvasContext(selector, getCurrentInstance()); ctx.translate(windowWidth / 2, windowHeight / 2); paint.value = new Paint(ctx as unknown as CanvasRenderingContext2D); });
return paint; }
|
其中比较关键的一个点就是ctx.scale(pixelRatio, pixelRatio);
,我们通过css样式设置的大小,只是canvas展示的大小,事件绘图时画布的大小是通过canvas.width = windowWidth * pixelRatio;
来确定的,这是设置是画布大小是设备屏幕的物理像素大小,为了保持视觉的一致性,所以就需要.scale
方法进行缩放。
对于canvas
的api,我就不介绍了,与Web端的canvas
完全保持一致。
背景的实现
一开始我以为背景很简单,其实就是在画布上绘制一个宽高100%的矩形,然后填充颜色就可以了,但是实际上是行不通的,就比如这样一个场景:
当目前画布上已经绘制了很多笔记了,如果直接矩形填充,就会把当前的画布上的笔记全部覆盖了。如果绘制矩形之前,先将当前绘制的笔记保存起来,然后等背景绘制完成之后再将保存的笔记重新绘制在画布上,是否可行呢?答案当然的可行的,但不是最优的。
我们只需要在设置背景之前,先通过ctx.getImageData
将当前画布保存起来,然后设置玩背景之后,再同各国ctx.putImageData
将画布还原就可以了。为什么说不是最优的方案呢?因为设置背景是一个用户的自由操作,可能会存在反复更换的情况,这是不可预料的,但绝对是可行的。我的解决方案是在canvas
的底部搞了一个view
,然后设置背景的时候只需要改变底部view
的背景颜色就行了,不需要对画布进行任何操作,画布永远都是透明的。但也存在一个小问题,就是当用户将画布保存在本地的时候,还是需要将背景绘制到canvas画布上,但这个操作相对于更换背景应该是很少的。
1 2 3 4 5 6 7 8 9 10 11 12 13
| <view class="canvas canvas-bg" :style="{ backgroundColor: state.backgroundColor }"></view> <canvas id="drawCanvas" type="2d" class="canvas" ></canvas> <view class="canvas canvas-cover" @touchstart.stop="handleTouchStart" @touchmove.stop="handleTouchMove" @touchend.stop="handleTouchEnd" @touchcancel.stop="handleTouchEnd" ></view>
|
将画布保存在本地
这个功能其实就是api的调用,没啥可说的,直接上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| export const useGenerateImage = async (selector: string): Promise<string> => { return new Promise((resolve, reject) => { uni .createSelectorQuery() .select('#' + selector) .fields( { node: true, size: true, }, ({ node: canvas }: any) => { uni.canvasToTempFilePath({ canvas, success: ({ tempFilePath }) => { resolve(tempFilePath); }, fail: reject, }); } ) .exec(); uni.canvasToTempFilePath({ canvasId: selector, success: ({ tempFilePath }) => { resolve(tempFilePath); }, fail: reject, }); }); }
|
然后通过uni.saveImageToPhotosAlbum
将生成的图片链接保存在本地,如果是h5端,可使用以下方法:
1 2 3 4 5 6
| export function download(url: string, name = String(Date.now())) { const a = document.createElement('a'); a.download = name; a.href = url; a.click(); }
|
未完待续
篇幅有限,先分享到这里,撤销和播放请参考下一篇文章。
如果觉得不错,可以关注我的公众号【末日码农】,我会将开发中遇到的实际问题和一些好的技术知识分享给大家。