一 前言
今天分享一个小程序生成水印的小技巧——canvas
绘制背景图,接下来我会详细介绍绘制的细节。希望开发过微信小程序的同学可以把文章收藏起来,这样如果以后遇到类似的需求,可以翻出来作为参考。
本文的插件同样适用于Taro,uniapp,原生等构建的小程序项目,项目demo
是采用Taro-Vue
构建的。
我们先来看看demo效果。
二 原理实现
canvas绘制背景图这个方案原理本质上是非常简单的,就如把大象放冰箱一共分成三步那样简单😂😂。
第一步冰箱门打开,因为这个功能是前端实现的,而且是canvas画出来的,所以我们需要海报的基础配置,比如canvas海报宽高,文字内容,文字颜色,海报文字的旋转角度等。
第二步把大象🐘放进去,canvas得到配置,绘制水印底图。
那么问题来了,我们绘制的底图是整张水印底图吗?
答案是否定的。我们只需要画一个模版图片就可以了,如下图所示:
但是为了让水印填充整个手机屏幕,我们需要将水印图片作为背景图片,然后设置background-repeat:repeat;
就可以了。
- 第三步把冰箱门关上,我们通过canvas生成的图片,将图片填充整个屏幕就可以了。
三 细节实现
demo样板
canvas模版
接下来我们开始实现具体的细节,首先我们页面放入一个<canvas>
。
<view
class="markBox"
:style="{
backgroundImage: `url(${url})` }"
>
<canvas
v-show="isShow"
:id="settings.id"
type="2d"
:class="settings.id"
:style="{
width:settings.width + 'px' , height : settings.height + 'px' }"
/>
</view>
这里有一点值得注意,就是我们设置的
type = '2d'
最好不要用canvasId
方式来操作canvas
,包括获取canvas实例,调用canvasToTempFilePath
api等,不然可能会失效。这里采用的是通过id方式,来获取canvas实例的。当我们绘制完成后,隐藏
canvas
,将view容器设置背景图片,背景图片就是canvas绘制形成的图片。canvas
宽度和高度是根据canvas
的配置项添加的,所以我们要动态用style
属性设置宽高。
配置项
export default {
/* 前端生成水印 */
name:"MakeWaterMark",
data(){
return {
isShow:true,
url:'',
settings: {
'id': 'waterMark', /* canvas id */
'content': '我不是外星人', /* 水印内容 */
'width': 200, /* 图片/canvas 宽度 */
'height': 200, /* 图片/canvas 高度 */
'rotate': -45, /* 水印文案旋转角度*/
'left':60, /* 水印文案相对图片水平偏移量 */
'top':80, /* 水印文案相对图片垂直偏移量 */
'fontSize': '15px', /* 水印文字大小 */
'fontFamily': 'Microsoft JhengHei', /* 水印文字样式 */
'bg': '#fff', /* 图片背景颜色 */
'color': '#ccc', /* 水印文字颜色 */
'fontWeight': 'normal', /* 水印文字宽度 */
}
}
},
}
样式处理
.markBox{
position: fixed;
left:0;
right:0;
bottom: 0;
top:0;
background-repeat:repeat ;
}
- 给外层容器设置样式,能够让水印图片平铺整个页面。
插件核心代码
插件的核心功能就是生成水印图片,除此之外还要满足两个要求:
- 插件本身和页面/组件低耦合。插件本身可以在任何组件中使用。
- 插件不受构建平台的限制,就是既能在原生微信小程序中使用,也能在
Taro
,uniapp
等构建工具中使用。
接下来我们看一下具体细节:
function createPromise(callback){
return new Promise((resolve,reject)=>{
callback(resolve,reject)
})
}
/**
* 制作水印图片
*/
class MarkwaterMark{
constructor(options){
/* 初始化配置 */
this.platform = null
this.pixelRatio = null
this.context = null
this.options = options
this.ready = createPromise(this._init.bind(this))
/* 生成组件方法 */
this.make = (cb) => {
if(this.context){
this._markPicture(cb)
}else{
this.ready.then(()=>{
this.context && this._markPicture(cb)
})
}
}
}
/* 初始化方法 */
_init(next,fail){
const {
platform , pixelRatio } = wx.getSystemInfoSync()
this.platform = platform
this.pixelRatio = pixelRatio
const query = wx.createSelectorQuery()
query.select('#' + this.options.id ).fields({
node: true,
size: true
}).exec((res)=>{
let {
node,
} = res[0] || {
}
if (!node) return fail && fail()
this.context = node.getContext('2d')
this.node = node
next()
})
}
/* 制作水印图片 */
_markPicture(cb){
const {
width , height , bg ,color ,fontSize, fontFamily,fontWeight ,content , left, top ,rotate } = this.options
this.node.width = (width || 200) * this.pixelRatio
this.node.height =( height || 200) * this.pixelRatio
this.context.scale(this.pixelRatio,this.pixelRatio)
this.context.fillStyle = bg || '#fff'
this.context.fillRect(0, 0, width, height)
this.context.fillStyle = color || '#000'
this.context.save()
this.context.translate(left,top)
this.context.rotate(Math.PI * rotate / 180 )
this.context.font = `${fontWeight} 400 ${fontSize} ${
fontFamily}`
this.context.fillText(content, 0, 0)
this.context.restore()
this._savePicture(cb)
}
/* 生成图片 */
_savePicture(cb){
const {
width , height } = this.options
wx.canvasToTempFilePath({
x:0,
y:0,
width,
height,
destWidth:width*1,
destHeight:height*1,
canvas:this.node,
success:function(res){
cb && cb(res.tempFilePath)
}
})
}
}
/**
*
* @param {*} options 配置项
*/
function makeWatermark(options){
if(!wx) return null
return new MarkwaterMark(options)
}
module.exports = makeWatermark
核心功能流程分析:
第一步:暴露makeWatermark接口,可以实例化一个
MarkwaterMark
对象,实例化过程中本身先进行初识化配置,包裹一层Promise用于创建图片。由于canvas操作中,很多方法都是异步的,所以用 createPromise 方法代理一层Promise。第二步:MarkwaterMark对象上,有
make
方法,会获取canvas实例,然后设置canvas画布的宽高和缩放比,绘制水印canvas。
- 第三步:将
canvas
通过canvasToTempFilePath
接口把canvas画布转化成临时图片,并把临时图片路径通过callback形式,传递给业务组件或者页面。
插件使用
在业务组件(上述demo)中,我们就可以使用上述插件了。具体参考如下:
mounted(){
Taro.nextTick(()=>{
/* 创建一个 makeWatermark 对象 */
const marker = makeWatermark(this.settings)
/* 调用make,生成图片 */
marker.make((url)=>{
/* url 为临时路径 */
const fileManage = Taro.getFileSystemManager()
let base64 = 'data:image/jpg;base64,' + fileManage.readFileSync(url,'base64');
this.url = base64
this.isShow = false
})
})
},
重要细节:
这里还有一个比较重要细节就是,小程序中背景图片一般都是网络图片或者base64图片,对于临时路径的图片在真机上是不显示的。为了解决这个问题,我们需要把临时图片转换成base64格式图片。
- 通过
getFileSystemManager
以base64
方式访问刚生成的临时图片,然后返回base64格式,接下来就可以把 base64 图片设置为背景图片了。
效果:
大功告成!!!
四 总结
通过本文我们学习了微信小程序生成水印的方式和流程。还有一些开发中的细节问题。感兴趣的同学可以收藏起来,以备不时之需。
最后, 送人玫瑰,手留余香,觉得有收获的朋友可以给笔者点赞,关注一波 ,陆续更新前端超硬核文章。