您当前的位置:首页 > 计算机 > 编程开发 > 小程序

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

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

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

需求的复杂度各不相同,不外乎 背景图+微信头像+昵称+小程序码+其他。本文作者入坑较深,使用了在前端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 值和图片服务器返回的地址 存入数据库,可提高复用性,也可以简化前端的一些操作。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐