聊聊生成水印原理與插件編寫
本文轉(zhuǎn)載自微信公眾號(hào)「前端Sharing」,作者前端Sharing。轉(zhuǎn)載本文請(qǐng)聯(lián)系前端Sharing公眾號(hào)。
一 前言
今天分享一個(gè)小程序生成水印的小技巧——canvas繪制背景圖,接下來我會(huì)詳細(xì)介紹繪制的細(xì)節(jié)。希望開發(fā)過微信小程序的同學(xué)可以把文章收藏起來,這樣如果以后遇到類似的需求,可以翻出來作為參考。
本文的插件同樣適用于Taro,uniapp,原生等構(gòu)建的小程序項(xiàng)目,項(xiàng)目demo是采用Taro-Vue構(gòu)建的。
我們先來看看demo效果。
二 原理實(shí)現(xiàn)
canvas繪制背景圖這個(gè)方案原理本質(zhì)上是非常簡單的,就如把大象放冰箱一共分成三步那樣簡單????。
- 第一步冰箱門打開,因?yàn)檫@個(gè)功能是前端實(shí)現(xiàn)的,而且是canvas畫出來的,所以我們需要海報(bào)的基礎(chǔ)配置,比如canvas海報(bào)寬高,文字內(nèi)容,文字顏色,海報(bào)文字的旋轉(zhuǎn)角度等。
- 第二步把大象??放進(jìn)去,canvas得到配置,繪制水印底圖。
那么問題來了,我們繪制的底圖是整張水印底圖嗎?
答案是否定的。我們只需要畫一個(gè)模版圖片就可以了,如下圖所示:
但是為了讓水印填充整個(gè)手機(jī)屏幕,我們需要將水印圖片作為背景圖片,然后設(shè)置background-repeat:repeat;就可以了。
- 第三步把冰箱門關(guān)上,我們通過canvas生成的圖片,將圖片填充整個(gè)屏幕就可以了。
三 細(xì)節(jié)實(shí)現(xiàn)
demo樣板
canvas模版接下來我們開始實(shí)現(xiàn)具體的細(xì)節(jié),首先我們頁面放入一個(gè)
- <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>
- 這里有一點(diǎn)值得注意,就是我們?cè)O(shè)置的type = '2d'最好不要用canvasId方式來操作canvas,包括獲取canvas實(shí)例,調(diào)用canvasToTempFilePathapi等,不然可能會(huì)失效。這里采用的是通過id方式,來獲取canvas實(shí)例的。
- 當(dāng)我們繪制完成后,隱藏canvas,將view容器設(shè)置背景圖片,背景圖片就是canvas繪制形成的圖片。
- canvas寬度和高度是根據(jù)canvas的配置項(xiàng)添加的,所以我們要?jiǎng)討B(tài)用style屬性設(shè)置寬高。
配置項(xiàng)
- export default {
- /* 前端生成水印 */
- name:"MakeWaterMark",
- data(){
- return {
- isShow:true,
- url:'',
- settings: {
- 'id': 'waterMark', /* canvas id */
- 'content': '我不是外星人', /* 水印內(nèi)容 */
- 'width': 200, /* 圖片/canvas 寬度 */
- 'height': 200, /* 圖片/canvas 高度 */
- 'rotate': -45, /* 水印文案旋轉(zhuǎn)角度*/
- 'left':60, /* 水印文案相對(duì)圖片水平偏移量 */
- 'top':80, /* 水印文案相對(duì)圖片垂直偏移量 */
- '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 ;
- }
- 給外層容器設(shè)置樣式,能夠讓水印圖片平鋪整個(gè)頁面。
插件核心代碼
插件的核心功能就是生成水印圖片,除此之外還要滿足兩個(gè)要求:
- 插件本身和頁面/組件低耦合。插件本身可以在任何組件中使用。
- 插件不受構(gòu)建平臺(tái)的限制,就是既能在原生微信小程序中使用,也能在Taro,uniapp等構(gòu)建工具中使用。
接下來我們看一下具體細(xì)節(jié):
- 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 配置項(xiàng)
- */
- function makeWatermark(options){
- if(!wx) return null
- return new MarkwaterMark(options)
- }
- module.exports = makeWatermark
核心功能流程分析:
- 第一步:暴露makeWatermark接口,可以實(shí)例化一個(gè)MarkwaterMark對(duì)象,實(shí)例化過程中本身先進(jìn)行初始化配置,包裹一層Promise用于創(chuàng)建圖片。由于canvas操作中,很多方法都是異步的,所以用 createPromise 方法代理一層Promise。
- 第二步:MarkwaterMark對(duì)象上,有make方法,會(huì)獲取canvas實(shí)例,然后設(shè)置canvas畫布的寬高和縮放比,繪制水印canvas。
- 第三步:將canvas通過canvasToTempFilePath接口把canvas畫布轉(zhuǎn)化成臨時(shí)圖片,并把臨時(shí)圖片路徑通過callback形式,傳遞給業(yè)務(wù)組件或者頁面。
插件使用
在業(yè)務(wù)組件(上述demo)中,我們就可以使用上述插件了。具體參考如下:
- mounted(){
- Taro.nextTick(()=>{
- /* 創(chuàng)建一個(gè) makeWatermark 對(duì)象 */
- const marker = makeWatermark(this.settings)
- /* 調(diào)用make,生成圖片 */
- marker.make((url)=>{
- /* url 為臨時(shí)路徑 */
- const fileManage = Taro.getFileSystemManager()
- let base64 = 'data:image/jpg;base64,' + fileManage.readFileSync(url,'base64');
- this.url = base64
- this.isShow = false
- })
- })
- },
重要細(xì)節(jié):
這里還有一個(gè)比較重要細(xì)節(jié)就是,小程序中背景圖片一般都是網(wǎng)絡(luò)圖片或者base64圖片,對(duì)于臨時(shí)路徑的圖片在真機(jī)上是不顯示的。為了解決這個(gè)問題,我們需要把臨時(shí)圖片轉(zhuǎn)換成base64格式圖片。
- 通過 getFileSystemManager 以 base64方式訪問剛生成的臨時(shí)圖片,然后返回base64格式,接下來就可以把 base64 圖片設(shè)置為背景圖片了。
效果:
大功告成!!!
四 總結(jié)
通過本文我們學(xué)習(xí)了微信小程序生成水印的方式和流程。還有一些開發(fā)中的細(xì)節(jié)問題。感興趣的同學(xué)可以收藏起來,以備不時(shí)之需。