我的第一个小程序【涂图了】 —— canvas画布

功能

先列出目前小程序已完成了功能:

  • 笔记绘制;
  • 颜色和宽度;
  • 背景;
  • 撤销;
  • 恢复撤销;
  • 清空;
  • 保存本地;
  • 笔记播放;
  • 分享/口令分享;

微信搜索【涂图了】即可体验,下面简单介绍几个比较重要的功能实现。

画布的实现

由于一开始使用了uni + vite + vue3来进行小程序的开发,遇到的第一个坑就是当前版本的uni不支持canvas响应touch事件,从而直接导致无法进行正常的绘制操作。于是就给uni-app提了一个issue,为了不影响开发进度,于是先自己搞了一个解决方案:

其实也比较简单,就是在canvas上面覆盖了一层view,将touch事件绑定在view上。

然后就是创建上下文,由于目前uni没有跟上微信官方的api,所以就直接使用了微信官方提供 的api来获取上下文:

export default function usePaint(selector: string) {
  const paint = ref<Paint>();

  const initCanvas = (canvas: any) => {
    const { windowWidth, windowHeight, pixelRatio } = uni.getSystemInfoSync();
    /**
     * 解决绘图路径锯齿问题
     * 1. 尺寸取物理像素 windowWidth * pixelRatio
     * 2. 画布缩放像素比 ctx.scale
     */
    canvas.width = windowWidth * pixelRatio;
    canvas.height = windowHeight * pixelRatio;

    const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
    ctx.translate(windowWidth * pixelRatio / 2, windowHeight * pixelRatio / 2);

    // #ifndef MP-TOUTIAO
    ctx.scale(pixelRatio, pixelRatio);
    // #endif

    paint.value = new Paint(ctx);
  };

  onReady(() => {
    // #ifdef MP
    uni
      .createSelectorQuery()
      .select('#' + selector)
      // #ifdef MP-TOUTIAO
      // @ts-ignore
      .node()
      .exec(([{ node: canvas }]) => {
        initCanvas(canvas);
      })
      // #endif
      // #ifndef MP-TOUTIAO
      .fields(
        {
          // @ts-ignore
          node: true,
          size: true,
        },
        ({ node: canvas }: any) => {
          initCanvas(canvas);
        }
      ).exec();
      // #endif
    // #endif
    // #ifndef MP
    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);
    // #endif
  });

  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画布上,但这个操作相对于更换背景应该是很少的。

<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的调用,没啥可说的,直接上代码:

export const useGenerateImage = async (selector: string): Promise<string> => {
  return new Promise((resolve, reject) => {
    // #ifdef MP-WEIXIN
    uni
    .createSelectorQuery()
    .select('#' + selector)
    .fields(
      {
        // @ts-ignore
        node: true,
        size: true,
      },
      ({ node: canvas }: any) => {
        uni.canvasToTempFilePath({
          // @ts-ignore
          canvas,
          success: ({ tempFilePath }) => {
            resolve(tempFilePath);
          },
          fail: reject,
        });
      }
    )
    .exec();
    // #endif
    // #ifndef MP-WEIXIN
    uni.canvasToTempFilePath({
      canvasId: selector,
      success: ({ tempFilePath }) => {
        resolve(tempFilePath);
      },
      fail: reject,
    });
    // #endif
  });
}

然后通过uni.saveImageToPhotosAlbum将生成的图片链接保存在本地,如果是h5端,可使用以下方法:

export function download(url: string, name = String(Date.now())) {
  const a = document.createElement('a');
  a.download = name;
  a.href = url;
  a.click();
}

未完待续

篇幅有限,先分享到这里,撤销播放功能比较复杂,后面再详细说。欢迎大家前来体验我的小程序【涂图了】,提出的你的宝贵意见

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容