之前在移动端上做了几个cavnas
生成海报的需求,发现不是插件不够好用就是自己每次手写代码去绘制cavans
十分麻烦,所以抽空集中处理一下这个功能。
需要实现的功能清单
- 移动端的主流平台兼容:H5和微信小程序
- 像写
CSS
一样去写海报的样式。 - 基础的三样几何图形的绘制:盒子(包括边框)、图片、文字;有了这3个基础几何图形,基本上就可以满足日常海报的样式了。
先来看下最终预览效果 cavnas-生成海报
实现过程
第一点非常好解决,因为都是移动端,所以直接用uni-app即可,至于canvas
的api
在不同平台端表现不同这里已经解决了,比如CanvasContext.draw
: H5端原生的getContext("2d")
获取的上下文中是通过context.save()
和context.restore()
就可以继续绘制下一个图形,但uni-app则需要在原生的基础上再多加一个context.draw(true)
,才能够正常绘制下一个图形,不然就有很奇怪的视图问题,还有其他的像context.fill()
、context.clip()
等一些绘制顺序和调用的方式和网页原生canvas-api
不一致就不展开细说了,因为已经处理好。
配置样式传参
第二点,我希望调用这个工具的时候,不用去记一些JS
的api
进行视图操作,而是像CSS
一样设置width
、font-size
、border-radius
、z-index
等常用属性,比起JS-api
,CSS
样式一看就一目了然。接下来需要用TS
声明三种几何图形的CSS
样式类型,在这里代码提示和约束体验十分明显,看下代码:
/** `cavans`位置类型 */
interface CavansPosition {
/** 距离顶部偏移值 */
top?: number
/** 距离底部偏移值,会覆盖`top` */
bottom?: number
/** 距离左边偏移值 */
left?: number
/** 距离右边偏移值,会覆盖`left` */
right?: number
/** 位置层级,与`css`行为一致 */
zIndex?: number
}
/** `cavans`矩阵尺寸类型 */
interface CavansRect {
/** 生成的图片宽度 */
width: number
/** 生成的图片高度 */
height: number
/**
* 边框圆角
* - 当`width === height`,`borderRadius = width / 2`或者`borderRadius = height / 2`就会变成一个圆形
*/
borderRadius?: number
/** 边框厚度 */
borderWidth?: number
/** 边框颜色 */
borderColor?: string
}
interface CavansImg extends CavansPosition, Omit<CavansRect, "borderWidth" | "borderColor"> {
type: "img"
/**
* 生成图片的路径
*
* - 网络图片地址,前提是这个图片可以跨域请求,微信小程序端需要配置`request`域名白名单
* - (仅限H5端生效)本地相对路径地址
* - (仅限H5端生效)`base64`图片编码,例如:`data:image/jpge;base64,xxxxxxxx`
*/
src: string
}
interface CavansBox extends CavansRect, CavansPosition {
type: "box"
/** 容器背景颜色 */
backgroundColor: string
}
interface CavansText extends CavansPosition {
type: "text"
/** 文字内容 */
text: string
/** 字体大小 */
fontSize: number
/** 字体颜色 */
color: string
// /** 指定字体的宽度,超过会被挤压 */
// width?: number
// /** 与`css`的`font-family`行为一致 */
// fontFamily?: string
/**
* 与`css`的`text-align`行为一致
* [参考](https://uniapp.dcloud.io/api/canvas/CanvasContext?id=canvascontextsettextalign)
* - 默认`"left"`
*/
textAlign?: "left" | "center" | "right"
/**
* 用于设置文字的水平对齐
* [参考](https://uniapp.dcloud.io/api/canvas/CanvasContext?id=canvascontextsettextbaseline)
* - 默认:`normal`
*/
textBaseline?: "top" | "bottom" | "middle" | "normal"
}
interface CavansFail {
/** 错误信息 */
errMsg: string
/**
* 错误类型
* - `export`: canvas导出图片路径错误
* - `load`: 图片加载失败错误
*/
type: "export" | "load"
/** 图片加载失败时携带的对象 */
info?: CavansImg
}
interface CavansCreaterParams {
/**
* `cavans`节点`id`
* @example
* ```html
* <cavans id="xxx" canvas-id="xxx"></cavans>
* ```
*/
cavansId: string
/** `cavans`整体宽度 */
width: number
/** `cavans`整体高度 */
height: number
/** 生成的内容列表 */
list: Array<CavansImg | CavansBox | CavansText>
/**
* 生成的图片类型
* - 默认`"png"`
*/
fileType?: "jpg" | "png"
/** 成功回调 */
success?: (res: UniApp.CanvasToTempFilePathRes) => void
/** 图片加载失败回调 */
fail?: (error: CavansFail) => void
}
最后看下调用的样子
cavansCreater({
cavansId: "the-cavans",
width: 300,
height: 500
list: [
{
type: "box",
width: 40,
height: 40,
backgroundColor: "#07c160",
borderRadius: 1000,
borderColor: "orange",
borderWidth: 10,
left: 20,
top: 20,
zIndex: 10,
},
{
type: "box",
width: 40,
height: 40,
backgroundColor: "#07c160",
borderRadius: 1000,
borderColor: "orange",
borderWidth: 10,
right: 20,
top: 20,
zIndex: 10,
},
{
type: "box",
width: 40,
height: 40,
backgroundColor: "orange",
borderRadius: 1000,
borderColor: "#07c160",
borderWidth: 10,
left: 20,
bottom: 20,
zIndex: 10,
},
{
type: "box",
width: 40,
height: 40,
backgroundColor: "orange",
borderRadius: 1000,
borderColor: "#07c160",
borderWidth: 10,
right: 20,
bottom: 20,
zIndex: 10,
},
{
type: "box",
width: 300,
height: 500,
backgroundColor: "#eee",
borderRadius: 60
},
{
type: "img",
src: "https://muse-ui.org/img/img3.6e264e66.png",
// src: "../static/logo.png",
width: 300,
height: 217,
// borderRadius: 100
},
{
type: "img",
src: "https://game.gtimg.cn/images/lol/act/img/champion/Talon.png",
// src: "../static/logo.png",
width: 60,
height: 60,
borderRadius: 10,
bottom: 50,
left: 50,
// zIndex: 12
},
{
type: "img",
src: "https://game.gtimg.cn/images/lol/act/img/champion/Zed.png",
// src: "../static/logo.png",
width: 60,
height: 60,
borderRadius: 50,
bottom: 50,
right: 50,
// zIndex: 12
},
{
type: "text",
text: "向右对齐的文字",
color: "green",
textAlign: "right",
fontSize: 16,
top: this.cavansSize.height / 2,
right: 10,
zIndex: 20,
},
{
type: "text",
text: "底部居中文字",
fontSize: 16,
color: "orange",
textAlign: "center",
textBaseline: "middle",
bottom: 20,
left: this.cavansSize.width / 2
}
],
success(res) {
console.log("生成的图片信息 >>", res);
},
fail(err) {
console.log("错误信息 >>", err);
}
});
基础几何图形的绘制
配置好传参字段,最后就是实现绘制的步骤了;这里不放代码,只说思路:
- 首先是在方法里面拿到配置列表
list
后,先根据zIndex
去排序一次,这样就实现了层级的概念; - 然后就是分别对【3】种几何图形的绘制,分别封装成三个方法,然后数组遍历逐个给它绘制出来,最后生成本地路径导出即可;
像普通盒子需要圆角和边框就要用三角函数的计算裁剪出来,坐标的计算等方法也不展开细说了,因为都是比较精简的就没啥好说,具体看代码。
需要一提的是:微信小程序中,图片的加载和H5的图片加载是不同的,这个问题也是摸索了不少时间,所以代码里面条件编译了两种处理图片的方法,在设置图片时,注意看代码提示的注释即可,问题不大