「微信小程序」生成水印原理与插件编写

10,138 阅读3分钟

一 前言

今天分享一个小程序生成水印的小技巧——canvas绘制背景图,接下来我会详细介绍绘制的细节。希望开发过微信小程序的同学可以把文章收藏起来,这样如果以后遇到类似的需求,可以翻出来作为参考。

本文的插件同样适用于Taro,uniapp,原生等构建的小程序项目,项目demo是采用Taro-Vue构建的。

我们先来看看demo效果。

WechatIMG157.jpeg

二 原理实现

canvas绘制背景图这个方案原理本质上是非常简单的,就如把大象放冰箱一共分成三步那样简单😂😂。

  • 第一步冰箱门打开,因为这个功能是前端实现的,而且是canvas画出来的,所以我们需要海报的基础配置,比如canvas海报宽高文字内容文字颜色海报文字的旋转角度等。

  • 第二步把大象🐘放进去,canvas得到配置,绘制水印底图。

那么问题来了,我们绘制的底图是整张水印底图吗?

答案是否定的。我们只需要画一个模版图片就可以了,如下图所示:

111.jpg

但是为了让水印填充整个手机屏幕,我们需要将水印图片作为背景图片,然后设置background-repeat:repeat;就可以了。

222.jpg

  • 第三步把冰箱门关上,我们通过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实例,调用canvasToTempFilePathapi等,不然可能会失效。这里采用的是通过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 ;
}
  • 给外层容器设置样式,能够让水印图片平铺整个页面。

插件核心代码

插件的核心功能就是生成水印图片,除此之外还要满足两个要求:

  • 插件本身和页面/组件低耦合。插件本身可以在任何组件中使用。
  • 插件不受构建平台的限制,就是既能在原生微信小程序中使用,也能在Tarouniapp等构建工具中使用。

接下来我们看一下具体细节:

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格式图片

  • 通过 getFileSystemManagerbase64方式访问刚生成的临时图片,然后返回base64格式,接下来就可以把 base64 图片设置为背景图片了。

效果:

WechatIMG157.jpeg

大功告成!!!

四 总结

通过本文我们学习了微信小程序生成水印的方式和流程。还有一些开发中的细节问题。感兴趣的同学可以收藏起来,以备不时之需。

我写了一本深入系统学习React的小册

为了让大家系统的学习React,进阶React,笔者最近写了一本《React进阶实践指南》的小册,本小册从基础进阶篇优化进阶篇原理进阶篇生态进阶篇实践进阶篇,五个方向详细探讨 React 使用指南 和 原理介绍。

  • 基础进阶篇里,将重新认识react中 state,props,ref,context等模块,详解其基本使用和高阶玩法。

  • 优化进阶篇里,将讲解 React性能调优和细节处理,让React写的更优雅。

  • 原理进阶篇里,将针对React几个核心模块原理进行阐述,一次性搞定面试遇到React原理问题。

  • 生态进阶篇里,将重温React重点生态的用法,从原理角度分析内部运行的机制。

  • 实践进阶篇里,将串联前几个模块,进行强化实践。

至于小册为什么叫进阶实践指南,因为在讲解进阶玩法的同时,也包含了很多实践的小demo。还有一些面试中的问答环节,让读者从面试上脱颖而出。

目前以及完成前22章节,小册马上上线啦,想要进阶React的可以看过来,关注小册最新动态。

最后, 送人玫瑰,手留余香,觉得有收获的朋友可以给笔者点赞,关注一波 ,陆续更新前端超硬核文章。