zoukankan      html  css  js  c++  java
  • 小程序生成海报 canvas

    前言

    微信小程序需要生成海报进行朋友圈分享,但是不同的手机会有问题,

    然后首先是图片的问题

    图片

    在模拟器上没有报错,可是真机测试却什么也没画出来。 canvas.drawImage 是不支持网络图片的,只支持本地图片。

    所以,任何的网络图片都需要先缓存到本地,(当然上线的时候需要把网络图片的前缀加入白名单里面)

    再通过 drawImage 调用存储的本地资源进行绘制,

    缓存可以通过 wx.getImageInfo 和 wx.downloadFile 实现

    wx.getImageInfo({
      src: 'https://i415454.jpg',
      success: function (res) {
        console.log(res.width)
        console.log(res.path)
      }
    })

    然后通过 draw 方法 的是 draw 方法是异步的,如果图片还没加载成功,有可能画出来的是空的,所以 draw 方法通常都会带有定时器这样的回调。

     this.ctx.draw(false, () => {
            wx.setStorageSync('canvasdrawer_pic_cache', this.cache)
            const system = wx.getSystemInfoSync().system
            if (/ios/i.test(system)) {
              this.saveImageToLocal()
            } else {
              // 延迟保存图片,解决安卓生成图片错位bug。
              setTimeout(() => {
                this.saveImageToLocal()
              }, 800)
            }
          })

    画布首先分为 矩形, 图片,文字,线这几种,

    是结合了promise 来处理 ,

    产生的图片 直接通过 previewImage 进入手机预览模式,预览模式的图片可以直接保存到本地

    然后就是我的DEMO 先放2张图

     这就是生成后的图片了

     先写组件  canvasdrawer

     js:

    Component({
      properties: {
        painting: {
          type: Object,
          value: { view: [] },
          observer(newVal, oldVal) {
            if (!this.data.isPainting) {
              if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
                if (newVal && newVal.width && newVal.height) {
                  this.setData({
                    showCanvas: true,
                    isPainting: true
                  })
                  this.readyPigment()
                }
              } else {
                if (newVal && newVal.mode !== 'same') {
                  this.triggerEvent('getImage', { errMsg: 'canvasdrawer:samme params' })
                }
              }
            }
          }
        }
      },
      data: {
        showCanvas: false,
    
         100,
        height: 100,
    
        tempFileList: [],
    
        isPainting: false
      },
      ctx: null,
      cache: {},
      ready() {
        wx.removeStorageSync('canvasdrawer_pic_cache')
        this.cache = wx.getStorageSync('canvasdrawer_pic_cache') || {}
        this.ctx = wx.createCanvasContext('canvasdrawer', this)
      },
      methods: {
        readyPigment() {
          const { width, height, views } = this.data.painting
          this.setData({
            width,
            height
          })
    
          const inter = setInterval(() => {
            if (this.ctx) {
              clearInterval(inter)
              this.ctx.clearActions()
              this.ctx.save()
              this.getImagesInfo(views)
            }
          }, 100)
        },
        getImagesInfo(views) {
          const imageList = []
          for (let i = 0; i < views.length; i++) {
            if (views[i].type === 'image') {
              imageList.push(this.getImageInfo(views[i].url))
            }
          }
    
          const loadTask = []
          for (let i = 0; i < Math.ceil(imageList.length / 8); i++) {
            loadTask.push(new Promise((resolve, reject) => {
              Promise.all(imageList.splice(i * 8, 8)).then(res => {
                resolve(res)
              }).catch(res => {
                reject(res)
              })
            }))
          }
          Promise.all(loadTask).then(res => {
            let tempFileList = []
            for (let i = 0; i < res.length; i++) {
              tempFileList = tempFileList.concat(res[i])
            }
            this.setData({
              tempFileList
            })
            this.startPainting()
          })
        },
        startPainting() {
          const { tempFileList, painting: { views } } = this.data
          for (let i = 0, imageIndex = 0; i < views.length; i++) {
            if (views[i].type === 'image') {
              this.drawImage({
                ...views[i],
                url: tempFileList[imageIndex]
              })
              imageIndex++
            } else if (views[i].type === 'text') {
              if (!this.ctx.measureText) {
                wx.showModal({
                  title: '提示',
                  content: '当前微信版本过低,无法使用 measureText 功能,请升级到最新微信版本后重试。'
                })
                this.triggerEvent('getImage', { errMsg: 'canvasdrawer:version too low' })
                return
              } else {
                this.drawText(views[i])
              }
            } else if (views[i].type === 'rect') {
              this.drawRect(views[i])
            }
          }
          this.ctx.draw(false, () => {
            wx.setStorageSync('canvasdrawer_pic_cache', this.cache)
            const system = wx.getSystemInfoSync().system
            if (/ios/i.test(system)) {
              this.saveImageToLocal()
            } else {
              // 延迟保存图片,解决安卓生成图片错位bug。
              setTimeout(() => {
                this.saveImageToLocal()
              }, 800)
            }
          })
        },
        drawImage(params) {
          this.ctx.save()
          const { url, top = 0, left = 0, width = 0, height = 0, borderRadius = 0, deg = 0 } = params
          if (borderRadius) {
            // 圓角
            this.ctx.beginPath()
            this.ctx.arc(width / 2 + left, height / 2 + top, width / 2, 0, Math.PI * 2, false);
            this.ctx.clip()
            this.ctx.drawImage(url, left, top, width, height)
          }  
         else if (deg !== 0) {
            this.ctx.translate(left + width / 2, top + height / 2)
            this.ctx.rotate(deg * Math.PI / 180)
            this.ctx.drawImage(url, -width / 2, -height / 2, width, height)
          } else {
            this.ctx.drawImage(url, left, top, width, height)
          }
          // }
          this.ctx.restore()
        },
        drawText(params) {
          this.ctx.save()
          const {
            MaxLineNumber = 2,
            breakWord = false,
            color = 'black',
            content = '',
            fontSize = 16,
            top = 0,
            left = 0,
            lineHeight = 20,
            textAlign = 'left',
            width,
            bolder = false,
            textDecoration = 'none'
          } = params
    
          this.ctx.beginPath()
          this.ctx.setTextBaseline('top')
          this.ctx.setTextAlign(textAlign)
          this.ctx.setFillStyle(color)
          this.ctx.setFontSize(fontSize)
    
          if (!breakWord) {
            this.ctx.fillText(content, left, top)
            this.drawTextLine(left, top, textDecoration, color, fontSize, content)
          } else {
            let fillText = ''
            let fillTop = top
            let lineNum = 1
            for (let i = 0; i < content.length; i++) {
              fillText += [content[i]]
              if (this.ctx.measureText(fillText).width > width) {
                if (lineNum === MaxLineNumber) {
                  if (i !== content.length) {
                    fillText = fillText.substring(0, fillText.length - 1) + '...'
                    this.ctx.fillText(fillText, left, fillTop)
                    this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText)
                    fillText = ''
                    break
                  }
                }
                this.ctx.fillText(fillText, left, fillTop)
                this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText)
                fillText = ''
                fillTop += lineHeight
                lineNum++
              }
            }
            this.ctx.fillText(fillText, left, fillTop)
            this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText)
          }
    
          this.ctx.restore()
    
          if (bolder) {
            this.drawText({
              ...params,
              left: left + 0.3,
              top: top + 0.3,
              bolder: false,
              textDecoration: 'none'
            })
          }
        },
        drawTextLine(left, top, textDecoration, color, fontSize, content) {
          if (textDecoration === 'underline') {
            this.drawRect({
              background: color,
              top: top + fontSize * 1.2,
              left: left - 1,
               this.ctx.measureText(content).width + 3,
              height: 1
            })
          } else if (textDecoration === 'line-through') {
            this.drawRect({
              background: color,
              top: top + fontSize * 0.6,
              left: left - 1,
               this.ctx.measureText(content).width + 3,
              height: 1
            })
          }
        },
        drawRect(params) {
          this.ctx.save()
          const { background, top = 0, left = 0, width = 0, height = 0, radius = 0 } = params
          this.ctx.setFillStyle(background)
          this.ctx.fillRect(left, top, width, height)
          
          if (radius!=0){
            this.ctx.beginPath()
            this.ctx.setFillStyle(background)
            this.ctx.setStrokeStyle(background);
            this.ctx.setLineJoin('round');  //交点设置成圆角
            this.ctx.setLineWidth(radius) ;
            this.ctx.strokeRect(width + radius / 2, height + radius / 2, width - radius, height - radius);
            this.ctx.fillRect(width + radius, height + radius, width - radius * 2, height - radius * 2);
            this.ctx.stroke();
            this.ctx.closePath();
          }
          this.ctx.restore();
        },
        getImageInfo(url) {
          return new Promise((resolve, reject) => {
            if (this.cache[url]) {
              resolve(this.cache[url])
            } else {
              const objExp = new RegExp(/^http(s)?://([w-]+.)+[w-]+(/[w- ./?%&=]*)?/)
              if (objExp.test(url)) {
                wx.getImageInfo({
                  src: url,
                  complete: res => {
                    if (res.errMsg === 'getImageInfo:ok') {
                      this.cache[url] = res.path
                      resolve(res.path)
                    } else {
                      this.triggerEvent('getImage', { errMsg: 'canvasdrawer:download fail' })
                      reject(new Error('getImageInfo fail'))
                    }
                  }
                })
              } else {
                this.cache[url] = url
                resolve(url)
              }
            }
          })
        },
        saveImageToLocal() {
          const { width, height } = this.data
          wx.canvasToTempFilePath({
            x: 0,
            y: 0,
            width,
            height,
            canvasId: 'canvasdrawer',
            complete: res => {
              if (res.errMsg === 'canvasToTempFilePath:ok') {
                this.setData({
                  showCanvas: false,
                  isPainting: false,
                  tempFileList: []
                })
                this.triggerEvent('getImage', { tempFilePath: res.tempFilePath, errMsg: 'canvasdrawer:ok' })
              } else {
                this.triggerEvent('getImage', { errMsg: 'canvasdrawer:fail' })
              }
            }
          }, this)
        }
      }
    })
    View Code

    html :

    <canvas canvas-id="canvasdrawer" style="{{width}}px;height:{{height}}px;" class="board" wx:if="{{showCanvas}}"></canvas>

    css:

    .board {
      position: fixed;
      top: 2000rpx;
    }

    在页面中调用这个组件 canvasdrawer

    html:

    <canvasdrawer painting="{{painting}}" class="canvasdrawer" bind:getImage="eventGetImage"/>

    js:

     // 生成
        eventDraw() {
          wx.showLoading({
            title: '绘制分享图片中',
            mask: true
          })
          this.setData({
            painting: {
               375,
              height: app.globalData.screenHeight,
              clear: true,
              views: [
                {
                  type: 'rect',
                  background: this.data.skin.theme_color,
                  top: 0,
                  left: 0,
                   375,
                  radius:10,
                  height: app.globalData.screenHeight,
                },
                {
                  type: 'image',
                  url: '/images/common/posterBg.png',  // 背景 //https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531103986231.jpeg
                  top: 0,
                  left: 0,
                   375,
                  height: 667
                },
                {
                  type: 'image',
                  url: '/images/common/avatar.png', 
                  top: 274,
                  left: 30,
                   45,
                  borderRadius:45,
                  height: 45  //头像
                },
                {
                  type: 'text',
                  content: '大帅比哈哈',
                  fontSize: 15,
                  color: '#333333',
                  textAlign: 'left',
                  top: 564/2,
                  left: 162/2,
                  bolder: false
                },
                {
                  type: 'text',
                  content: '邀请你一起来享受优惠!',
                  fontSize: 13,
                  color: this.data.skin.theme_color,
                  textAlign: 'left',
                  top: 611/2,
                  left: 162 / 2
                },
                {
                  type: 'image',
                  url: this.data.shareActivity.topImage, //活动图片
                  top: 104,
                  left: 30,
                    315,
                  height: 159
                },
                {
                  type: 'image',
                  url: '/images/common/avatar.png',
                  top: (600 +179) /2,
                  left: 60,
                   245/2,
                  height: 245/2
                },
                {
                  type: 'image',
                  url: this.data.fingerImage, // 指紋 'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531385433625.jpeg'
                  top: (615 + 179) / 2,
                  left: 213,
                   215 / 2,
                  height: 215 / 2,        
                },
                {
                  type: 'text',
                  content: this.data.shareActivity.shareTitle, //'正品MAC魅可口红礼盒生日唇膏小辣椒Chili西柚情人',
                  fontSize: 16,
                  lineHeight: 21,
                  color: '#383549',
                  textAlign: 'left',
                  top: 336,
                  left: 30,
                   310,
                  MaxLineNumber: 2,
                  breakWord: true,
                  bolder: true
                },
                {
                  type: 'text',
                  content: '长按图片识别二维码,立即参与活动~',
                  fontSize: 13,
                  color: '#999',
                  textAlign: 'left',
                  top: (879+176)/ 2 ,
                  left: 75,
                  lineHeight: 20,
                  MaxLineNumber: 2,
                  breakWord: true,
                   209
                }
              ]
            }
          })
        },
    
    
        //保存
        eventGetImage(event) {
          let _this = this;
          wx.hideLoading()
          const { tempFilePath, errMsg } = event.detail
          if (errMsg === 'canvasdrawer:ok') {
            this.setData({
              shareImage: tempFilePath,
            })
    
            wx.previewImage({
              urls: [tempFilePath],
              success: function () {
                _this.setData({
                  isShareBtnDisabled: false,
                  painting:{},
                })
              },
              fail: function () {
              }
            })
            
          }
        }
    View Code

    这要就可以啦  海报就兼容 苹果和安卓 手机 嘻嘻 

    还需努力

  • 相关阅读:
    父div不会被子div撑高
    ie6兼容问题
    浏览器兼容性技巧
    css hack基本语法
    网站设置为灰色
    .net cookie跨域请求指定请求域名
    实体对象属性和值转为键值对Dictionary
    C#通过对象属性名修改值
    jQuery.noConflict()解决imgBox.js依赖jquery版本问题
    华为OJ之最长公共子序列
  • 原文地址:https://www.cnblogs.com/yf-html/p/12074087.html
Copyright © 2011-2022 走看看