Taro框架使用canvas生成图片-附带源码和效果

简介: Taro框架使用canvas生成图片-附带源码和效果

为什么选择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


image.png


代码如下-采坑


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日-附带源码注释和效果


image.png


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;


相关文章
|
1月前
GreenSock动效库TweenMax简单使用代码片段
GreenSock动效库TweenMax简单使用代码片段
|
17天前
|
存储
THREEJS实战6_加载fbx模型
THREEJS实战6_加载fbx模型
60 1
|
8月前
|
Dart 前端开发 API
Flutter笔记:手写一个简单的画板工具
1. 任务介绍Flutter笔记实现一个简单的画板工具作者目 录1. 任务介绍2. 知识点准备3. 代码实现与效果1. 任务介绍在本文中,我们将一起开发一个基本的Flutter画板应用,用户可以在画板上自由绘制,选择不同的颜色来绘制线条。这个画板应用将允许用户通过点击颜色选择按钮来选择画笔的颜色,并提供鼠标光标支持以增强用户体验。
171 0
|
5月前
|
人工智能 JavaScript Linux
基于Three.js的3D自动纹理化开发包
DreamTexture.js 基于 Three.js 和稳定扩散(stable diffusion) AI 模型开发,用于实现 3D 模型的自动纹理化。
58 0
|
5月前
|
算法 图形学
Unity——DOTween插件使用方法简介
Unity——DOTween插件使用方法简介
277 0
|
6月前
|
前端开发
React基础语法03-引入本地图片和服务器图片的方法
React基础语法03-引入本地图片和服务器图片的方法
47 0
|
8月前
|
数据库 容器
Flutter笔记:无限滚动与动态加载的实现
在 Flutter 中,实现一个无尽滚动列表通常涉及使用 ListView、ListView.builder 或 ListView.separated 组件,并结合数据源和滚动控制器。这使得你可以加载和显示大量数据,只有在需要时才会动态加
156 0
|
9月前
|
前端开发 小程序 JavaScript
微信小程序 - DZMDrawingBoard - (Canvas封装的画板、手写签名、生成图片、保存相册...库)
微信小程序 - DZMDrawingBoard - (Canvas封装的画板、手写签名、生成图片、保存相册...库)
138 0
|
10月前
|
移动开发 前端开发 JavaScript
leaflet使用domtoimage插件与h5 canvas实现截图功能并下载
leaflet使用domtoimage插件与h5 canvas实现截图功能并下载
|
11月前
|
前端开发 小程序 数据安全/隐私保护