taro canvas 生成海報

前言

  • ?? 一下這幾天寫小程序频丘,使用 canvas 動態(tài)生成海報郁稍,下載分享的效果
  • 使用 taro 做小程序辜梳,因為可以用 React粱甫,使用了最新的 React hooks 的方式(好像也不新了)
  • 剛開始寫感覺很累,因為沒有搞過小程序作瞄,并且也不會用 canvas茶宵,我只能寫一點,編譯一下看效果??宗挥,走了很多彎路乌庶,花了很多心思,結(jié)果出來后契耿,感覺自己太笨了(學(xué)習(xí)能力還有待提高)


    成品效果.png
  • 按鈕樣式還沒有改瞒大,等成品出來改一下 (打個小廣告)

下載網(wǎng)絡(luò)圖片,處理多張圖片

  • 因為要動態(tài)畫頭像搪桂,需要把網(wǎng)絡(luò)圖片地址透敌,下載后畫到 canvas 中

封裝一個處理多張圖片的 promise

  • 因為不想使用 getImageInfo 一層層嵌套,圖片很多的話,豈不是亂的像??一樣酗电。
// 處理多張網(wǎng)絡(luò)圖片
  const processMultipleImages = (url) => {
    return new Promise((resolve, reject) => {
      Taro.getImageInfo({
        src: url,
        success: (res) => {
          resolve(res)
        },
        fail: () => {
          Taro.showToast({
            title: '下載失敗!'
          })
        }
      })
    })
  }

處理所有圖片

  • 使用 promise.all 轉(zhuǎn)換所有的圖片地址
 // 獲取基本信息
  useEffect(() => {
    // 獲取所有的網(wǎng)絡(luò)圖片
    Promise.all(
      imageAry.map(img => processMultipleImages(img))
    ).then(images => {
      const imgAll = images.map(i => i.path)
      SetUrl(imgAll)
    })
  }, [])

畫主體

獲取屏幕信息

  • 主要是獲取寬度和高度魄藕,動態(tài)給 canvas 設(shè)置寬高,已經(jīng) canvas 間距顾瞻,rate 表示轉(zhuǎn)換的倍數(shù)(根據(jù)設(shè)計稿的標準寬高)泼疑,目前是用的這種方式,網(wǎng)上看了很多都沒有轉(zhuǎn)換荷荤,難道不用根據(jù)設(shè)備適配嗎退渗?還是說我用的方式不對,業(yè)務(wù)催得緊沒有仔細研究蕴纳,如果你知道的話会油,歡迎交流~
    const d = Taro.getSystemInfoSync()
    const w = d.windowWidth * 0.85
    const h = (w / 0.75).toFixed(2)
    const rate = (d.windowWidth / 375).toFixed(2)
    SetWidth(w)
    SetHeight(h)
    SetRate(rate)
  • 寬度和高度
  return (
    <View className="share-user-container">
      <Canvas style={{width: `${width}px`, height: `${height}px`}} canvasId="shareuser" id="shareuser" className="canvas-wrapper"></Canvas>
      <Button onClick={onClickSaveImage}>保存到相冊</Button>
    </View>
  )

圓形頭像

    const ctx = Taro.createCanvasContext('shareuser');
   // 畫內(nèi)圓 并 填充頭像
    ctx.beginPath()
    const x = 56 * rate
    const y = 74 * rate + 64 * rate
    ctx.arc(x, y, 64 * rate, 0, 2 * Math.PI)
    ctx.clip()
    ctx.drawImage(imageUrl[0], 0 * rate, 74 * rate, 128 * rate, 128 * rate)
    ctx.closePath()
    ctx.restore()
    // 繪制文本
    drawText(ctx, '#1D1E1F', '來自xxx 的脫單團', 66 * rate, 24 * rate, 12)
    ctx.save()
  • 這里頭像可能會壓縮,導(dǎo)致很丑

  • drawText 繪制文字方法

// 繪制文本
  const drawText = (ctx, color, text, x, y, font = 16) => {
    ctx.setFontSize(font)
    ctx.setFillStyle(color)
    ctx.setTextAlign('left')
    ctx.fillText(text, x, y)
    ctx.stroke()
    ctx.closePath()
  }

畫圓

// 畫外圓
    ctx.beginPath()
    ctx.arc(56 * rate, 140 * rate, 80 * rate, 0, 2*Math.PI)
    ctx.lineWidth = 16 * rate
    ctx.clip()
    ctx.strokeStyle = "#FFE04A";
    ctx.stroke()
    ctx.closePath()

二維碼

  • 這里的二維碼目前是寫死的古毛,但業(yè)務(wù)需要動態(tài)生成翻翩,等我做了加上待更新...
  • 更新了這里,可以去看一下 小程序動態(tài)生成二維碼
    const size14 = 14 * rate
    // 繪制二維碼
    ctx.drawImage(imageUrl[1], 210 * rate, 120 * rate, 86 * rate, 86 * rate)
    drawText(ctx, '#1D1E1F', '掃碼認識Ta', 216 * rate, 220 * rate, size14)

最后生成圖片

  • 這里微信的官方說稻薇,放到 ctx.draw() 的 callback 里面嫂冻,但是沒有執(zhí)行,不知道為啥塞椎,這里就先使用了 setTimeout 模擬異步生成
   setTimeout(() => {
      Taro.canvasToTempFilePath({
        x:0,
        y:0,
        width,
        height,
        canvasId: 'shareuser',
        success: (result) => {
          SetImage(result.tempFilePath)
        },
        fail: (err) => {
          Taro.showToast('圖片生成失斀胺隆!')
        }
      })
    }, 600)

保存到相冊中案狠,需要用戶授權(quán)

  • 調(diào)用授權(quán)服傍,需要兼容如果用戶點擊取消的操作
// 保存到相冊
  const onClickSaveImage = () => {
    Taro.getSetting({
      success(res) {
        // 如果沒有授權(quán)過,則要獲取授權(quán)
        if (!res.authSetting['scope.writePhotosAlbum']) {
          Taro.authorize({
            scope: 'scope.writePhotosAlbum',
            success() {
              // 保存圖片
              savePictureSystem()
            },
            fail() { // 用戶拒絕
              Taro.showModal({
                title: '授權(quán)',
                content: '您拒絕了授權(quán)請求骂铁,是否要手動開啟吹零?',
                success: function (res) {
                  if (res.confirm) {
                    Taro.openSetting({
                      success: function (res) {
                        console.log(res.authSetting)
                        res.authSetting = {
                          "scope.userInfo": true,
                          "scope.userLocation": true
                        }
                      }
                    })
                  } else if (res.cancel) {
                    Taro.showToast({
                      title: '保存失敗拉庵!',
                      icon: 'close',
                      duration: 2000
                    })
                  }
                }
              })
            }
          })
        } else { // 如果已經(jīng)授權(quán)過灿椅,可以直接保存
          savePictureSystem()
        }
      }
    })
  }
 // 把圖片保存到系統(tǒng)中
  const savePictureSystem = () => {
    Taro.saveImageToPhotosAlbum({
      filePath: saveImage,
      success(res) {
        Taro.showToast({
          title: '保存成功!'
        })
      },
      fail() {
        Taro.showToast({
          title: '保存失敗!',
          icon: 'close',
          duration: 2000
        })
      }
    })
  }

所有代碼

// 獲取基本信息
  useEffect(() => {
    const d = Taro.getSystemInfoSync()
    const w = d.windowWidth * 0.85
    const h = (w / 0.75).toFixed(2)
    const rate = (d.windowWidth / 375).toFixed(2)
    SetWidth(w)
    SetHeight(h)
    SetRate(rate)

    // 獲取所有的網(wǎng)絡(luò)圖片
    Promise.all(
      imageAry.map(img => processMultipleImages(img))
    ).then(images => {
      const imgAll = images.map(i => i.path)
      SetUrl(imgAll)
    })
  }, [])

  useEffect(() => {
    if (imageUrl.length > 0) {
      drawContent()
    }
  }, [imageUrl])

  // 畫主體內(nèi)容
  const drawContent = () => {
    const ctx = Taro.createCanvasContext('shareuser');
    const cx = 5 * rate + 20 * rate
    const cy = 12 * rate + 20 * rate
    // 背景顏色
    ctx.fillStyle = '#fff'
    ctx.fillRect(0, 0, width, height)
    ctx.save()

    // 頭像
    ctx.beginPath()
    ctx.arc(cx, cy, 20 * rate, 0, 2 * Math.PI)
    ctx.clip()
    ctx.drawImage(imageUrl[0], 6 * rate, 12 * rate, 40 * rate, 40 * rate)
    ctx.restore()

    drawText(ctx, '#1D1E1F', '來自xxx 的脫單團', 66 * rate, 24 * rate, 12)
    ctx.save()
    // 畫外圓
    ctx.beginPath()
    ctx.arc(56 * rate, 140 * rate, 80 * rate, 0, 2*Math.PI)
    ctx.lineWidth = 16 * rate
    ctx.clip()
    ctx.strokeStyle = "#FFE04A";
    ctx.stroke()
    ctx.closePath()
    // 畫內(nèi)圓 并 填充頭像
    ctx.beginPath()
    const x = 56 * rate
    const y = 74 * rate + 64 * rate
    ctx.arc(x, y, 64 * rate, 0, 2 * Math.PI)
    ctx.clip()
    ctx.drawImage(imageUrl[0], 0 * rate, 74 * rate, 128 * rate, 128 * rate)
    ctx.closePath()

    ctx.restore()

    // 繪制圓圈裝飾
    ctx.beginPath()
    ctx.arc(250 * rate, 47 * rate, 18 * rate, 0, 2*Math.PI)
    ctx.lineWidth = 4 * rate
    ctx.strokeStyle = "#FFE04A";
    ctx.stroke()
    ctx.closePath()

    ctx.beginPath()
    ctx.arc(200 * rate, 80 * rate, 9 * rate, 0, 2*Math.PI)
    ctx.lineWidth = 5 * rate
    ctx.strokeStyle = "#FFE04A";
    ctx.stroke()
    ctx.closePath()

    ctx.beginPath()
    ctx.arc(280 * rate, 98 * rate, 14 * rate, 0, 2*Math.PI)
    ctx.lineWidth = 10 * rate
    ctx.strokeStyle = "#FFE04A";
    ctx.stroke()
    ctx.closePath()

    drawFillCircle(ctx, 238, 83, 9)
    drawFillCircle(ctx, 220, 106, 8)
    drawFillCircle(ctx, 200, 140, 8)

    ctx.restore()

    const size14 = 14 * rate
    // 繪制二維碼
    ctx.drawImage(imageUrl[1], 210 * rate, 120 * rate, 86 * rate, 86 * rate)
    drawText(ctx, '#1D1E1F', '掃碼認識Ta', 216 * rate, 220 * rate, size14)

    // 繪制個人基本信息
    ctx.beginPath()
    const margin56 = 56 * rate
    drawText(ctx, '#1D1E1F', '某個用戶的昵稱', size14, 270 * rate, 20 * rate)
    drawText(ctx, '#1D1E1F', '資料', size14, 300 * rate, size14)
    drawText(ctx, '#1D1E1F', '這是個人信息|什么|換行', margin56 * rate, 300 * rate, size14)
    drawText(ctx, '#1D1E1F', '興趣', size14, 336 * rate, size14)
    drawText(ctx, '#1D1E1F', '唱歌、籃球名段、rap...', margin56 * rate, 336 * rate, size14)
    drawText(ctx, '#1D1E1F', '簡介', size14, 372 * rate, size14)
    drawText(ctx, '#1D1E1F', '這是一段很長的簡介...', margin56 * rate, 372 * rate, size14)


    ctx.draw()
    setTimeout(() => {
      Taro.canvasToTempFilePath({
        x:0,
        y:0,
        width,
        height,
        canvasId: 'shareuser',
        success: (result) => {
          SetImage(result.tempFilePath)
        },
        fail: (err) => {
          Taro.showToast('圖片生成失斱逖铩!')
        }
      })
    }, 600)
  }

  // 處理多張網(wǎng)絡(luò)圖片
  const processMultipleImages = (url) => {
    return new Promise((resolve, reject) => {
      Taro.getImageInfo({
        src: url,
        success: (res) => {
          resolve(res)
        },
        fail: () => {
          Taro.showToast({
            title: '生成失敗!'
          })
        }
      })
    })
  }

  // 繪制實心圓
  const drawFillCircle = (ctx, x, y, r, w) => {
    ctx.beginPath()
    ctx.arc(x * rate, y * rate, r * rate, 0, 2*Math.PI)
    ctx.fillStyle = "#FFE04A";
    ctx.fill()
    ctx.closePath()
  }

  // 繪制文本
  const drawText = (ctx, color, text, x, y, font = 16) => {
    ctx.setFontSize(font)
    ctx.setFillStyle(color)
    ctx.setTextAlign('left')
    ctx.fillText(text, x, y)
    ctx.stroke()
    ctx.closePath()
  }

  // 保存到相冊
  const onClickSaveImage = () => {
    Taro.getSetting({
      success(res) {
        // 如果沒有授權(quán)過伸辟,則要獲取授權(quán)
        if (!res.authSetting['scope.writePhotosAlbum']) {
          Taro.authorize({
            scope: 'scope.writePhotosAlbum',
            success() {
              savePictureSystem()
            },
            fail() { // 用戶拒絕
              Taro.showModal({
                title: '授權(quán)',
                content: '您拒絕了授權(quán)請求麻惶,是否要手動開啟?',
                success: function (res) {
                  if (res.confirm) {
                    Taro.openSetting({
                      success: function (res) {
                        console.log(res.authSetting)
                        res.authSetting = {
                          "scope.userInfo": true,
                          "scope.userLocation": true
                        }
                      }
                    })
                  } else if (res.cancel) {
                    Taro.showToast({
                      title: '保存失斝欧颉窃蹋!',
                      icon: 'close',
                      duration: 2000
                    })
                  }
                }
              })
            }
          })
        } else { // 如果已經(jīng)授權(quán)過卡啰,可以直接保存
          savePictureSystem()
        }
      }
    })
  }

  // 把圖片保存到系統(tǒng)中
  const savePictureSystem = () => {
    Taro.saveImageToPhotosAlbum({
      filePath: saveImage,
      success(res) {
        Taro.showToast({
          title: '保存成功!'
        })
      },
      fail() {
        Taro.showToast({
          title: '保存失敗!',
          icon: 'close',
          duration: 2000
        })
      }
    })
  }

  return (
    <View className="share-user-container">
      <Canvas style={{width: `${width}px`, height: `${height}px`}} canvasId="shareuser" id="shareuser" className="canvas-wrapper"></Canvas>
      <Button onClick={onClickSaveImage}>保存到相冊</Button>

    </View>
  )
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市警没,隨后出現(xiàn)的幾起案子匈辱,更是在濱河造成了極大的恐慌,老刑警劉巖杀迹,帶你破解...
    沈念sama閱讀 212,080評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亡脸,死亡現(xiàn)場離奇詭異,居然都是意外死亡树酪,警方通過查閱死者的電腦和手機浅碾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來续语,“玉大人垂谢,你說我怎么就攤上這事〈眩” “怎么了滥朱?”我有些...
    開封第一講書人閱讀 157,630評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長力试。 經(jīng)常有香客問我徙邻,道長,這世上最難降的妖魔是什么畸裳? 我笑而不...
    開封第一講書人閱讀 56,554評論 1 284
  • 正文 為了忘掉前任鹃栽,我火速辦了婚禮,結(jié)果婚禮上躯畴,老公的妹妹穿的比我還像新娘。我一直安慰自己薇芝,他們只是感情好蓬抄,可當(dāng)我...
    茶點故事閱讀 65,662評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著夯到,像睡著了一般嚷缭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耍贾,一...
    開封第一講書人閱讀 49,856評論 1 290
  • 那天阅爽,我揣著相機與錄音,去河邊找鬼荐开。 笑死付翁,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的晃听。 我是一名探鬼主播百侧,決...
    沈念sama閱讀 39,014評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼砰识,長吁一口氣:“原來是場噩夢啊……” “哼帆吻!你這毒婦竟也來了唇兑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,752評論 0 268
  • 序言:老撾萬榮一對情侶失蹤数焊,失蹤者是張志新(化名)和其女友劉穎辛润,沒想到半個月后膨处,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,212評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡砂竖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,541評論 2 327
  • 正文 我和宋清朗相戀三年真椿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晦溪。...
    茶點故事閱讀 38,687評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡瀑粥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出三圆,到底是詐尸還是另有隱情狞换,我是刑警寧澤,帶...
    沈念sama閱讀 34,347評論 4 331
  • 正文 年R本政府宣布舟肉,位于F島的核電站修噪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏路媚。R本人自食惡果不足惜黄琼,卻給世界環(huán)境...
    茶點故事閱讀 39,973評論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望整慎。 院中可真熱鬧脏款,春花似錦、人聲如沸裤园。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拧揽。三九已至剃盾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間淤袜,已是汗流浹背痒谴。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铡羡,地道東北人积蔚。 一個月前我還...
    沈念sama閱讀 46,406評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像烦周,于是被迫代替她去往敵國和親库倘。 傳聞我的和親對象是個殘疾皇子临扮,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,576評論 2 349

推薦閱讀更多精彩內(nèi)容