需求:需要根据背景图片和用户的微信的头像和昵称,生成一张海报图,并保存到手机相册。

首先在画布上绘制海报背景图片,然后再绘制头像、昵称和挑战成功的信息。

代码如下:

       getImageInfo() {return new Promise((resolve) => {uni.getImageInfo({src: this.sharePosterImage,success: (image) => {console.log(image, "海报图");this.$set(this, "canvasWidth", image.width + "px");this.$set(this, "canvasHeight", image.height + "px");resolve(true);},});});},// 获取用户详情initUserData() {const _this = this;uni.getUserProfile({desc: "用于完善会员资料",success: (res) => {uni.authorize({scope: "scope.writePhotosAlbum",success: async () => {wx.showLoading({title: "加载中",});this.headImgUrl = res.userInfo.avatarUrl;this.headName = res.userInfo.nickName;await _this.getImageInfo();const posterUrl = await _this.initShareImage();_this.posterImgUrl = posterUrl;_this.posterShow = true;wx.hideLoading();uni.saveImageToPhotosAlbum({filePath: posterUrl,success() {_this.shareBtnShake = false;uni.hideToast();_this.successToast = true;},fail() {_this.shareBtnShake = false;uni.showToast({title: "保存失败",icon: "error",});},});},fail: () => {wx.hideLoading();_this.shareBtnShake = false;uni.showModal({title: "提示",content: "您未授权OPPO客服小程序保存图片或视频到相册,是否现在去设置?",showCancel: true,success: ({ confirm }) => {if (confirm) {_this.$authUtil.openSetting();}},});},});},fail: (error) => {_this.shareBtnShake = false;console.log(error);},});},initShareImage() {return new Promise((resolve) => {this.$nextTick(async () => {const vwRate = this.canvasWidth.replace("px", "");const vhRate = this.canvasHeight.replace("px", "");// 初始化await this.$refs.rCanvas.init({scale: "1",canvas_id: "rCanvas",canvas_width: this.canvasWidth.replace("px", ""),canvas_height: this.canvasHeight.replace("px", ""),hidden: "true",});// 画图await this.$refs.rCanvas.drawImage({url: this.sharePosterImage,x: 0,y: 0,w: this.canvasWidth.replace("px", ""),h: this.canvasHeight.replace("px", ""),}).catch(() => {});// 画头像await this.$refs.rCanvas.drawImage({url: this.headImgUrl,x: (18 / 360) * vwRate,y: (64 / 779) * vhRate,w: (40 / 360) * vwRate,h: (40 / 360) * vwRate,border_color: "#FFFFFF",is_radius: true,}).catch(() => {});// 画昵称await this.$refs.rCanvas.drawText({text: this.headName,x: (74 / 360) * vwRate,y: (76 / 779) * vhRate,font_color: "#000000",font_size: (12 / 360) * vwRate,font_weight: "400",line_height: (17 / 360) * vwRate,text_align: "left",font_family: "PingFang SC",}).catch(() => {});// 画获奖矩形await this.$refs.rCanvas.fillRoundRect({x: (74 / 360) * vwRate,y: (85 / 779) * vhRate,w: (268 / 360) * vwRate,h: (65 / 779) * vhRate,radius: (12 / 779) * vhRate,fill_color: "#FFFFFF",}).catch(() => {});await this.$refs.rCanvas.fillRoundRect({x: (132 / 360) * vwRate,y: (98 / 779) * vhRate,w: (48 / 360) * vwRate,h: (20 / 779) * vhRate,radius: (4 / 779) * vhRate,fill_color: "#FDECEE",}).catch(() => {});// 画文字await this.$refs.rCanvas.drawText({text: `我是第`,x: (86 / 360) * vwRate,y: (114 / 779) * vhRate,font_color: "#000000",font_size: (14 / 360) * vwRate,font_weight: "400",line_height: (20 / 360) * vwRate,text_align: "left",font_family: "PingFang SC",}).catch(() => {});const numberX = 156 - 4 * this.numWinning.toString().length;await this.$refs.rCanvas.drawText({text: this.numWinning,x: (numberX / 360) * vwRate,y: (114 / 779) * vhRate,font_color: "#EC3E50",font_size: (14 / 360) * vwRate,font_weight: "400",line_height: (20 / 360) * vwRate,text_align: "left",font_family: "PingFang SC",}).catch(() => {});await this.$refs.rCanvas.drawText({text: `位挑战成功,免费领取`,x: (184 / 360) * vwRate,y: (114 / 779) * vhRate,font_color: "#000000",font_size: (14 / 360) * vwRate,font_weight: "400",line_height: (20 / 360) * vwRate,text_align: "left",font_family: "PingFang SC",}).catch(() => {});await this.$refs.rCanvas.drawText({text: `了奖品。`,x: (86 / 360) * vwRate,y: (134 / 779) * vhRate,font_color: "#000000",font_size: (14 / 360) * vwRate,font_weight: "400",line_height: (20 / 360) * vwRate,text_align: "left",font_family: "PingFang SC",}).catch(() => {});// 生成海报await this.$refs.rCanvas.draw((res) => {resolve(res.tempFilePath);});});});},

rCanvas组件封装的都是canvas 2d绘图方法:

// -----------------------------rCanvas.vue内容
<template><view><viewclass="r-canvas-component":style="{ width: canvas_width / scale + 'px', height: canvas_height / scale + 'px' }":class="{ hidden: hidden }"><canvasv-if="canvas_id":id="canvas_id"class="r-canvas":canvas-id="canvas_id":style="{width: canvas_width + 'px',height: canvas_height + 'px',transform: `scale(${r_canvas_scale})`,}"></canvas></view></view>
</template><script>
import rCanvasJS from "./r-canvas.js";
export default {mixins: [rCanvasJS],
};
</script>
<style>
.r-canvas {transform-origin: 0 0;
}
.r-canvas-component {overflow: hidden;
}
.r-canvas-component.hidden {position: fixed;top: -5000upx;
}
</style>// ------------------------------r-canvas.js内容
export default{data(){return{system_info:{}, //system infocanvas_width:0, //canvas width pxcanvas_height:0, //canvas height pxctx:null, //canvas objectcanvas_id:null, //canvas idhidden:false,//Whether to hide canvasscale:1,//canvas scaler_canvas_scale:1,if_ctx:true}},methods:{/*** save r-canvas.vue object* @param {Object} that*/// saveThis(that){//     rCanvasThis = that// },/*** Draw round rect text* @param {Object} config* @param {Number} config.x x坐标* @param {Number} config.y y坐标* @param {Number} config.w 宽度* @param {Number} config.h 高度* @param {Number} config.radius 圆角弧度* @param {String} config.fill_color 矩形颜色*/fillRoundRect(config) {return new Promise((resolve,reject)=>{let x = this.compatibilitySize(parseFloat(config.x)*this.scale)let y = this.compatibilitySize(parseFloat(config.y)*this.scale)let w = this.compatibilitySize(parseFloat(config.w)*this.scale)let h = this.compatibilitySize(parseFloat(config.h)*this.scale)let radius = config.radius?parseFloat(config.radius)*this.scale:10*this.scalelet fill_color = config.fill_color || "black"// The diameter of the circle must be less than the width and height of the rectangleif (2 * radius > w || 2 * radius > h) { reject("The diameter of the circle must be less than the width and height of the rectangle")return false; }this.ctx.save();this.ctx.translate(x, y);//  this.drawRoundRectPath({w: w, h: h, radius: radius});this.ctx.fillStyle = fill_colorthis.ctx.fill();this.ctx.restore();resolve()})},/*** (x1, y1) 第1个点的坐标* (x2, y2)    第2个点的坐标* (x3, y3)   第3个点的坐标* fill_color: 填充颜色*/drawTriRect(config) {return new Promise((resolve) => {this.ctx.beginPath();this.ctx.moveTo(config.x1, config.y1);this.ctx.lineTo(config.x2, config.y2);this.ctx.lineTo(config.x3, config.y3);this.ctx.fillStyle = config.fill_color;this.ctx.closePath();this.ctx.fill();resolve();});},/*** Draws the sides of a rounded rectangle* @param {Object} config* @param {Number} config.w 宽度* @param {Number} config.h 高度* @param {Number} config.radius 圆角弧度*/drawRoundRectPath(config) {this.ctx.beginPath(0);this.ctx.arc(config.w - config.radius, config.h - config.radius, config.radius, 0, Math.PI / 2);this.ctx.lineTo(config.radius, config.h);this.ctx.arc(config.radius, config.h - config.radius, config.radius, Math.PI / 2, Math.PI);this.ctx.lineTo(0, config.radius);this.ctx.arc(config.radius, config.radius, config.radius, Math.PI, Math.PI * 3 / 2);this.ctx.lineTo(config.w - config.radius, 0);this.ctx.arc(config.w - config.radius, config.radius, config.radius, Math.PI * 3 / 2, Math.PI * 2);this.ctx.lineTo(config.w, config.h - config.radius);this.ctx.closePath();},/*** Draw special Text,line wrapping is not supported* @param {Object} config* @param {String} config.text 文字* @param {Number} config.x x坐标* @param {Number} config.y y坐标* @param {String} config.font_color 文字颜色* @param {String} config.font_family 文字字体* @param {Number} config.font_size 文字大小(px)*/drawSpecialText(config_arr){return new Promise(async (resolve,reject)=>{if(config_arr && config_arr.length>0){for(let i in config_arr){if(i != 0){for(let a in config_arr){if(a < i){// 先处理font-size才能知道字体宽度let font_size = config_arr[a].font_size?parseFloat(config_arr[a].font_size)*this.scale:20*this.scalethis.ctx.setFontSize(this.compatibilitySize(font_size))config_arr[i].x = parseFloat(config_arr[i].x)*this.scale + this.ctx.measureText(config_arr[a].text).width}else{break;}}}await this.drawText(config_arr[i])}resolve()}else{reject("The length of config arr is less than 0")return;}})},/*** array delete empty* @param {Object} arr*/arrDeleteEmpty(arr){let newArr = []for(let i in arr){if(arr[i]){newArr.push(arr[i])}}return newArr},/*** Draw Text,support line* @param {Object} config* @param {String} config.text 文字* @param {Number} config.max_width 文字最大宽度(大于宽度自动换行)* @param {Number} config.line_height 文字上下行间距* @param {Number} config.x x坐标* @param {Number} config.y y坐标* @param {String} config.font_color 文字颜色* @param {String} config.font_family 文字字体 默认值:Arial* @param {String} config.text_align 文字对齐方式(left/center/right)* @param {Number} config.font_size 文字大小(px)* @param {Boolean} config.line_through_height 中划线大小* @param {Boolean} config.line_through_color 中划线颜色* @param {String} config.font_style 规定文字样式* @param {String} config.font_variant 规定字体变体* @param {String} config.font_weight 规定字体粗细* @param {String} config.line_through_cap 线末端类型* @param {String} config.line_clamp 最大行数* @param {String} config.line_clamp_hint 超过line_clamp后,尾部显示的自定义标识 如 ...* @param {String} config.is_line_break 是否开启换行符换行* */drawText(config,configuration = {}){configuration['line_num'] = configuration.line_num?configuration.line_num:0configuration['text_width'] = configuration.text_width?configuration.text_width:0return new Promise(async (resolve,reject)=>{if(config.text){let draw_width = 0,draw_height = 0,draw_x = config.x,draw_y = config.ylet font_size = config.font_size?(parseFloat(config.font_size)*this.scale):(20*this.scale)let font_color = config.font_color || "#000"let font_family = config.font_family || "Arial"let line_height = config.line_height || config.font_size || 20let text_align = config.text_align || "left"let font_weight = config.font_weight || "normal"let font_variant = config.font_variant || "normal"let font_style = config.font_style || "normal"let line_clamp_hint = config.line_clamp_hint || '...'let lineBreakJoinText = ""let max_width = config.max_width?parseFloat(config.max_width)*this.scale:0// checkout is line breakif(config.is_line_break){let splitTextArr = config.text.split(/[\n]/g)if(splitTextArr && splitTextArr.length > 0){let newSplitTextArr = this.arrDeleteEmpty(splitTextArr)if(newSplitTextArr && newSplitTextArr.length > 0){lineBreakJoinText = newSplitTextArr.slice(1).join("\n")config.text = newSplitTextArr[0]}else{reject("Text cannot be empty:103")return}}else{reject("Text cannot be empty:102")return}}this.ctx.setFillStyle(font_color) // colorthis.ctx.textAlign = text_align;this.ctx.font = `${font_style} ${font_variant} ${font_weight} ${parseInt(font_size)}px ${font_family}`if(configuration.text_width >= this.ctx.measureText(config.text).width){draw_width = configuration.text_width}else if(max_width > 0){draw_width = max_width < this.ctx.measureText(config.text).width ? this.resetCompatibilitySize(max_width) : this.resetCompatibilitySize(this.ctx.measureText(config.text).width)}else{draw_width = this.resetCompatibilitySize(this.ctx.measureText(config.text).width)}configuration.text_width = draw_widthif( max_width && this.compatibilitySize(this.ctx.measureText(config.text).width) > this.compatibilitySize(max_width)){let current_text = ""let text_arr = config.text.split("")for(let i in text_arr){if( this.compatibilitySize(this.ctx.measureText(current_text+text_arr[i]).width) > this.compatibilitySize(max_width) ){// Hyphenation that is greater than the drawable width continues to drawif(config.line_clamp && parseInt(config.line_clamp) == 1){// Subtracting the current_text tail width from the line_clamp_hint widthlet current_text_arr = current_text.split('')let json_current_text = ''while(true){current_text_arr = current_text_arr.slice(1)json_current_text = current_text_arr.join('')if(this.compatibilitySize(this.ctx.measureText(json_current_text).width) <= this.compatibilitySize(this.ctx.measureText(line_clamp_hint).width)){current_text = current_text.replace(json_current_text,'')break;}}configuration.line_num += 1this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font sizethis.ctx.fillText(current_text + line_clamp_hint, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));}else{configuration.line_num += 1this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font sizethis.ctx.fillText(current_text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));config.text = text_arr.slice(i).join("")config.y = config.y + line_heightif(config.line_clamp){config.line_clamp = parseInt(config.line_clamp) - 1}await this.drawText(config,configuration)}break;}else{current_text = current_text+text_arr[i]}}}else{if(config.line_through_height){let x = parseFloat(config.x)*this.scalelet wlet y = parseFloat(config.y)*this.scale - (font_size / 2.6) if(text_align == "left"){w = this.ctx.measureText(config.text).width/1.1 + parseFloat(config.x)*this.scale}else if(text_align == "right"){w = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width/1.1}else if(text_align == "center"){x = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width / 1.1 / 2w = parseFloat(config.x)*this.scale + this.ctx.measureText(config.text).width / 1.1 / 2}this.drawLineTo({x:x,y:y,w:w,h:y,line_width:config.line_through_height,line_color:config.line_through_color,line_cap:config.line_through_cap})}configuration.line_num += 1this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font sizethis.ctx.fillText(config.text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));if(config.line_clamp){config.line_clamp = parseInt(config.line_clamp) - 1}}if(lineBreakJoinText){await this.drawText({...config,text:lineBreakJoinText,y:config.y + line_height},configuration)}draw_height = config.font_size * configuration.line_numdraw_width = configuration.text_widthresolve({draw_width,draw_height,draw_x,draw_y})}else{reject("Text cannot be empty:101")}})},/*** Draw Line* @param {Object} config* @param {Object} config.x x坐标* @param {Object} config.y y坐标* @param {Object} config.w 线的宽度* @param {Object} config.h 线的高度* @param {Object} config.line_width 线的宽度* @param {Object} config.line_color 线条颜色*/drawLineTo(config){let x = this.compatibilitySize(config.x)let y = this.compatibilitySize(config.y)let w = this.compatibilitySize(config.w)let h = this.compatibilitySize(config.h)let line_width = config.line_width?parseFloat(config.line_width)*this.scale:1*this.scalelet line_color = config.line_color || "black"let line_cap = config.line_cap || "butt"this.ctx.beginPath()this.ctx.lineCap = line_capthis.ctx.lineWidth = line_widththis.ctx.strokeStyle = line_colorthis.ctx.moveTo(x,y)this.ctx.lineTo(w,h)this.ctx.stroke()},/** * Compatibility px* @param {Object} size*/compatibilitySize(size) {let canvasSize = (parseFloat(size) / 750) * this.system_info.windowWidthcanvasSize = parseFloat(canvasSize * 2)return canvasSize},/*** Restore compatibility px* @param {Object} size*/resetCompatibilitySize(size) {let canvasSize = (parseFloat(size/2)/this.system_info.windowWidth) * 750return canvasSize},/*** Init canvas*/init(config){return new Promise(async (resolve,reject)=>{if(!config.canvas_id){reject("Canvas ID cannot be empty, please refer to the usage example")return;}this.hidden = config.hiddenthis.canvas_id = config.canvas_idlet system_info = await uni.getSystemInfoSync()this.system_info = system_infothis.scale = config.scale&&parseFloat(config.scale)>0?parseInt(config.scale):1this.canvas_width = (config.canvas_width ? this.compatibilitySize(config.canvas_width) : system_info.windowWidth) * this.scalethis.canvas_height = (config.canvas_height ? this.compatibilitySize(config.canvas_height) : system_info.windowHeight) * this.scale,this.r_canvas_scale = 1/this.scalethis.ctx = uni.createCanvasContext(this.canvas_id,this)this.setCanvasConfig({global_alpha:config.global_alpha?parseFloat(config.global_alpha):1,backgroundColor:config.background_color?config.background_color:"#fff"})resolve()})},/*** clear canvas all path*/clearCanvas(){return new Promise(async (resolve,reject)=>{if(!this.ctx){reject("canvas is not initialized:101")return}else{this.ctx.clearRect(0,0,parseFloat(this.canvas_width)*this.scale,parseFloat(this.canvas_height)*this.scale)await this.draw()resolve()}})},/*** Set canvas config* @param {Object} config*/setCanvasConfig(config){this.ctx.globalAlpha = config.global_alphathis.ctx.fillStyle = config.backgroundColorthis.ctx.fillRect(0, 0, parseFloat(this.canvas_width)*this.scale, parseFloat(this.canvas_height)*this.scale)},/*** Draw to filepath*/draw(callback){return new Promise((resolve,reject)=>{let stop = setTimeout(()=>{this.ctx.draw(false,setTimeout(()=>{uni.canvasToTempFilePath({canvasId: this.canvas_id,quality: 1,success: (res)=>{console.log('res99999',res)resolve(res)callback && callback(res)},fail:(err)=>{reject(JSON.stringify(err)|| "Failed to generate poster:101")}},this)},300))clearTimeout(stop)},300)})},/*** draw rect* @param {Number} config.x x坐标* @param {Number} config.y y坐标* @param {Number} config.w 图形宽度(px)* @param {Number} config.h 图形高度(px)* @param {Number} config.color 图形颜色* @param {Number} config.is_radius 是否开启圆图(1.1.6及以下版本废弃,请使用border_radius)* */drawRect(config){return new Promise(async (resolve,reject)=>{let color = config.color || 'white'config['color'] = colorthis.ctx.fillStyle = color;if(config.is_radius || config.border_radius){this.setNativeBorderRadius(config)this.ctx.fill()}else{this.ctx.fillRect(this.compatibilitySize(parseFloat(config.x)*this.scale),this.compatibilitySize(parseFloat(config.y)*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale))}resolve()})},/*** Draw image* @param {Object} config* @param {String} config.url 图片链接* @param {Number} config.x x坐标* @param {Number} config.y y坐标* @param {Number} config.w 图片宽度(px)* @param {Number} config.h 图片高度(px)* @param {Number} config.border_width 边大小* @param {Number} config.border_color 边颜色* @param {Number} config.is_radius 是否开启圆图(1.1.6及以下版本废弃,请使用border_radius)* @param {Number} config.border_radius 圆角弧度*/drawImage(config){return new Promise(async (resolve,reject)=>{if(config.url){let type = 0 // 1、network image  2、native image  3、base64 imagelet image_urllet reg = /^https?/ig;if(reg.test(config.url)){type = 1}else{if((config.url.indexOf("data:image/png;base64") != -1) || config.url.indexOf("data:image/jpeg;base64") != -1){type = 3}else{type = 2}}if(type == 1){// network imageawait this.downLoadNetworkFile(config.url).then(res=>{ // two functionimage_url = res}).catch(err=>{reject(err)return;})}else if(type == 2){// native imageconst imageInfoResult = await uni.getImageInfo({src: config.url});try{if(imageInfoResult.length <= 1){reject(imageInfoResult[0].errMsg + ':404')return}}catch(e){reject(e+':500')return}let base64 = await this.urlToBase64({url:imageInfoResult[1].path})// #ifdef MP-WEIXINawait this.base64ToNative({url:base64}).then(res=>{image_url = res}).catch(err=>{reject(JSON.stringify(err)+":501")return;})// #endif// #ifndef MP-WEIXINimage_url = base64// #endif}else if(type == 3){// #ifdef MP-WEIXINawait this.base64ToNative({url:config.url}).then(res=>{image_url = res}).catch(err=>{reject(JSON.stringify(err)+":500")return;})// #endif// #ifndef MP-WEIXINimage_url = config.url// #endif}else{reject("Other Type Errors:101")return}if(config.border_width){let border_radius = 0if(config.border_radius){let multiple = config.w / config.border_radiusborder_radius = (parseFloat(config.w) + parseFloat(config.border_width)) / multiple}// drawRectawait this.drawRect({x:parseFloat(config.x) - parseFloat(config.border_width)/2,y:parseFloat(config.y) - parseFloat(config.border_width)/2,w:parseFloat(config.w) + parseFloat(config.border_width),h:parseFloat(config.h) + parseFloat(config.border_width),color:config.border_color,border_radius:border_radius,border_width:config.border_width,is_radius:config.is_radius})}if(config.border_radius){this.setNativeBorderRadius(config)}else if(config.is_radius){//已废弃 is_radiusthis.ctx.save();this.ctx.beginPath();this.ctx.arc(this.compatibilitySize(parseFloat(config.x)*this.scale+parseFloat(config.w)*this.scale/2), this.compatibilitySize(parseFloat(config.y)*this.scale+parseFloat(config.h)*this.scale/2), this.compatibilitySize(parseFloat(config.w)*this.scale/2), 0, 2 * Math.PI, false)this.ctx.strokeStyle = "#FFFFFF"; // 设置绘制圆形边框的颜色this.ctx.clip();this.ctx.stroke();}await this.ctx.drawImage(image_url,this.compatibilitySize(parseFloat(config.x)*this.scale),this.compatibilitySize(parseFloat(config.y)*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale))this.ctx.restore() //Restore previously saved drawing contextresolve()}else{let err_msg = "Links cannot be empty:101"reject(err_msg)}})},/*** base64 to native available path* @param {Object} config*/base64ToNative(config){return new Promise((resolve,reject)=>{let fileName = new Date().getTime()var filePath = `${wx.env.USER_DATA_PATH}/${fileName}_rCanvas.png`wx.getFileSystemManager().writeFile({filePath: filePath,data: config.url.replace(/^data:\S+\/\S+;base64,/, ''),encoding: 'base64',success: function() {resolve(filePath)},fail: function(error) {reject(error)}})})},/*** native url to base64* @param {Object} config*/urlToBase64(config){return new Promise(async (resolve,reject)=>{if (typeof window != 'undefined') {await this.downLoadNetworkFile(config.url).then(res=>{ // two functionresolve(res)}).catch(err=>{reject(err)})}else if (typeof plus != 'undefined') {plus.io.resolveLocalFileSystemURL(config.url,(obj)=>{obj.file((file)=>{let fileReader = new plus.io.FileReader()fileReader.onload = (res)=>{resolve(res.target.result)}fileReader.onerror = (err)=>{reject(err)}fileReader.readAsDataURL(file)}, (err)=>{reject(err)})},(err)=>{reject(err)})}else if(typeof wx != 'undefined'){wx.getFileSystemManager().readFile({filePath: config.url,encoding: 'base64',success: function(res) {resolve('data:image/png;base64,' + res.data)},fail: function(error) {reject(error)}})}})},setNativeBorderRadius(config){let border_radius = config.border_radius?(parseFloat(config.border_radius)*this.scale):(20*this.scale)if ((parseFloat(config.w)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.w)*this.scale) / 2;if ((parseFloat(config.h)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.h)*this.scale) / 2;this.ctx.beginPath();this.ctx.moveTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + border_radius), this.compatibilitySize((parseFloat(config.y)*this.scale)));this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius));this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius));this.ctx.arcTo((this.compatibilitySize(parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius));this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius));this.ctx.closePath();this.ctx.strokeStyle = config.color || config.border_color; // 设置绘制边框的颜色this.ctx.stroke();this.ctx.save()this.ctx.clip();},/*** Download network file* @param {Object} url : download url*/downLoadNetworkFile(url){return new Promise((resolve,reject)=>{uni.downloadFile({url,success:(res)=>{if(res.statusCode == 200){resolve(res.tempFilePath)}else{reject("Download Image Fail:102")}},fail:(err)=>{reject("Download Image Fail:101")}})})},/*** Save image to natice* @param {Object} filePath : native imageUrl*/saveImage(filePath){return new Promise((resolve,reject)=>{if(!filePath){reject("FilePath cannot be null:101")return;}// #ifdef H5var createA = document.createElement("a");createA.download = filePath;createA.href = filePath;document.body.appendChild(createA);createA.click();createA.remove();resolve()// #endif// #ifndef H5uni.saveImageToPhotosAlbum({filePath: filePath,success:(res)=>{resolve(res)}, fail:(err)=>{reject(err)}})// #endif})}}
}

其中踩的坑需要总结一下:

1.创建 canvas 的绘图上下文CanvasContext 对象

在微信开发文档中的方法是wx.createCanvasContext,但是注意: 从基础库2.9.0开始已经停止维护了。所以这个接口不再继续使用。

创建canvasContext对象需要使用一下方法:

                    const query = wx.createSelectorQuery();query.select("#shareImage") // canvas的id.fields({node: true,size: true,}).exec((res) => {let canvas = res[0].node;const ctx = canvas.getContext("2d");});

2.下载网络图片生成背景图时需要注意使用canvas.createImage()创建图片对象,并且在image.onload()中绘制图片ctx.drawImage()

 const bgImg = canvas.createImage();bgImg.src = image.path;bgImg.onload = () => {ctx.drawImage(bgImg, 0, 0, image.width, image.height);}

3.这个真是个大坑啊.......canvas.createImage()在个别机型(Iphone12)上创建的是空对象,但是在微信开发者工具或者别的机型上就是可以正常使用的。所以解决方法就是使用旧版API,同时旧版api和新版canvas 2d不能同时混用。

微信小程序,使用canvas画图生成海报并保存手机相册相关推荐

  1. 微信小程序-运用painter插件生成海报分享朋友圈--比canvas好用

    微信小程序-运用painter插件生成海报–比canvas好用 先放插件地址:https://github.com/Kujiale-Mobile/Painter 还有个可视化把海报生成代码的地址:ht ...

  2. 【图文保存为图片并下载到相册】海报 h5, 微信和 ios 不支持 和 用uniapp 微信小程序 使用canvas把页面转为图片保存到手机

    第一种,[图文保存为图片并下载到相册] h5, 微信和 ios 不支持 1,安装html2canvas npm install html2canvas --save 2,在需要的页面引入 import ...

  3. 微信小程序使用canvas画图并保存到手机相册踩坑总结

    接到个项目做微信小程序的,需要将手机中的页面保存到手机相册中,效果图如下 首先想到的就是利用canvas画图然后在保存到相册,看起来很简单网上也有很多例子,但都不完整,很多网友分享的都在半吊子工程,只 ...

  4. 微信小程序之canvas画图

    开题 前几天接到个需求,长按图片保存到相册,该图片上有用户头像和昵称以及对应的二维码:那这就不能直接当作图片来操作了,要先把整体图片画出来:我当时用的是canvas.效果图如下: canvas dra ...

  5. 微信小程序分享朋友圈生成海报

    微信小程序实现分享到朋友圈 分享朋友圈现在大家的通用做法就是通过Canvas生成一张图片后进行保存,然后自行转发朋友圈.最近项目有这个需求, 于是就记录一下.(老规矩,我的博客复制粘贴就好使) 如果想 ...

  6. 微信小程序用canvas画图并分享

    最近开始做微信小程序,有这样一个需求: 从列表页进入详情,在每一个详情页面去分享,分享出来的图片是带有当前详情数据的图片 如下图的列表: 分享出来的样子: 解决方案和思路:canvas画图生成图片 上 ...

  7. 微信小程序使用canvas绘制分享海报

    一.准备工作 最近在做的一个vue项目中,需要做一个分享海报,头秃!!! 首先确定海报那些地方是随机变动的.海报背景.引用二维码生成组件(目前市场上有很多生成二维码的组件,这个项目用的是uqrcode ...

  8. 微信小程序2D canvas绘制分享海报

    效果图: wxml <view><canvas type="2d" id="canvas_box" style="width:260 ...

  9. 微信小程序使用Painter组件生成海报

    文档地址 我是直接下载github源码放到项目组件中 {"usingComponents": {"painter": "../../component ...

最新文章

  1. python数字组合算法_面试宝典_Python.常规算法.0001.在圆圈和框框分别填入1~8数字各一次?...
  2. redis集群(主从配置)
  3. python学习笔记全过程_Python学习过程笔记整理(一)
  4. 伯乐发卡系统源码 可用
  5. 小米A3真机开箱照片曝光:水滴屏+后置三摄
  6. Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyExce
  7. java空间大战,看这篇足矣了!
  8. mysql数据库升幂_斯特林数
  9. VS使用SDL2时LNK2019无法解析的外部符号_main
  10. 2020强网杯青少赛Pursuing_The_Wind战队WRITEUP
  11. 史密斯圆图matlab,用MATLAB程序实现Smith圆图的图解过程
  12. pdf编辑器如何在pdf上修改
  13. Funcode-Q版泡泡堂
  14. Python文件IO处理技巧: 读写、重定向、间隔符、路径、存在性与文件列表
  15. 网易云课堂uni-app实战社区交友类app开发笔记
  16. 计算机网络教程 第6版
  17. 【已解决】docker overlay2占用大量磁盘空间处理方法
  18. 网络系统高可用是几个9?
  19. Linux下的gedit复制粘贴问题
  20. 工业数字化转型 — 工业机器人

热门文章

  1. 云计算考证笔记、CPU虚拟化、内存虚拟化、IO虚拟化、存储虚拟化
  2. wgs84坐标格式转换度分秒_ArcGIS坐标单位转换(米和度分秒之间是如何转换的?)...
  3. Python海龟Turtle的使用画中秋画的方法详细讲解
  4. 她的话指引了很多人的未来生活———亦舒
  5. ubuntu安装微信、QQ、企业QQ、阿里旺旺等
  6. 各行业工资单出炉 IT类连续多年霸占“榜首”位置
  7. 解决markdown-it-prism未能正确加载Languages的BUG
  8. 20190919CF训练
  9. 阿里云服务器的购买以及使用
  10. 华为云空间联系人是不是机主_电脑上整理华为云空间联系人数据