小程序篇-canvas使用小技巧

前面说的话

某产品:这个版本我想要做个我们的内容可以生成海报的功能,

用户可以保存海报,然后在朋友圈分享,

巴拉巴拉

。。。

。。

,现在UI还在设计一款比较漂亮的海报。。。

某前端(心里想):希望布局不要太复杂,不然用canvas生成的话,又得加班加点了。
(眉头一皱):嗯,可以,到时候UI出来设计稿的时候,我就开始动工。


然后UI出图了。【我们UI一般把图上传至 蓝湖 这样比较好看一些尺寸信息,下面的图片也是从 蓝湖 上截图的】

设计稿图

感觉还不错,不会很复杂的样子,(以这张海报先举个例子)

看到这幅图的时候,你要做的几件事:

1.首先问问UI,这是不是最终版本

2.确定好是最终版本后,我们开始在这个海报页上区分,哪些是变量,哪些是固定元素/切图,哪些是UI需要提供的切图

3.确定好之后,我们就可以开始动工了


接下来我们就来看看几个需要实现的技术点

1.海报中间的图片需要怎么样去控制,才能显示得“好(保持纵横比缩放图片,只保证图片的短边能完全显示出来也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。)”?

2.底部的文字如何实现换行

3.分享自@xxx 的文字如果太长怎么办?


我们一一来解决这些问题吧

基础准备,设计稿一般都为750*1334,一般来说我们会让设计稿标注各个元素的间距,这个间距是px单位的,

但是小程序是rpx单位的,怎么建立起对应关系,而又在开发中提升效率呢

举个例子
首先我们创建一个叫做poster的小程序页面
我们在wxml,这里有三个变量,分别是cardCreateImgUrl【canvas生成的图片路径】、WIDTH【设计稿的宽度】、HEIGHT【设计稿的高度】

<!-- ... -->
<view id="posterWrp">
  <view class="cardCreateWrp">
     <image src="{{cardCreateImgUrl}}" mode="aspectFill" class="imgCardWrp" bindload="bindload" 
style="width:{{WIDTH}}rpx;height:{{HEIGHT}}rpx;"></image> 
  </view>
  <canvas class="myCanvas" canvas-id="myCanvas" style="width:{{WIDTH}}px;height:{{HEIGHT}}px;" ></canvas>
</view>
<!-- ... -->

wxss中,我们把canvas的top设置成-9999px ,
其实canvas是存在页面的,只是我们把它用这种方式隐藏了,
这样做的目的,就相当于canvas我们现在只用来作为一个画图工具,
我们画好canvas并把它生成的图片路径赋值到image标签的src来进行显示,
这就是我们提到的提高开发效率的第一步,
我们现在看到的是一张已经生成好的图片,而不是canvas

// ...
.cardCreateWrp image {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
.myCanvas {
  position: absolute;
  top: -9999px;
  left: 50%;
  transform: translate(-50%, -50%);
}
// ...

js中,
我们根据设计稿的宽高来设置

海报图片

data: {
  // ...
  WIDTH: 750,
  HEIGHT: 1148,
  cardCreateImgUrl: ''
  // ...
},

我们在一开始画canvas的时候就要把canvas生成图片了

// ...
// 前面是一些canvans的操作,略过
// ...
ctx.draw(false, () => {
  // 生成图片
  wxp.canvasToTempFilePath({
    canvasId: 'myCanvas'
  }).then(({
    tempFilePath
  }) => {
    this.setData({
      cardCreateImgUrl: tempFilePath
    });
  });
});

这样子的话,我们一开始就是一张图片,边画画布的时候,就能看到图片生成的样子。

这些都是准备工作,准备好了之后我们就可以开始解决我们在画图过程中的技术点了。


1.海报中间的图片需要怎么样去控制,才能显示得“好(保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。)”?

这里就要涉及到画布的clip()方法和我们用js去计算了,
不清楚clip的话可以先去微信小程序官网api了解一下,
了解以后我们来看以下的代码

设计图的图片宽高是这样的 621px、668px

距离顶部和左部是这样的 251px、66px

代码就不多解释了,反正就是这些计算就是让图片能更好的显示

// ...
wxp.getImageInfo({
  // 七牛云做压缩
  src: `${data.imageUrl}?imageView2/1/w/621/h/668/q/50`
}).then((res) => {
  // 计算海报宽高 
  const scale1 = res.width / res.height;
  const scale2 = 621 / 668;
  let drawW = 0,
      drawH = 0,
      mt = 0,
      ml = 0;
  if (scale1 > scale2) {
    drawH = 668;
    drawW = 668 * scale1;
    ml = (621 - drawW) / 2;
  } else {
    drawW = 621;
    drawH = drawW / scale1;
    mt = (668 - drawH) / 2;
  }

  ctx.save();
  ctx.beginPath();
  ctx.strokeStyle = "rgba(0,0,0,0)";
  ctx.rect(66, 251, 621, 668);
  ctx.closePath();
  ctx.stroke();

  ctx.clip();
  // 画图片
  ctx.drawImage(res.path, ml + 66, mt + 251, drawW, drawH);
  ctx.restore();
  
 // ...
});
// ...

这样子,我们第一个技术点就解决了


2.底部的文字如何实现换行
我们都知道canvas不像我们在写html页面时,如果文字太长了,会自动换行。
所以我们应该怎么做呢
这里我们用到了 measureText() 这个方法

我们把文字过长换行的方法封装了一下
我们创建了一个名为canvas-text-break.js 的js文件


/**
 * @desc canvas文字过长换行脚本
 * @param {Object} ctx canvas对象
 * @param {String} text 文字
 * @param {Number} x 距离左边的宽度
 * @param {Number} y 距离右边的宽度
 * @param {Number} w 文本区域的宽度
 * @param {Object} fontStyle 文本的字体风格/位置,有默认值
 */
const CTB = ({
  ctx,
  text,
  x,
  y,
  w,
  fontStyle: {
    lineHeight = 60,
    textAlign = 'left',
    textBaseline = 'top',
    font = 'normal 40px arial',
    fillStyle = '#000000'
  }
}) => {
  ctx.save();
  ctx.font = font;
  ctx.fillStyle = fillStyle;
  ctx.textAlign = textAlign;
  ctx.textBaseline = textBaseline;
  const chr = text.split('');
  const row = [];
  let temp = '';

  /*
  判断如果末尾是,!。》 就不要换行
  判断如果末尾是《 就要换行
  */
  for (let a = 0; a < chr.length; a++) {
    if (ctx.measureText(temp).width < w) { } else {
      if (/[,。!》]/im.test(chr[a])) {
        // console.log(`我是${chr[a]},我在末尾,我不换行`);
        temp += ` ${chr[a]}`;
        // 跳过这个字符
        a++;
      }
      if (/[《]/im.test(chr[a - 1])) {
        // console.log(`我是${chr[a-1]},我在末尾,我要换行`);
        // 删除这个字符
        temp = temp.substr(0, temp.length - 1);
        a--;
      }

      row.push(temp);
      temp = '';
    }

    temp += chr[a] ? chr[a] : '';
  }
  row.push(temp);
  for (let b = 0; b < row.length; b++) {
    ctx.fillText(row[b], x, y + b * lineHeight);
  }
  ctx.restore();
};
export default CTB;

文字的相关信息

根据上面这个图,我们就可以获取信息

  x: 64,
  y: 988,
  w: 350,
  fontStyle: {
    lineHeight: 39,
    textAlign: 'left',
    textBaseline: 'top',
    font: 'normal 23px arial',
    fontSize: 23,
    fillStyle: '#000000'
  }

然后在js中我们这样子做

import CTB from '../../utils/canvas-text-break';
let ctx = null;
// ...

// ...
ctx = wx.createCanvasContext('myCanvas');
const text = '你来人间一趟,你要看看太阳。和你的心上人一起走在街上。';
// ...
CTB({
  ctx,
  text,
  x: 64,
  y: 988,
  w: 350,ß
  fontStyle: {
    lineHeight: 39,
    textAlign: 'left',
    textBaseline: 'top',
    font: 'normal 23px arial',
    fontSize: 23,
    fillStyle: '#000000'
  }
});

这样子,我们的第二个技术点也解决了


3.分享自@xxx 的文字如果太长怎么办?
这个问题比较简单


文字距离顶部和右部是这样的 1065px、66px

我们在js中这样写,记得
ctx.textBaseline = 'top';
ctx.textAlign = 'right';
这样子的话文字多的话,就会往左边延伸了,这个原理和我们在操作html文字的原理是类似的

ctx.save();
ctx.font = 'bold 25px arial';
ctx.fillStyle = '#000000';
ctx.textBaseline = 'top';
ctx.textAlign = 'right';
const {
  nickName
} = wx.getStorageSync('user');
ctx.fillText(`分享自 @${nickName}`, WIDTH - 63, 1065);
ctx.restore();

这样子,我们所有的技术点都解决了


注:wxp为我自己封装的对象,并不是wx的对象,在这里 小程序篇-wx自带api接入Promise,提升编程体验

更新:可能有些人比较习惯看demo,这里分享一个 代码片段

以上只是分享一些小技巧而已,我们在实际操作中肯定遇到的场景会更复杂,这些都是一些基础要知道的。
——尼古拉斯·峰

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,378评论 25 707
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生x阅读 15,967评论 3 119
  • 继上一篇文章提到,詹姆斯·瑟伯的童话,还有另外两篇,分别是《13只钟》和《白鹿》,另又涵括了一个短故事《公主与铁盒...
    安之腾阅读 1,589评论 6 10
  • 斯人若彩虹,遇上方知有
    traveler_80e0阅读 141评论 0 0
  • 前言:无论你自己的程序还是其他人的,都是程序,都是人写的,按照思路往下走。 对比秘钥 像支付宝,微信这类调节方式1...
    zcwfeng阅读 1,100评论 0 1