2025年3月14日 星期五 甲辰(龙)年 月十三 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > 小程序

微信小程序开发——生成分享图片

时间:02-25来源:作者:点击数:9

做小程序开发,大都会遇到这么一个需求:生成分享图片。

需求的复杂度各不相同,不外乎 背景图+微信头像+昵称+小程序码+其他。本文作者入坑较深,使用了在前端canvas画布来实现。

大概的总结了下遇到的一些知识点:

1、创建一个通用的图片分享组件components;

2、用drawImage绘制自适应图片;

3、获取小程序码接口真的很坑;

4、保存base64图片到本地临时文件;

5、Promise.all解决获取图片数据过程中的异步;

废话不说了 直接贴代码

组件:

canvas-share.wxml

  • <view catchtap="handleClose" class="share {{ visible ? 'show' : '' }}">
  • <canvas class="canvas-hide" canvas-id="share" style="width:{{canvasWidth*2}}rpx;height:{{canvasHeight*2}}rpx" />
  • <view class="content" style="transform:scale({{responsiveScale}});-webkit-transform:scale({{responsiveScale}});">
  • <image class="canvas" catchtap="zuzhimaopao" src="{{imageFile}}" style="width:{{canvasWidth/3*2}}rpx;height:{{canvasHeight/3*2}}rpx" />
  • <view class="save" catchtap="handleSave"><text>保存图片</text></view>
  • </view>
  • </view>

canvas-share.js

  • const app = getApp();
  • var base64src = require("../../utils/base64src.js");
  • var ajax = require("../../common/commonAjax.js");
  • var util = require("../../utils/util.js");
  • function getImageInfo(url) {
  • return new Promise((resolve, reject) => {
  • wx.getImageInfo({
  • src: url,
  • success: resolve,
  • fail: reject,
  • })
  • })
  • }
  • function getImageInfo2(search){
  • let url="";
  • return new Promise((resolve, reject) => {
  • ajax.getAccessToken(search, function (e) {
  • if (e.ErrorCode == 0) {
  • let base64data = e.Data;
  • base64src.base64src(base64data, res => {
  • //resolve(res);
  • url = res;
  • wx.getImageInfo({
  • src: url,
  • success: resolve,
  • fail: reject,
  • })
  • });
  • }
  • })
  • })
  • }
  • function createRpx2px() {
  • const { windowWidth } = wx.getSystemInfoSync()
  • return function(rpx) {
  • return windowWidth / 750 * rpx
  • }
  • }
  • const rpx2px = createRpx2px()
  • function canvasToTempFilePath(option, context) {
  • return new Promise((resolve, reject) => {
  • wx.canvasToTempFilePath({
  • ...option,
  • success: resolve,
  • fail: reject,
  • }, context)
  • })
  • }
  • function saveImageToPhotosAlbum(option) {
  • return new Promise((resolve, reject) => {
  • wx.saveImageToPhotosAlbum({
  • ...option,
  • success: resolve,
  • fail: reject,
  • })
  • })
  • }
  • Component({
  • properties: {
  • visible: {
  • type: Boolean,
  • value: false,
  • observer(visible) {
  • if (visible && !this.beginDraw) {
  • this.draw()
  • this.beginDraw = true
  • }
  • }
  • },
  • userInfo: {
  • type: Object,
  • value: null,
  • observer: function (e) {
  • this.setData({
  • userInfo: e
  • })
  • }
  • },
  • courseName: {
  • type: String,
  • value: "",
  • observer: function (e) {
  • this.setData({
  • courseName: e
  • })
  • }
  • },
  • page: {
  • type: String,
  • value: "",
  • observer: function (e) {
  • this.setData({
  • page: e
  • })
  • }
  • },
  • scene: {
  • type: String,
  • value: "",
  • observer: function (e) {
  • this.setData({
  • scene: e
  • })
  • }
  • }
  • },
  • data: {
  • beginDraw: false,
  • isDraw: false,
  • canvasWidth: 1000,
  • canvasHeight: 1200,
  • imageFile: '',
  • responsiveScale: 1,
  • userInfo: app.globalData.userInfo,
  • courseName:"",
  • page:"",
  • scene:"",
  • },
  • lifetimes: {
  • ready() {
  • const designWidth = 375
  • const designHeight = 603 // 这是在顶部位置定义,底部无tabbar情况下的设计稿高度
  • // 以iphone6为设计稿,计算相应的缩放比例
  • const { windowWidth, windowHeight } = wx.getSystemInfoSync()
  • const responsiveScale =
  • windowHeight / ((windowWidth / designWidth) * designHeight)
  • if (responsiveScale < 1) {
  • this.setData({
  • responsiveScale,
  • })
  • }
  • },
  • },
  • methods: {
  • handleClose() {
  • this.triggerEvent('close')
  • },
  • zuzhimaopao(){
  • },
  • handleSave() {
  • const { imageFile } = this.data
  • if (imageFile) {
  • saveImageToPhotosAlbum({
  • filePath: imageFile,
  • }).then(() => {
  • wx.showToast({
  • icon: 'none',
  • title: '分享图片已保存至相册',
  • duration: 2000,
  • })
  • })
  • }
  • },
  • draw() {
  • wx.showLoading()
  • let cpage=this;
  • console.log(this.data);
  • const { userInfo, canvasWidth, canvasHeight,courseName } = this.data;
  • const { avatarUrl, nickName } = userInfo;
  • const avatarPromise = getImageInfo(avatarUrl);
  • const backgroundPromise = '/images/share.png';
  • let search={
  • page:this.data.page,
  • scene:this.data.scene
  • }
  • const filePath = getImageInfo2(search);
  • Promise.all([avatarPromise, filePath])
  • .then(([avatar, filePathxcx]) => {
  • const ctx = wx.createCanvasContext('share', this)
  • const canvasW = rpx2px(canvasWidth * 2)
  • const canvasH = rpx2px(canvasHeight * 2)
  • // 绘制背景
  • ctx.drawImage(
  • backgroundPromise,
  • //background,
  • 1,
  • 1,
  • canvasW,
  • canvasH
  • )
  • // 绘制头像
  • const radius = rpx2px(80 * 2)
  • const y = rpx2px(1030 * 2)
  • // ctx.arc(canvasW / 2 - radius * 4, y, radius, 0, 2 * Math.PI)
  • // ctx.clip()
  • ctx.drawImage(
  • avatar.path,
  • canvasW / 2 - radius * 5 - 10,
  • y - radius + 15,
  • radius * 2,
  • radius * 2,
  • )
  • // 绘制小程序码
  • if (!util.isNullObj(filePathxcx)){
  • ctx.drawImage(
  • filePathxcx.path,
  • // 'http://usr/tmp_base64src.png',
  • canvasW / 2 + radius * 2.5,
  • y - radius * 1.3,
  • radius * 3,
  • radius * 3,
  • )
  • }
  • // 绘制用户名
  • ctx.setFontSize(36)
  • ctx.setTextAlign('center')
  • ctx.setFillStyle('#000000')
  • ctx.fillText(
  • nickName,
  • canvasW / 2 - radius * 1.5 - 25,
  • // y + rpx2px(150 * 2),
  • y - radius * 0.5,
  • )
  • ctx.stroke()
  • // 绘制课程名称
  • ctx.setFontSize(48)
  • ctx.setTextAlign('center')
  • ctx.setFillStyle('#434999')
  • ctx.fillText(
  • "《" + courseName + "》",
  • canvasW / 2,
  • y - radius * 3.5,
  • )
  • ctx.draw(false, () => {
  • canvasToTempFilePath({
  • canvasId: 'share',
  • }, cpage).then(({ tempFilePath }) => cpage.setData({ imageFile: tempFilePath }))
  • })
  • wx.hideLoading()
  • cpage.setData({ isDraw: true })
  • })
  • .catch(() => {
  • cpage.setData({ beginDraw: false })
  • wx.hideLoading()
  • })
  • }
  • }
  • })

前端保存base64图片的引用

base64src.js

  • const fsm = wx.getFileSystemManager();
  • const FILE_BASE_NAME = 'tmp_base64src'; //自定义文件名
  • function base64src(base64data, cb) {
  • const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64data) || [];
  • if (!format) {
  • return (new Error('ERROR_BASE64SRC_PARSE'));
  • }
  • const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}`;
  • const buffer = wx.base64ToArrayBuffer(bodyData);
  • fsm.writeFile({
  • filePath,
  • data: buffer,
  • encoding: 'binary',
  • success() {
  • cb(filePath);
  • },
  • fail() {
  • return (new Error('ERROR_BASE64SRC_WRITE'));
  • },
  • });
  • };
  • export { base64src };

调用组件:

<canvas-share bindclose="close" userInfo="{{userInfo}}" visible="{{visible}}" courseName="{{aColumn.sName}}" page="/pages/course/PDFDetail" scene="iAutoID={{iAutoID}}" />

参数:

userInfo:wx.getUserInfo 获取的用户信息

visible:是否显示

courseName:可忽略,特殊需求 分享图上的显示课程名称

page:用于生成小程序码的参数 扫码跳转的页面

scene:用于生成小程序码的参数 扫码跳转的页面参数

bindclose:隐藏组件visible:false

Php后台 获取小程序码涉及的接口和方法

  • public function GetAccessTokenAction(){
  • $formVals= json_decode($this->getParam('formVals'));
  • $page="";
  • $scene="";
  • if ($formVals){
  • $page=$formVals->page;
  • $scene=$formVals->scene;
  • }
  • // $page="pages/course/oneVideoDetail";
  • // $scene="iAutoID=60";
  • $appid=Yaf_G::getConf('appid', 'dcr');
  • $secret=Yaf_G::getConf('appsecret', 'dcr');
  • $rwm_contents="";
  • $url1="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appid}&secret={$secret}";
  • $result = self::curl_file_get_contents($url1);
  • $access_token= isset($result)?json_decode($result)->access_token:'';
  • if($access_token!=""){
  • // $url2="https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={$access_token}";
  • // $rwm_contents = self::curl_file_get_contents($url2,$page,$scene);
  • $url2="https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=".$access_token;
  • //dump($access_token);
  • $post_data = array(
  • "page"=>$page,
  • "scene"=>$scene,
  • "width"=>50
  • );
  • $post_data=json_encode($post_data);
  • $rwm_contents=self::send_post($url2,$post_data);
  • $result=self::data_uri($rwm_contents,'image/png');
  • }
  • return $this->showMgApiMsg($result);
  • }
  • protected function send_post( $url, $post_data ) {
  • $options = array(
  • 'http' => array(
  • 'method' => 'POST',
  • 'header' => 'Content-type:application/json',
  • //header 需要设置为 JSON
  • 'content' => $post_data,
  • 'timeout' => 60
  • //超时时间
  • )
  • );
  • $context = stream_context_create( $options );
  • $result = file_get_contents( $url, false, $context );
  • return $result;
  • }
  • public function data_uri($contents, $mime)
  • {
  • $base64 = base64_encode($contents);
  • return ('data:' . $mime . ';base64,' . $base64);
  • }
  • public function curl_file_get_contents($durl){
  • // header传送格式
  • $headers = array(
  • "token:1111111111111",
  • "over_time:22222222222",
  • );
  • // 初始化
  • $curl = curl_init();
  • // 设置url路径
  • curl_setopt($curl, CURLOPT_URL, $durl);
  • // 将 curl_exec()获取的信息以文件流的形式返回,而不是直接输出。
  • curl_setopt($curl, CURLOPT_RETURNTRANSFER, true) ;
  • // 在启用 CURLOPT_RETURNTRANSFER 时候将获取数据返回
  • curl_setopt($curl, CURLOPT_BINARYTRANSFER, true) ;
  • // 添加头信息
  • curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  • // CURLINFO_HEADER_OUT选项可以拿到请求头信息
  • curl_setopt($curl, CURLINFO_HEADER_OUT, true);
  • // 不验证SSL
  • curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
  • curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
  • // 执行
  • $data = curl_exec($curl);
  • // 打印请求头信息
  • // echo curl_getinfo($curl, CURLINFO_HEADER_OUT);
  • // 关闭连接
  • curl_close($curl);
  • // 返回数据
  • return $data;
  • }

思考:

1、后端绘图更便于调试;

2、生成二维码可存入图片服务器 在根据page+scene 值和图片服务器返回的地址 存入数据库,可提高复用性,也可以简化前端的一些操作。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
上一篇:小程序 应用生命周期 下一篇:很抱歉没有了
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐