写一个舔狗日记

2,139 阅读3分钟

不要让自己的底限成为你的上限

思路

  1. 熊猫头表情包固定的宽高,储存熊猫头高度
  2. 次要内容,标题、时间、天气,此处也需要记录高度以及间距(这些内容可以控制显示)
  3. 随机的文案
  4. 可以生成图片,canvas直接渲染会变成一行,所以需要根据每行多少字做切割

render node节点

image.png 处理页面文案显示,舔狗文案是从网上扒的,包含了****高亮内容。通过切割**形式高亮文本,小程序无法直接渲染wxml,需要转换成array形式。

  getNodeText(text) {
    const soulTextArray = text.split('**');
    let nodes = []
    for (const key of Object.keys(soulTextArray)) {
      nodes.push({
        name: 'view',
        attrs: {
          class: key % 2 === 0 ? 'text' : 'lineheight'
        },
        children: [{
          type: 'text',
          text: soulTextArray[key]
        }]
      })
    }
    return nodes;
  }

动态处理canvas高度

  setCanvasHeight() {
    let canvasDrawHeightOffset = 20;
    const { title, time, weather } = this.data.checkboxValue;
    if (title) {
      canvasDrawHeightOffset = canvasDrawHeightOffset + 4;
    } else {
      canvasDrawHeightOffset = 0;
    }
    if (weather || time) {
      canvasDrawHeightOffset = canvasDrawHeightOffset + 40;
    }
    // 这里处理空白区域的
    if (!title && !time && !weather) {
      canvasDrawHeightOffset = 20;
    }
    this.setData({
      // 按照每行高度 18 * 每句句子 + 图片高度 + canvas渲染文案差距的高度
      'canvasInfo.height': 18 * this.data.textArr.length + this.data.imgHeight + canvasDrawHeightOffset
    });
  }

生成canvas内容

需要注意的是,我用的是最新的2.17.0的库,使用的也是小程序最新的api<canvas type="2d" id="myCanvas"></canvas>,之前的api被官方废弃了。

wxml

通过动态高度,这样就能动态适应文案内容了。

<canvas type="2d" id="myCanvas" style="width: 300px;height: {{canvasInfo.height}}px;" bindlongtap="saveCanvas"></canvas>

js

小程序canvas这块的api更新后用法需要注意

  • const ctx = canvas.getContext('2d');获得 Canvas 的绘图上下文
  • 需要先拿到页面canvas实例,然后使用 canvas.requestAnimationFrame 添加到计划中的动画帧
  • 渲染图片使用canvas.createImage(), ctx.drawImage(this._img, 0, 0, 300, imgHeight * 2, 75, 20, 150, imgHeight);
  • 结尾使用ctx.restore()即可,每次对ctx的操作都会由requestAnimationFrame自动渲染
  // 页面转canvas
  pageToCanvas() {
    this.setData({
      canvasHidden: false
    });
    const query = wx.createSelectorQuery()
    query.select('#myCanvas')
      .fields({
        node: true,
        size: true
      })
      .exec(this.canvasInit.bind(this));
  },
  
  // canvas初始化
  canvasInit(res) {
    const width = res[0].width
    const height = res[0].height

    const canvas = res[0].node;
    const ctx = canvas.getContext('2d');
    // 先清空一次
    ctx.clearRect(0, 0, 300, 300);

    const dpr = wx.getSystemInfoSync().pixelRatio;
    canvas.width = width * dpr;
    canvas.height = height * dpr;
    this.setData({
      'canvasInfo.destWidth': width * dpr,
      'canvasInfo.destHeight': height * dpr
    })
    ctx.scale(dpr, dpr);
    const renderLoop = () => {
      this.drawDog(ctx, canvas);
      // 控制渲染
      if (this.data.countDraw < 3) {
        this.setData({
          countDraw: this.data.countDraw + 1
        });
        canvas.requestAnimationFrame(renderLoop);
      }
    };
    canvas.requestAnimationFrame(renderLoop);
    // 渲染图片
    const img = canvas.createImage();
    img.onload = () => {
      this._img = img;
    }
    img.src = this.data.dogOptions.img;
    this.setData({
      canvas
    });
  },
  drawDog(ctx, canvas) {
    if (!this._img) return;
    // 获得句子
    const {
      imgHeight,
      canvasInfo: {height}
    } = this.data;
    ctx.fillStyle = '#FFFFFF';
    ctx.fillRect(0, 0, 300, height);
    ctx.fillStyle = '#000000';
    ctx.font = 'normal 14px sans-serif';
    // 渲染图片高度
    ctx.drawImage(this._img, 0, 0, 300, imgHeight * 2, 75, 20, 150, imgHeight);
    let canvasDrawHeightOffset = 4;
    if (this.data.checkboxValue.title) {
      ctx.font = 'normal bold 16px sans-serif';
      ctx.fillText('舔狗日记', 300 / 2 - 30, imgHeight + canvasDrawHeightOffset);
      canvasDrawHeightOffset = canvasDrawHeightOffset + 20;
    }
    ctx.font = 'normal 14px sans-serif';
    // canvas渲染平行offset
    let timeOffset = 0;
    let timeText = '';
    if (this.data.checkboxValue.time) {
      timeText += this.data.currTime;
      timeOffset = timeOffset - 30;
    }
    if (this.data.checkboxValue.weather) {
      timeText += '    阴';
      timeOffset = timeOffset - 10;
    }
    console.log(this.data.checkboxValue)
    if (timeText) {
      ctx.fillText(timeText, 300 / 2 + timeOffset, imgHeight + canvasDrawHeightOffset);
      canvasDrawHeightOffset = canvasDrawHeightOffset + 20;
    }
    for (const [key, value] of Object.entries(this.data.textArr)) {
      ctx.fillText(value, 20, imgHeight + key * 18 + 4 + canvasDrawHeightOffset);
    }
    ctx.restore();
  },

保存canvas渲染后的图片

小程序canvas这块的api更新后用法需要注意

保存图片前需要把canvas转成图片,通过canvasToTempFilePathapi可以拿到canvas转成图片后的内容

生成图片

  async getCurrentCanvasImage() {
    return new Promise((resolve, reject) => {
      wx.canvasToTempFilePath({
        canvas: this.data.canvas,
        success: (toFile) => {
          console.log(toFile.tempFilePath);
          resolve(toFile.tempFilePath);
        },
        fail: (res) => {
          console.log('图片生成失败');
          reject(null);
        }
      });
    });
  }

保存图片

使用saveImageToPhotosAlbum可以保存图片,但是需要拿到保存图片的权限,如果没有权限需要进行判断提示用户开启权限

  async saveCanvas() {
    const filePath = await this.getCurrentCanvasImage();
    // 此处做一下失败处理
    if (!filePath) {
      wx.showModal({
        title: '失败',
        content: '未知原因,生成图片失败'
      });
      return;
    }
    wx.saveImageToPhotosAlbum({
      filePath,
      success: (res) => {
        wx.showToast({
          title: '图片已保存',
        });
      },
      fail: async (error) => {
        const settingInfo = await wx.getSetting();
        if (!settingInfo.authSetting['scope.writePhotosAlbum']) {
          wx.showModal({
            title: '没有权限',
            content: '请开启相册权限',
            success: () => {
              wx.openSetting({
                success: (setting) => {
                  console.log(setting)
                }
              });
            }
          });
        }
      }
    })
  }

成果:

image.png