快应用模版「今日美句」具体实现及流程分享

快应用 Sep 16, 2021

在先前快应用 IDE 再新增两套完善的模版,开箱即用一文中,提及了快应用 IDE 新增了一套模版,主要基于 Canvas 实现的「社交类快应用」──今日美句。本文即该模版作者的分享,讲述具体实现上的方法和流程;假如对此感兴趣,欢迎参与讨论。

一、首页日历

用于展示自当日,至往后一个月的日历数。每“张”日历卡片由图片、日期(阳历、阴历)及每日金句组成。

1. 左右切换

首页日历切换

使用swiper组件可以实现左右切换当前日期的需求。

swiper组件需要固定一个高度,不能由子组件撑开。首页每“张”日历卡片需占一屏,使用下面方法获取pageHeight

  • index.ux

    async onInit() {
        // this.$app.$def.manifest.config.designWidth 可获取已配置designWidth(*只读),如未配置则使用默认designWidth
        const designWidth =
          this.$app.$def.manifest.config.designWidth || this.$app.$def.designWidth // 这里建议将默认designWidth即`750`,作为常量保存在`app.ux`
        const { windowHeight, windowWidth } = await $utils.deviceGetInfo() // utils.js中方法已注册到全局
        this.height = (windowHeight / windowWidth) * designWidth // pageHeight
      }
    
  • utils.js

    /**
     * 获取设备信息
     */
    function deviceGetInfo() {
      return new Promise((resolve, reject) => {
        require("@system.device").getInfo({
          success: (ret) => {
            resolve(ret);
          },
        });
      });
    }
    
  • app.ux

    const $utils = require("[pathName]/utils").default;
    /* @desc: 注入方法至全局 global,以便页面调用 */
    const hook2global = global.__proto__ || global;
    hook2global.$utils = $utils;
    

2. 日历卡片

日历卡片

2-1. 图片

2-1-1. 居中

由于展示在卡片的背景图片,不一定大小相同。此时可以设置一个固定的“box",来绘制图片区域。该模板中设置图片宽高比(sw:sh)固定为 3:2。

使用ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)进行绘制时,需要注意:

  • 如果图片的实际高>= “box”的高,则需绘制的图片宽度dwsw,再计算出dh
  • 如果图片的实际高 < “box”的高,则需绘制的图片高度dhsh,再计算出dw
const sw = this.width;
const sh = this.width / (3 / 2);
let dw = sw;
let dh = dw / (img.width / img.height);
if (dh < sh) {
  dh = sh;
  dw = dh * (img.width / img.height);
}
2-1-2. 明暗

首页的图片明暗设置一个默认值。需要从父组件传递,详细在卡片分享-明暗中说明。

2-2. 日历

日历部分,分为阴历、阳历。

2-2-1. 日历数据

这里直接找了一个 js 库去获取到日期相关信息calendarGitHub 地址

calendar-info
2-2-2. 绘制阴历

年月日以“列”的形式排版,覆盖在图片上。根据获取到的日历数据,需要使用的信息有

const { Animal, IDayCn, IMonthCn, gzDay, gzMonth, gzYear } = calendar;

日期内容整体靠右,先定义一些需要的常量、变量。常量数值并无固定,但建议根据卡片宽度(首页日历宽度为页面宽度,即designWidth),按比例计算,提高兼容性

const YTD_SIZE = sw / 28; // 年月及星期 文字大小
const FIRST_WORD_TOP = sh / 6.25, // 每列文字内容,首字符的上边距
  LINE_HEIGHT = YTD_SIZE + 10, // 字体大小加上下间距总和10
  LETTER_SPACING = YTD_SIZE + 10; // 字体大小加左右间距总和10
// 第一个需要绘制的日期字符的初始坐标 (dx,dy)
let dx = sw * 0.76,
  dy = FIRST_WORD_TOP;

由于文字内容以“列”的形式排版,则每绘制一个字符,dy就需要增加一个文字的lineHeight

lunarDateHandler(ctx, str, dx, dy, lineHieght) {
    str.split('').forEach(ele => { // calendar返回的日期信息,不一定是一个字符,比如 gzMonth=“戊戌”等
      ctx.fillText(ele, dx, dy)
      dy += lineHieght
    })
    return dy // 返回新的dy用于绘制分割线的高
}

每列文本内容的左边,有一条分割线。有些分割线位于两列文字之间,给分割线定义一些左右的"margin"

const BORDER_LEFT = 10, // margin-left
      BORDER_RIGHT = 10, // margin-right
      BORDER_TOP = FIRST_WORD_TOP - YTD_SIZE, // 每列文字内容的左边框,左右边距及上边距

因为 canvas 绘制文字时,并不存在“行高”的概念,在绘制每列最后一个字符时,实际上只需要再增加一个字符下边距的值(即 (lineHieght-YTD_SIZE)/2,返回的就是实际需要的线条高度,但是我们并没有在lunarDateHandler方法中处理,直接增加了一个lineHeight

this.lunarDateBorderLeft(
      ctx,
      dx - BORDER_RIGHT,
      BORDER_TOP,
      dx - BORDER_RIGHT,
      dy - YTD_SIZE  // 这里需要减去一个字符高度,视觉上使文字上边距和线条上边距对齐,达到字符拥有“行高”的效果
    )
/**
 * 绘制线条的方法
 */
lunarDateBorderLeft(ctx, x0, y0, x1, y1) {
    ctx.moveTo(x0, y0)
    ctx.lineTo(x1, y1)
    ctx.stroke()
}
2-2-3. 绘制阳历

年月及星期:

​ 简单定义一个(x,y),使得文字内容位于右下角即可,建议根据卡片宽度,按比例来计算。

当日日期:

​ 简单定义一个(x,y),使得文字内容位于左下角即可,建议根据卡片宽度,按比例来计算。

2-2-4. 每日金句

下方的文本内容,上下居中且换行展示。

根据卡片的高度(由于首页占一屏,即pageHeight)减去上方图片及日期的高度,计算出剩余的、可用于展示文字内容的高度contentHeight

const contentHeight = this.height - sh;

定义固定文字内容左边距每行宽度

const lineWidth = $utils.lineWidthHandler(w); // 固定每行宽度
const default_drawX = w * 0.06; // 固定一个左边距
2-2-5. 开始绘制
  • 绘制前:
// 获取文字内容总宽度
const txtToatalWidth = ctx.measureText(content).width; // 这个宽度和文字大小及文字内容的长度有关
let drawTxt = ""; // 当前绘制的内容
let drawLine = 1; // 第几行开始绘制
let drawIndex = 0; // 当前绘制内容的索引
  • 绘制方法

    需要绘制内容的宽度ctx.measureText(drawTxt).width<每行宽度lineWidth,则直接绘制:

    if (txtToatalWidth <= lineWidth) {
      ctx.fillText(content, drawX, drawY);
    }
    

    需要绘制内容的宽度ctx.measureText(drawTxt).width>每行宽度lineWidth

    for (let i = 0; i < content.length; i++) {
    	drawTxt += content[i]
    if (ctx.measureText(drawTxt).width >= lineWidth) {
            if (drawLine >= 10) { // 绘制的行数大于10时,不再进行绘制,以省略号的形式展示
              ctx.fillText(content.substring(drawIndex, i) + '..', drawX, drawY)
              break
            } else {
              ctx.fillText(content.substring(drawIndex, i + 1), drawX, drawY)
              drawIndex = i + 1
              drawLine += 1
              drawY += lineHeight
              drawTxt = ''
            }
          } else {
            //   内容绘制完毕,但是剩下的内容宽度不到lineWidth
            if (i === content.length - 1) {
              const lastConten = content.substring(drawIndex)
              ctx.fillText(lastConten, drawX, drawY)
            }
          }
       }
    }
    
  • 封装方法

    此时可以将文本内容换行方法封装,命名为textWrap

    function textWrap(ctx,content,lineWidth,lineHeight,drawX,drawY){
      if (txtToatalWidth <= lineWidth) {
      // 需要绘制内容的宽度`ctx.measureText(drawTxt).width`<每行宽度`lineWidth`,则直接绘制:
        ctx.fillText(content, drawX, drawY)
      } else{
          // 需要绘制内容的宽度`ctx.measureText(drawTxt).width`>每行宽度`lineWidth`:
        ...
      }
    }
    

二、卡片分享

将需要分享的内容(金句、诗歌/词),进行相应编辑后,生成图片保存到相册,并分享到各平台。

1. 分享内容

1-1. 金句模板 (日历形式)

日历卡片
1-1-1. 固定卡片宽度

定义一个固定的宽度,建议根据designWidth,按比例计算

let designWidth =
  this.$app.$def.manifest.config.designWidth || this.$app.$def.designWidth;
this.width = designWidth * 0.9;
1-1-2. 计算卡片高度
this.fontSize = this.width / 21; // 随意定义一个字体大小
this.lineHeight = this.width / 21 + 20; // 根据字体大小增加“上下间距”,定义“行高”
const lineWidth = $utils.lineWidthHandler(this.width); // 固定每行宽度
this.lines = (this.info.content.length * this.fontSize) / lineWidth; // 计算总行数
const otherLines = this.lineHeight * 5; // slogan及source预留高度
const padding = this.width / 7.5; // 文本内容上下padding
// 图片宽高比3:2
const imgHeight = this.width / 1.5;
// 卡片最小高度
const minHeight = imgHeight * 2;
const padding = this.lineHeight * 4;
this.height = this.lines * this.lineHeight + padding + imgHeight + otherLines;
this.height = this.height < minHeight ? minHeight : this.height;

绘制步骤及方法同日历卡片。此时,可以将日历“卡片”下方内容的绘制方法封装,只需要传递卡片的宽、高,文字大小、行高和所需绘制内容等关键信息,即可绘制不同 size的“卡片”:

function drawContent(ctx, info, w, h, fontSize, lineHeight, marginTop, slogan) {
  ctx.fillStyle = "#000000";
  const lineWidth = $utils.lineWidthHandler(w); // 固定每行宽度
  const default_drawX = w * 0.06; // 固定一个左边距
  const content = info.content;
  let title = info.title || "";
  let author = info.author || "";
  const source = `${author} ${title}`;

  ctx.fillStyle = color;
  ctx.font = `${fontSize}px`;

  const lineNum = Math.ceil(lineWidth / fontSize);
  const lines = Math.ceil(content.length / lineNum);
  let drawX = default_drawX;
  let sourceLeft = w - ctx.measureText(source).width - drawX;
  let drawY = (h - lines * lineHeight) / 2 + marginTop;
  // 绘制引用出处(作者、标题)
  if (!!title || !!author) {
    drawY = (h - lines * lineHeight + lineHeight) / 2 + marginTop - lineHeight;
    let sourceTop = lines * lineHeight + drawY + lineHeight / 2;
    ctx.fillText(source, sourceLeft, sourceTop);
  }
  // 绘制句子
  textWrap(ctx, content, lineWidth, lineHeight, drawX, drawY);
  // 绘制分享来源
  if (slogan !== "") {
    slogan = `分享自${slogan}快应用`;
    const sloganSize = $utils.minFontSize(w); // 定义任意合适的字体大小,建议根据designWidth按比例计算
    ctx.fillStyle = "#cccccc";
    ctx.globalAlpha = 0.6;
    ctx.font = `${sloganSize}px`;
    let sloganMarginLeft = (w - ctx.measureText(slogan).width) / 2; // 居中
    const sloganMarginBottom = 50;
    let sloganMarginTop = h - sloganMarginBottom + marginTop;
    ctx.fillText(slogan, sloganMarginLeft, sloganMarginTop);
    ctx.globalAlpha = 1;
  }
  return lines;
}

1-2. 金句模板(图文形式)

固定为正方形,背景填充为纯图片,文本内容上下左右居中于图片上方

图文卡片
1-2-1. 绘制图片

图片也需要根据实际宽高进行居中展示,参考日历卡片-图片的绘制方法

1-2-2. 绘制内容

由于是直接绘制在图片上方,此时 marginTop 为 0,直接调用drawContent方法:

drawContent(
  ctx,
  info,
  w,
  h, // h = w
  fontSize,
  lineHeight,
  (marginTop = 0)
);

1-3. 绘制金句卡片

关于明暗度的绘制,实际上是添加了一层黑色的"蒙版",通过父子组件之间传值,$watch监听蒙版透明度的变化,实现效果

drawCard(
    idx,
    info,
    font = this.font,
    fontSize = this.fontSize,
    hasTitle = this.hasTitle,
    hasAuthor = this.hasAuthor,
    lineHeight = this.lineHeight,
    alignType = this.alignType
  ) {
    const canvas = this.$element(`canvas${idx}`) //获取 canvas 组件
    const ctx = canvas.getContext('2d') //获取 canvas 绘图上下文
    ctx.clearRect(0, 0, this.width, this.height)
    ctx.fillStyle = '#ffffff'
    ctx.fillRect(0, 0, this.width, this.height)
    // 绘制背景图
    const img = new Image()
    img.src = info.data.image
    img.onload = () => {
      // 固定背景图大小 w: h = 3: 2
      const sw = this.width
      const sh = this.width / (3 / 2)
      let dw = sw
      let dh = dw / (img.width / img.height)
      if (dh < sh) {
        dh = sh
        dw = dh * (img.width / img.height)
      }
      ctx.drawImage(
        img,
        0,
        -(dh - sh) / 2,
        img.width,
        img.height,
        0,
        -(dh - sh),
        dw,
        dh
      )
      // 明暗度
      ctx.globalAlpha = this.alpha
      ctx.fillStyle = '#000000'
      ctx.fillRect(0, 0, sw, sh)
      // 文字部分
      ctx.globalAlpha = 1 // 还原默认透明度 1
      // 日期部分 阴历
      this.drawLunarDate(ctx, info.calendar, sw, sh)
      // 日期部分 阳历
      const DATE_SIZE = sw / 3.5 // 当日 文字大小
      const marginTop = sh + DATE_SIZE * 0.2
      this.drawNewDate(ctx, info.calendar, sw, sh, DATE_SIZE, marginTop)
      // 内容
      const contentHeight = this.height - sh
      this.lines = drawContent(
        ctx,
        info.data,
        sw,
        contentHeight,
        fontSize,
        '#000000',
        this.slogan,
        marginTop,
        hasTitle,
        hasAuthor,
        font,
        lineHeight
      )
    }
    img.onerror = () => {
      console.log('图片加载失败')
    }
  }

1-4. 诗词/诗歌模板

诗词/诗歌的换行比较特殊,例如:

诗词诗歌

这样的文本内容,不能单纯用”。”或者“ ”(空格)区分。

1-4-1. 数据处理

因此从数据着手,将需要换行的句子,用**“/”隔开**:

`“行尽潇湘到洞庭。楚天阔处数峰青。旗梢不动晚波平。/红蓼一湾纹缬乱,白鱼双尾玉刀明。夜凉船影浸疏星。”`

`“等待也许终于有人记得端来/她那甜甜的 甜甜的 甜点”`

再将其处理为数组

let arr = this.info.content.split("/");

这样,我们可以得到一个数组:

诗词-数组
1-4-2. 新的数组

可以发现,上图中,诗词(左图)的每行,并不是按照数组中每个元素去绘制的,而是将每个数组元素中的内容绘制了两行。

  • 随意设置一个合适的字体大小及行高:

    this.fontSize = $utils.setFontSize(this.width);
    this.lineHeight = $utils.setLineHeight(this.fontSize);
    
  • 处理数组的方法:

    getContentArr(){
        const lineWidth = $utils.lineWidthHandler(this.width) // 固定每行宽度
        let arr = this.info.content.split('/')
        let newArr = []
        for (let i = 0; i < arr.length; i++) {
          let totalWords = arr[i].length // 需要绘制的文本总字数
          let countWordOfALine = Math.floor(lineWidth / this.fontSize) // 限制一行绘制的字数
          if (totalWords > countWordOfALine) {  // 超出一行所限的文本内容
            const aliquot = Math.floor(totalWords / countWordOfALine)
            const remainder = arr[i].substring(aliquot * countWordOfALine)
            let start = 0
            let end = countWordOfALine
            arr.splice(i, 0)
            for (let j = 0; j < aliquot; j++) {
              start = countWordOfALine * j
              end = start + countWordOfALine
              let str = arr[i].substring(start, end) // 绘制aliquot次countWordOfALine长度的文本内容
              newArr.push(str)
            }
            newArr.push(remainder) // 剩余不够绘制一次countWordOfALine长度的文本内容
          } else {
            newArr.push(arr[i]) // 未超出一行所限的文本内容
          }
        }
        return newArr
      }
    
  • 然后,可以得到一个新的数组:

诗词-新数组
1-4-3. 绘制内容的总行数

根据新数组,我们可以得到绘制的总行数:

let contentArr = this.getContentArr();
this.lines = contentArr.length;
1-4-4. 绘制内容中的固定高度

我们还需要固定出引用来源(标题、作者和时代)、slogan 的高度,以及给卡片一个上下的padding

const titleSize = $utils.setFontSize(this.width, 16);
const sourceSize = $utils.minFontSize(this.width);
const cardTop = $utils.setFontSize(this.width, 5);
const cardBottom = cardTop;
const sourceTop = $utils.setFontSize(this.width, 44);
const sourceBottom = $utils.setFontSize(this.width, 18);
1-4-5. 计算卡片高度

有了上述信息,我们可以获得卡片的高度:

this.height =
  titleSize +
  sourceSize +
  this.lines * this.lineHeight +
  cardTop +
  cardBottom +
  sourceTop +
  sourceBottom +
  sloganSize;
1-4-6. 绘制内容对齐方式
  • 诗歌:

    文本内容左对齐

    绘制“日历卡片”的固定左边距的方法,同样适用“诗歌卡片”

    const default_drawX = w * 0.06; // 固定一个左边距
    let dx = default_drawX;
    
  • 诗词:

    文本内容上下左右居中对齐

    不适用于上述固定左边距的方法,需要对drawX再次进行计算

    if (info.dynasty !== "现代") {
      // 可以根据朝代去区分
      let lenArr = [];
      arr.forEach((ele) => lenArr.push(ele.length));
      let maxCount = Math.max(...lenArr); // 取数组元素中最长的内容,计算出其宽度
      let temp = arr.filter((ele) => ele.length === maxCount);
      dx = (w - ctx.measureText(temp[0]).width) / 2; // 得到可以使文本内容整体居中的左边距
    }
    
1-4-7. 封装方法

接下来可以封装绘制诗词/诗歌卡片,主体文本内容的方法:

drawContent(
    ctx,
    info,
    w,
    h,
    dy,
    arr,
    font,
    fontSize,
    lineHeight
  ) {
    const lineWidth = $utils.lineWidthHandler(w) // 固定每行宽度
    const default_drawX = w * 0.06 // 固定一个左边距
    let dx = default_drawX
    dy += lineHeight
    ctx.fillStyle = color
    ctx.globalAlpha = 1
    ctx.font = `${fontSize}px normal ${font}`
    if (info.dynasty !== '现代') {
      let lenArr = []
      arr.forEach(ele => lenArr.push(ele.length))
      let maxCount = Math.max(...lenArr)
      let temp = arr.filter(ele => ele.length === maxCount)
      dx = (w - ctx.measureText(temp[0]).width) / 2
    }
    for (let i = 0; i < arr.length; i++) {
      const element = arr[i]
      ctx.fillText(arr[i], dx, dy)
      if (i !== arr.length - 1) dy += lineHeight
    }
    return dy
  }

1-5. 绘制诗词/诗歌卡片

drawCard(
    info,
    font,
    fontSize,
    lineHeight
  ) {
    const canvas = this.$element('canvas') //获取 canvas 组件
    const ctx = canvas.getContext('2d') //获取 canvas 绘图上下文
    let w = this.width,
      h = this.height
    ctx.clearRect(0, 0, w, h)
    ctx.fillStyle = '#ffffff'
    ctx.fillRect(0, 0, w, h)
    // 标题
    let {
      contentArr,
      titleSize,
      sourceSize,
      cardTop,
      cardBottom,
      sourceTop,
      sourceBottom
    } = this.$parent().cardSizeHandler()
    const source = `${info.dynasty} • ${info.author}`
    ctx.globalAlpha = 1
    ctx.fillStyle = '#ffffff'
    ctx.fillRect(0, 0, w, h)
    ctx.fillStyle = '#000000'
    // 标题
    ctx.font = `${titleSize}px bold sans-serif`
    let dx = (w - ctx.measureText(info.title).width) / 2
    let dy = cardTop
    ctx.fillText(info.title, dx, dy)
    // 来源(作者)
    ctx.font = `${sourceSize}px normal sans-serif`
    dx = (w - ctx.measureText(source).width) / 2
    dy = dy + sourceTop + titleSize
    ctx.fillText(source, dx, dy)
    // 内容
    dy = dy + sourceSize + sourceBottom
    dy = this.drawContent(
      ctx,
      info,
      w,
      h,
      dy,
      contentArr,
      font,
      fontSize,
      lineHeight
    )
    // slogan
    const txt = `分享自${this.slogan}快应用`
    const sloganSize = $utils.minFontSize(w)
    ctx.fillStyle = '#000000'
    ctx.globalAlpha = 0.3
    ctx.font = `${sloganSize}px normal sans-serif`
    dx = (w - ctx.measureText(txt).width) / 2
    ctx.fillText(txt, dx, this.height - cardBottom / 2)
  }

2. 工具栏

工具栏

2-1. 模板

卡片模板,即分享内容

2-2. 图片(仅金句卡片支持)

布局不再赘述

实现切换图片,就要从卡片绘制方法入手,将图片地址,作为变量暴露出来,绘制方法在分享内容drawCard,由父组件传递给卡片组件:

// 子组件 options
  choosePicHandler(item, idx) {
      this.localImage = false
      this.subCurrentItem.index = idx
      this.subCurrentItem.type = this.tabBarActive
      this.recentlyList = this.recentlyList.filter(ele => ele !== item)
      this.recentlyList.unshift(item)
      if (this.recentlyList.length > 10) this.recentlyList.pop()
      $utils.setStorage('recentlyPics', JSON.stringify(this.recentlyList)) // 通过storage进行存储
      this.$emit('draw', {
        currentImg: item,
        subCurrentItem: this.subCurrentItem
      })
  }
// 父组件 shareCard
  getNewCurrentImage(e) {
      if (!!e.detail.currentImg) {
        this.cardInfo.data.image = e.detail.currentImg
        switch (this.cardType) {
          case 0:
            this.$child('card').drawCard(this.idx, this.cardInfo)
            break
          case 1:
            this.$child('square').drawCard(this.cardInfo.data)
            break
          default:
            break
        }
        $utils.setStorage(
          'subCurrentItem',
          JSON.stringify(e.detail.subCurrentItem)
        )  // 通过storage进行存储
      }
   }

2-3. 排版

同样是需要什么就在drawCard中进行声明,将其暴露出来

// 行距、字体大小的增减
  plusAndMinusHandler(e) {
    let { type, num } = e.detail
    if (type === 'lineHeight') {
      const minLineHeight = $utils.setLineHeight(this.fontSize)
      const maxLineHeight = $utils.maxLineHeight(this.fontSize)
      if (this.lineHeight <= minLineHeight && num < 0) {
        this.lineHeight = minLineHeight
        return
      }
      if (this.lineHeight >= maxLineHeight && num > 0) {
        this.lineHeight = maxLineHeight
        return
      }
      this.lineHeight += num
    }
    if (type === 'fontSize') {
      const minFontSize = $utils.minFontSize(this.width)
      const maxFontSize = $utils.maxFontSize(this.width)
      if (this.fontSize <= minFontSize && num < 0) {
        this.fontSize = minFontSize
        return
      }
      if (this.fontSize >= maxFontSize && num > 0) {
        this.fontSize = maxFontSize
        return
      }
      let temp = this.lineHeight - this.fontSize
      this.fontSize += num
      this.lineHeight = temp + this.fontSize
    }
    if (this.cardType === 0) this.cardSizeHandler()
    this.drawNewCard()
  },
    // 文本对齐
  textAlignHandler(e) {
    if (this.cardType === 0) {
      this.alignTypeNormal = e.detail.type
    }
    if (this.cardType === 1) {
      this.alignTypeSquare = e.detail.type
    }
    this.drawNewCard()
  },
  //来源显隐
  sourceHandler(e) {
    let { title, author } = e.detail.data
    this.drawNewCard({ hasTitle: title, hasAuthor: author })
  },
    // 诗歌/诗词 配色方案
  schemeHandler(e) {
    let { color, bgcolor } = e.detail.scheme
    this.color = color
this.bgcolor = bgcolor
    this.drawNewCard()
  }

三、遇到的问题

1.日期绘制

今日美句,很大程度上,参考了原生应用《句读》;其首页卡片原需求的样式布局如下:

calendar-card

由于快应用 canvas 组件,图形和文字在使用canvas.globalCompositeOperation 属性时,会有显示问题。因此将 10(即当日日期) 这部分绘制的内容,

直接文本填充:

let marginLeft = sw / 15;
ctx.font = `${fontSize}px bold sans-serif`;
ctx.fillStyle = "black";

后续引擎版本解决该 bug,可以使用下面代码实现原需求样式:

ctx.fillText(`${date}`, marginLeft, marginTop);
ctx.fillStyle = "white";
ctx.fillRect(0, sh - DATE_SIZE / 2, sw, DATE_SIZE / 2);
ctx.fillStyle = "black";
ctx.globalCompositeOperation = "destination-in";

2. 工具栏-字体

原需求如下:

工具栏-文字工具栏-文字

可以看到,快应用的 text 组件支持的一些字体样式,由于 canvas 组件开发时,未考虑到 ctx.font = 50px normal serif 这种写法,官网中给的写法默认是 ctx.font = 10px sans-serif,导致 50px normal serif 这种三个样式的写法中最后一个样式 都不生效

暂时只能注释该工具栏功能,待后续引擎版本修复后,可以将该部分代码开放使用。

const sentenceOpt = [
  {
    image: "../../assets/images/icon/template.png",
    type: "模板",
  },
  {
    image: "../../assets/images/icon/image.png",
    type: "图片",
  },
  // {
  //   image: '../../assets/images/icon/font.png',
  //   type: '字体'
  // },
  {
    image: "../../assets/images/icon/layout.png",
    type: "排版",
  },
];

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.