目前要开发一个小程序项目,领导要我上手直接就是Taro,语法基本是React的语法(可叹我是个vue爱好者,之前只是用过RN[0.43]版本,目前都0.59版本了),开发起来比原生的效率要高一点,Taro——一套遵循 React 语法规范的多端统一开发框架
为什么选择Taro
框架名称 | github-stars | UI框架 | 语法 | 研发团队 |
---|---|---|---|---|
Taro | 18k | Taro-ui | React语法 | 京东 |
uni-app | 7k | uni-app插件 | Vue语法 | DCloud |
mpvue | 17k | mpvue-weui | Vue语法 | 美团 |
Chameleon | 12k | Chameleon-ui | 小程序语法 | 滴滴 |
从对比stars看Taro优势比较大,从社区群体上看uni-app在开发这块还是很有潜力的,毕竟一直都在更新中,并且已有现有的开发工具
这是掘进上对比的Taro和uni-app的文章,有对比目前所流行的框架支持度以及生态如何
遇到问题canvas画图,然后保存图片
canvasToTempFilePath: fail canvas is empty
点击canvas按钮我请求一张网络图片,一直抛这个异常,查阅文章,网上基本都是小程序生成图片,很少有关于Taro
代码如下-采坑
wxDrawImage(){
let that = this;
var canvas = Taro.createCanvasContext('shareCanvas',this)
canvas.drawImage('https://www.vipbic.com/template/default/public/img/logo.png',0,0,this.state.canvasWidth,this.state.canvasWidth * 1.5)
canvas.setTextAlign('center')
canvas.setFillStyle('#ffffff')
canvas.setFontSize(12)
canvas.fillText("生成的文字", this.state.canvasWidth * 0.5, this.state.canvasWidth * 1.26)
canvas.stroke();
canvas.draw(true,()=>{
Taro.canvasToTempFilePath({
canvasId: 'shareCanvas',
success: function(res) {
console.log(res)
Taro.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: function(res) {
console.log(res)
},
fail: function(err) {
console.log(err)
}
},that)
}
},that)
})
}
如需正确使用,需将Taro.createCanvasContext('shareCanvas',this)
替换Taro.createCanvasContext('shareCanvas',this.$scope)
,小编我也是Google,百度搜了不少文章才知道,也许是我对react理解不够深入吧
此次是后更新时间2019年7月13日-附带源码注释和效果
TestCanvas.js 组件
import Taro, { Component } from '@tarojs/taro'
import { View, Canvas, Image } from '@tarojs/components'
import { AtButton } from 'taro-ui'
import base64src from '../base64src'
import shareImg from '../logo.png'
import './index.scss'
let baseUrlCode = '';
export default class TestCanvas extends Component {
constructor(props){
super(props);
this.state = {
canvasWidth:560,
canvasHeight:978,
bgImgPath:'',
posterImage:''
}
}
componentWillMount(){
// base64 需要转换
let str3 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKQAAACkAQMAAAAjexcCAAAABlBMVEX///8AAABVwtN+AAABc0lEQVRIidWWQZbDMAhDuYHuf0tuwCCBpzNrtGlem8Q/fXUsBDji246qygxkX/vES1pofzNRxZsGKeKgPVmjHifn4dBHuQ5ELyOtFLqr7Cc+qgd9QmvE1YSHKpqf42+MT3TMBiiY+G/BCy15oyIQZMC8w5miNKD0lAn8gYMWBZolTLbAQzuEoO5aQ/tvHHOnfHFQE3DiNYqBUp1cx9GG9avZkRKVRGmK9cmdMvXaGwxnKSnLREOGZq1rIz517pQzMbUVRjx1DJQvPu77zGWgbRAK3zOx4ve1PJTeoCqr1N7dKSdIjYN9LyYLDVSeo/tQW+otlDlYcl9iksZDlXrjviBfze60mHoqmqr5W/vONNT9hViP1tMGypakkKraz08MlAWe1p79z2umd/ocqDi+ZDFQ/n+bj9qzra797lS7Faj7yymzCgPVrk3tTuqM6h6q6smFaGvspFRJ2b39yUD5QJ+3zfRQRrO0H1YVjc8e8Ua/6/gBOpv1YBO8iNcAAAAASUVORK5CYII='
base64src(str3, res => {
baseUrlCode = res
});
Taro.getSystemInfo()
.then(res => {
this.setState({
// canvasWidth:res.windowWidth*2,
// canvasHeight:res.windowHeight*2
})
})
}
// 获取微信相册授权信息
getSetting(){
return new Promise((resolve,reject)=>{
Taro.getSetting()
.then((res)=>{
if (!res.authSetting['scope.writePhotosAlbum']) {
Taro.authorize({
scope:'scope.writePhotosAlbum',
})
.then(res=>{
if(res.errMsg == 'authorize:ok'){
resolve(true)
}else{
reject(false)
}
})
.catch(()=>{
reject(false)
})
}else{
resolve(true)
}
})
.catch(()=>{
reject(false)
})
})
}
// 下载网络图片
downLoad(){
let that = this;
wx.downloadFile({
url: 'https://bw-online-img.oss-cn-hangzhou.aliyuncs.com/miniprogram/home/06.png',
success: function (res) {
that.state.bgImgPath = res.tempFilePath;
that.openShareImg();
}
})
}
// 绘制图片
wxDrawImage(callback){
const {canvasHeight} = this.state
Taro.showLoading({ title: '海报生成中', mask: true });
const WIDTH = 560;
var ctx = Taro.createCanvasContext('shareCanvas',this.$scope)
ctx.fillStyle="#fff";
ctx.fillRect(0,0,WIDTH,canvasHeight);
ctx.clearRect(0,0,0,0);
Taro.getImageInfo({src:this.state.bgImgPath})
.then((res)=>{
// 获取图片的高度
const HEIGHT = res.height;
const IMAHEWIDTH = res.width;
ctx.drawImage(shareImg, (WIDTH-180)/2, -20, 180, 120);
ctx.restore();
ctx.setFillStyle('#333333') // 颜色
ctx.setFontSize(26);
let str1 = '限时特卖|03月2610:00-03月28日09:59';
let left1 = (WIDTH-(ctx.measureText(str1).width))/2
ctx.fillText(str1,left1,88+26); //字体加设计高度
ctx.fillStyle="#D8D8D8";
ctx.fillRect(0,152,WIDTH,560);
ctx.clearRect(0,0,0,0);
ctx.drawImage(this.state.bgImgPath, (WIDTH-IMAHEWIDTH)/2,152+(560-HEIGHT)/2, IMAHEWIDTH, HEIGHT);
ctx.restore();
let str2 = '限时特卖限时特限时特卖限时特卖限时特卖';
let [contentLeng, contentArray, contentRows] = this.textByteLength(str2, 20);
let hs = contentRows*38;
for (let m = 0; m < contentArray.length; m++) {
ctx.setFillStyle('#333333')
ctx.setTextAlign('left');
ctx.font = 'normal bold 28px sans-serif';
ctx.fillText(contentArray[m],32,786+38*m);
}
// 图片转码
ctx.drawImage(baseUrlCode,WIDTH-140-32,754,140,140);
ctx.restore();
ctx.setFillStyle('#333333') // 颜色
ctx.setFontSize(32);
let str4 = '¥2999.00';
let left4 = ctx.measureText(str4).width
ctx.font = 'normal bold 32px sans-serif'
ctx.fillText(str4,32,798+hs)
let str5 = '跨境商品';
let width5 = ctx.measureText(str5).width
ctx.fillStyle='#FFDDDD';
ctx.fillRect(left4+32+16,766+hs,width5+20,38)
ctx.setFillStyle('#E61717');
ctx.setFontSize(24)
ctx.font = 'normal lighter 24px sans-serif'
ctx.fillText(str5,left4+32+41,795+hs);
ctx.setFillStyle('#999999') // 颜色
ctx.setFontSize(26);
let str6 = '来自蓝鲸淘小店';
ctx.fillText(str6,32,canvasHeight-38); //字体加设计高度
ctx.setFillStyle('#999999') // 颜色
ctx.setFontSize(26);
let str7 = '长按识别二维码';
let width7 = ctx.measureText(str7).width
ctx.fillText(str7,WIDTH-width7-32,canvasHeight-38); //字体加设计高度
ctx.draw(true,()=>{
callback && callback()
})
})
}
// 授权提示
showModal(){
let that = this;
Taro.showModal({
title: '授权提示',
content: '打开保存图片权限',
success (res) {
if (res.confirm) {
Taro.openSetting({
success (res) {
if(res.authSetting['scope.writePhotosAlbum']){
// 调用画图
that.wxDrawImage(()=>{
that.saveImage()
})
}else{
Taro.showToast({
title: '授权失败',
icon: 'none'
});
}
},
fail(){
Taro.showToast({
title: '授权失败',
icon: 'none'
});
}
})
} else if (res.cancel) {
Taro.showToast({
title: '授权失败',
icon: 'none'
});
}
}
})
}
// 打开分享
openShareImg(){
this.getSetting().then((res)=>{
if(!res){
this.showModal()
}else{
this.wxDrawImage(()=>{
this.saveImage()
})
}
}).catch(()=>{
this.showModal()
})
}
openImage(){
}
// 图片保存
saveImage(){
let that = this;
const {canvasWidth, canvasHeight} = this.state
Taro.canvasToTempFilePath({
width: canvasWidth,
height: canvasHeight,
destWidth: canvasWidth * 2,
destHeight: canvasHeight * 2,
x: 0,
y: 0,
canvasId: 'shareCanvas',
success: function(res) {
Taro.hideLoading();
that.setState({
posterImage: res.tempFilePath
})
}
},that.$scope)
}
/**
* 生成海报获取文字
* @param string text 为传入的文本
* @param int num 为单行显示的字节长度
* @return array
*/
textByteLength (text, num){
let strLength = 0;
let rows = 1;
let str = 0;
let arr = [];
for (let j = 0; j < text.length; j++) {
if (text.charCodeAt(j) > 255) {
strLength += 2;
if (strLength > rows * num) {
strLength++;
arr.push(text.slice(str, j));
str = j;
rows++;
}
} else {
strLength++;
if (strLength > rows * num) {
arr.push(text.slice(str, j));
str = j;
rows++;
}
}
}
arr.push(text.slice(str, text.length));
return [strLength, arr, rows] // [处理文字的总字节长度,每行显示内容的数组,行数]
}
render () {
const {canvasWidth, canvasHeight, posterImage} = this.state
const style= {
position: 'fixed',
top: 0,
left: '1000px'
}
return (
<View className='canvas'>
<Canvas canvasId="shareCanvas" style={{width:canvasWidth+'px',height:canvasHeight+'px',...style}}></Canvas>
<AtButton type='primary' circle onClick={this.downLoad.bind(this)}>canvas保存图片</AtButton>
{ posterImage ? (<Image className='img' src={posterImage} style={{width:canvasWidth/2+'px',height:canvasHeight/2+'px',backgroundColor:'#fff'}}></Image>) : ''}
</View>
)
}
}
test.js页面
import Taro, { Component } from '@tarojs/taro'
import { View } from '@tarojs/components'
import TextCanvas from './TestCanvas'
class Test extends Component {
componentDidMount() {
}
componentDidShow() {
}
async onPullDownRefresh() {
}
render () {
return (
<View>
<TextCanvas></TextCanvas>
</View>
)
}
}
export default Test
用到的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 default base64src;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。