前端绘制并动态生成海报图片的那些事(canvas)

发表于 讨论求助 2020-10-13 12:12:53

近期遇到这类需求:根据不同用户 不同 的选择,最后生成带有 个人用户名 和 不同标签 的海报图,方便在朋友圈裂变传播。一共 7 张海报,状态分别如下图1:


    图1

如果要前端来生成这些图片,我们要怎么处理呢?继续往下看~

canvas.toDataURL() 生成图片

首先,前端要考虑怎么生成一张图片,这时候就会用到 canvastoDataURL 方法。

  • canvas.toDataURL(type,encoderOptions) 方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为 96dpi。


确定前端可以做后,下一步就是绘制我们需要的内容了。

创建一个画布

从 图1 中可以看到,除了文案和车型图片不同,背景底图是一致的。我们可以拆分需求:

  • 绘制底图

  • 绘制文案

这就需要用到 canvas 了,按照需求,我们要生成的图片大小是 640*810,先来看一下 html 代码。

  1. <section class="mask-poster">

  2.    <!-- img 用来存储 canvas 生成的图片地址 -->

  3.    <img id="poster" class="save-poster" src="" />

  4.    <canvas id="canvas" width="640" height="810"></canvas>

  5. </section>

我们需要先指定 canvas 画布的大小,然后开始 js 绘制。

js 创建画布如下:

  1. var oCanvas = document.querySelector('#canvas');

  2. var ctx = oCanvas.getContext('2d');

  3. ctx.fillRect(0, 0, 640, 810); // 指定画布大小

这时候会创建出一个黑色的 640810 的画布。效果如下图:

绘制海报背景图 (canvas.drawImage)

提示:这里有坑,要注意以下两点:

  • 需要 加载完成 的图片,再写入到 canvas

  • 涉及到跨域的图片文件,图片需要设置 crossOrigin 属性为 anonymous;

  1. // 绘制海报底图

  2. var oImg = new Image();

  3. oImg.setAttribute('crossOrigin', 'anonymous');

  4. oImg.src = 'https://s.autoimg.cn/topic/heycar/img/res.png';

  5. oImg.onload = function() {

  6.    ctx.drawImage(oImg, 0, 0, 640, 810);

  7. }

绘制效果如下图:

绘制文字

以 顶部标题 为例,需求是这样的:

  • 文案:用户昵称 的命中座驾是

  • 文字居中,昵称超出显示 ‘...’

我们先绘制昵称:

  1. // 绘制文字字体和大小

  2. ctx.font = '40px "PingFang SC", Helvetica, Arial, "Hiragino Sans GB", "Microsoft Yahei", STHeiTi, sans-serif';


  1. // 绘制文字颜色,fillStyle 默认值是 #000000,黑色的话可以不用写这行。

  2. ctx.fillStyle = '#000000';


  1. // 把文案绘制到 canvas 上,100 代表距离画布左上角 x 轴偏移量,60 代表 y 轴的偏移量

  2. ctx.fillText('悟空悟空', 100, 60);



图 4



代码运行效果如上方图4(忽略图中的框和线),思考一下问题:

  • 虚线框处内容需要根据前面文字的宽度,计算自己 x 轴偏移量。

  • 两个文案拼接,怎么居中?

我们接着往下看。

计算文字宽度

  1. ctx.measureText('悟空悟空').width;

measureText() 方法返回包含一个对象,该对象包含以像素计的指定字体宽度。

文字居中

  1. ctx.textAlign = 'center';

注: textAlign 的值是以 fillText 的 x 值来对齐。

所有属性值:

描述
start默认。文本在指定的位置开始。
end文本在指定的位置结束。
center文本的中心被放置在指定的位置。
left文本左对齐。
right文本右对齐。

回到绘制标题,步骤分别如下:

1. 以画布中心位置(320)为基准,昵称 ‘悟空悟空’ 设置 ctx.textAlign='end';。另一半文案设置 ctx.textAlign='start';

2. 昵称 如 图4 中,以 220 为中心居中。 opts.x 代表 步骤1 中的基准数值(320), wordWidth 代表 measureText计算出来的昵称宽度。

  1. ctx.fillText('悟空悟空', opts.x + (wordWidth - 220) / 2, 60);


3. 文字超出规定宽度(320),显示 ‘...’。 opts.text 代表昵称, maxWidth 代表320, opts.y 代表 y 轴偏移(60)。超出规定宽 截断 该昵称。


var wordWidth = 0; //记录文本宽度

  1. for( var i = 0; i < opts.text.length; i++) {

    1. wordWidth += ctx.measureText(opts.text[i]).width; // 计算文本宽度  

    2. // 文字超出规定宽,显示 "...""

  2.    if(wordWidth > maxWidth) {

  3.        ctx.fillText(opts.text.substring(0,i) + '...', opts.x + (wordWidth - 220) / 2, opts.y);

  4.        break;

  5.      }

  6. }

标题最终版效果如下图:

其它文案绘制原理同上,这里不多做介绍。

结语:完整版 demo 可以点击左下角 阅读原文 查看。大家往后遇到类似业务,希望这篇文章能帮助到你。如果你有更好的方案,欢迎留言分享,THANKS。


发表
26906人 签到看排名