小程式 canvas 生成海報 一次搞掂

萬少發表於2019-09-28

小程式 canvas 生成海報 - 解決螢幕圖片失真,解決無法使用外網圖片

原始碼在最下方

最終結果

2019-04-30090218

canvas(畫布) 元素用於在網頁上繪製圖形。畫布是一個矩形區域,您可以控制其每一畫素。canvas 擁有多種繪製路徑、矩形、圓形、字元以及新增影像的方法。

注意

需要注意的是,目前的canvas可以簡單分為兩種。一種是傳統網頁中的canvas,一種是小程式中的canvas。兩者的功能是完全一樣的。只是標籤的樣式,和 api 略有區別而已。目前我們主要講解小程式中的canvas。

canvas 的應用場景

  1. 線上遊戲

  2. 線上圖表

  3. 頁面特效

  4. 廣告

  5. 圖片合成 小程式中常見

    1. 點我加速


      141e77ea75e046709e45115209ba7d9
    2. 頭像紅旗


      u=1152834417,249646381&fm=11&gp=0

    3. 海報日曆


      ceeedd30772bdcbe211c7852f701a4a
    4. 其他


    1569634970242

簡單體驗

我們來畫一條直線

在canvas中,把畫直線的步驟分解為以下幾步:

  1. 編寫標籤
  2. 獲取畫布例項
  3. 定起點
  4. 連線終點
  5. 連線 (也叫描邊)
  6. 上色

編寫標籤

預設的寬高 為 300px * 150 px

不同於普通的標籤,必須要提供一個屬性 canvas-id,用於在 js中獲取該物件(不是dom物件!!!)

<canvas canvas-id="firstCanvas"></canvas>
複製程式碼

獲取畫布例項

通過 canvas-id 來獲取

該例項 不是dom元素,可以理解為另一種物件如 Math Date String等即可

index.js

Page({
  onLoad() {
    // 1 獲取畫布上下文物件
    const context = wx.createCanvasContext("firstCanvas");
    console.log(context);
  }
})
複製程式碼

點起點

在canvas中,存在一個座標系 如下圖:

我們在canvas中所講的座標都是相對於canvas內部座標而言

繪圖1

定個起點

    // 定起點
    context.moveTo(10, 10);
複製程式碼

定終點

	// 定終點
	context.lineTo(300,150);
複製程式碼

連線

	// 連線
    context.stroke();
複製程式碼

上色

    // 上色
    context.draw();
複製程式碼

完整程式碼

index.wxml

<!-- 1 寫標籤 -->
<canvas canvas-id="firstCanvas"></canvas>
複製程式碼

index.js

Page({
  onLoad() {
    // 2 獲取畫布上下文物件
    const context = wx.createCanvasContext("firstCanvas");
    // 3 定起點
    context.moveTo(10, 10);
    // 4 定終點
    context.lineTo(300,150);
    // 5 連線
    context.stroke();
    // 6 上色
    context.draw();
  }
})
複製程式碼

效果

1569657291546

內建的其他規則圖形

canvas中還封裝了畫規則圖形的方法,如:

  1. 畫空心的矩形

  2. 畫圓弧

  3. 畫實心的矩形

  4. 畫文字(把字串畫上去)

畫矩形

CanvasContext.strokeRect(number x, number y, number width, number height)

CanvasContext.strokeRect(畫在畫布的X,畫在畫布的Y,畫多寬,畫多高)

    // 1 獲取畫布上下文物件
    const context = wx.createCanvasContext("firstCanvas");
    // 2 呼叫canvas內建的畫“矩形”的方法
    context.strokeRect(10, 10, 100, 100);
    // 3 上色 
    context.draw();
複製程式碼

效果

1569657340868

畫圓弧

CanvasContext.arc(number x, number y, number r, number sAngle, number eAngle, boolean counterclockwise)

CanvasContext.arc(圓心的橫座標X,圓心的縱座標Y, 半徑的長度, 開始的弧度, 結束的弧度, ?是否反向來畫)

程式碼

  drawArc() {
    // 1 獲取畫布上下文物件
    const context = wx.createCanvasContext("firstCanvas");
    // context.arc(圓心的橫座標X,圓心的縱座標Y, 半徑的長度, 開始的弧度, 結束的弧度);
    // 2 呼叫內建的畫 “圓弧” 的方法
    context.arc(100, 100, 100, this.angleToArc(0), this.angleToArc(90));
    // 3 開始描邊
    context.stroke();
    // 4 上色
    context.draw();
  },
  /**
   * 將角度轉為弧度
   * @param {number} angle 角度
   */
  angleToArc(angle) {
    return angle * Math.PI / 180;
  }
複製程式碼

效果

1569657957559

畫實心的矩形

CanvasContext.fillRect(number x, number y, number width, number height)

    // 1 獲取畫布上下文物件
    const context = wx.createCanvasContext("firstCanvas");
    // 2 呼叫canvas內建的 畫填充 “矩形”的方法
    context.fillRect(10, 10, 100, 100);
    // 3 上色 
    context.draw();
複製程式碼

效果

1569658217231

畫文字

CanvasContext.strokeText(string text, number x, number y, number maxWidth)

CanvasContext.strokeText(要繪製的文字, 文字起始點的 x 軸座標, number y, 需要繪製的最大寬度,可選)

程式碼

    // 1 獲取畫布上下文物件
    const context = wx.createCanvasContext("firstCanvas");
    // 2 畫 “文字”
    context.strokeText("hello world", 100, 100);
    // 3 上色 
    context.draw();
複製程式碼

效果

1569658659539


設定樣式

經過以上的演示我們也發現,線條的顏色一直是黑色,這肯定是無法滿足我們騷跳的心的。現在來學習一下關於設定canvas線條樣式相關API。

  1. 設定線條顏色
  2. 設定線條粗細
  3. 設定填充顏色
  4. 設定文字大小

設定線條顏色

**特別要注意 **,setStrokeStyle是個函式,1.9.90版本後停止維護,使用以下的方式來修改。

  1. CanvasContext.setStrokeStyle("red") 已過時,不推薦
  2. CanvasContext.strokeStyle="red"; 正解

程式碼

    const context = wx.createCanvasContext("firstCanvas");
    context.moveTo(10, 10);
    context.lineTo(300, 150);
    // 5 修改顏色 需要在stroke之前修改
    context.strokeStyle = "red";
    context.stroke();
    context.draw();
複製程式碼

效果

1569663241332


設定線條粗細

**特別要注意 **,setLineWidth 是個函式,1.9.90版本後停止維護,使用以下的方式來修改。

  1. CanvasContext.setLineWidth(20) 已過時,不推薦
  2. CanvasContext.lineWidth=20; 正解

程式碼

    const context = wx.createCanvasContext("firstCanvas");
    context.moveTo(10, 10);
    context.lineTo(300, 150);
    // 設定線條寬度
    context.lineWidth = 20;
    context.stroke();
    context.draw();
複製程式碼

效果

1569663745450


設定填充顏色

**特別要注意 **,setFillStyle 是個函式,1.9.90版本後停止維護,使用以下的方式來修改。

  1. CanvasContext.setFillStyle("red") 已過時,不推薦
  2. CanvasContext.fillStyle="red"; 正解

程式碼

    const context = wx.createCanvasContext("firstCanvas");
    // 設定填充顏色
    context.fillStyle = "red";
    context.fillRect(10, 10, 100, 100);
    context.draw();
複製程式碼

效果

1569663956116


設定文字大小

**特別要注意 **,setFontSize 是個函式,1.9.90版本後停止維護,使用以下的方式來修改。

  1. CanvasContext.setFontSize("20") 已過時,不推薦
  2. CanvasContext.font="sans-serif"; 正解
  3. font 當前字型樣式的屬性。符合 CSS font 語法 的 DOMString 字串,至少需要提供字型大小和字型族名。預設值為 10px sans-serif。

程式碼

    const context = wx.createCanvasContext("firstCanvas");
    // 必須要同時提供 字號 和 字型
    context.font="10px  sans-serif";
    context.strokeText("10px", 10, 10);
    // 必須要同時提供 字號 和 字型
    context.font="50px  sans-serif";
    context.strokeText("50px", 50, 100);
    // 必須要同時提供 字號 和 字型
    context.font="80px  sans-serif";
    context.strokeText("80px", 80, 180);
    context.draw();
複製程式碼

效果

1569664436438

進階

在本環節主要講解稍微複雜一點的功能。要實現以下功能

但是需要先做一點技術鋪墊

主要用到的api有:

  1. 獲取系統資訊
  2. 選擇相簿圖片
  3. 獲取網路圖片資訊
  4. canvas 描繪 圖片到畫布上
  5. 將畫布儲存成一張圖片
  6. 將圖片下載到本地

1569678652709

基本API

以下api是實現以上案例所必須的

獲取系統資訊

獲取螢幕大小、裝置畫素比等

程式碼

wx.getSystemInfo({
  success (res) {
    console.log(res.model)
    console.log(res.pixelRatio)
    console.log(res.windowWidth)
    console.log(res.windowHeight)
    console.log(res.language)
    console.log(res.version)
    console.log(res.platform)
  }
})
複製程式碼

選擇相簿圖片

從本地相簿選擇圖片或使用相機拍照

程式碼

wx.chooseImage({
  count: 1,// 最多可以選擇的圖片張數
  sizeType: ['original', 'compressed'],// 所選的圖片的尺寸
  sourceType: ['album', 'camera'],//  選擇圖片的來源
  success (res) {
    // tempFilePath可以作為img標籤的src屬性顯示圖片
    const tempFilePaths = res.tempFilePaths
  }
})
複製程式碼

程式碼

wx.getSystemInfo({
  success (res) {
    console.log(res.model)
    console.log(res.pixelRatio)
    console.log(res.windowWidth)
    console.log(res.windowHeight)
    console.log(res.language)
    console.log(res.version)
    console.log(res.platform)
  }
})
複製程式碼

獲取網路圖片資訊

獲取圖片資訊。網路圖片需先配置download域名才能生效。

canvas提供了將圖片畫到畫布上的功能,但是要求所提供的圖片必須是外網下的圖片

因此可以藉助該方法將網路圖片變成本地圖片,同時返回該圖片的資訊

程式碼

wx.getImageInfo({
  src: 'cloud://c-73e071.632d-c-73e071/92637.jpg',
  success (res) {
    console.log(res.width)
    console.log(res.height)
  }
})
複製程式碼

繪製影像到畫布

不能使用本地圖片,要使用外網圖片的 必須要先 使用 wx.getImageInfo 下載到本地

有三個版本的寫法:

  • drawImage(imageResource, dx, dy)
  • drawImage(imageResource, dx, dy, dWidth, dHeight)
  • drawImage(imageResource, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
  • 說明drawImage(圖片路徑, 原圖的x, 原圖的y, 原圖的寬度, 原圖的高度, 畫布的x, 畫布的y, 畫多寬, 畫多高)

程式碼

context.drawImage('xxxx.jpg', 0, 0,100, 100);
複製程式碼

將畫布儲存成一張圖片

draw() 回撥裡呼叫該方法才能保證圖片匯出成功

程式碼

wx.canvasToTempFilePath({
  x: 100,
  y: 200,
  width: 50,
  height: 50,
  destWidth: 100,
  destHeight: 100,
  canvasId: 'myCanvas',
  success(res) {
    console.log(res.tempFilePath)
  }
})
複製程式碼

將圖片下載到本地

儲存圖片到系統相簿

程式碼

wx.saveImageToPhotosAlbum({
  success(res) { }
})
複製程式碼

案例實現

其實要實現一樣案例,最麻煩的不是這些API的呼叫,而是如何根據不同的圖片,合成比例合適不模糊的圖片

為什麼說比例合適

因為在canvas中,只支援 px 單位,那麼在使用javascript來描繪圖片時,就不存在 rpxvw%這些相對單位了。只能依靠手動來計算。如,在 canvas中,畫出一個大小為 螢幕寬的一半 螢幕高的一半的矩形?

為什麼說不模糊

問題的原因還是因為 手機的螢幕 都是高清屏,具體的原因可以參照 連結

如我們想要生成圖片大小為 100px * 100px,那麼就需要將 canvas的大小設定為 width = 圖片的寬度 * 裝置畫素比

height = 圖片的高度 * 裝置畫素比

檔案目錄

  1. index 首頁
  2. result 合成圖片的頁面

1569681221628

首頁 index

index

1569681395410

pages/index/index.wxml

<!-- 用來顯示 被選擇的圖片的 -->
<image mode="widthFix" src="{{src}}"></image>
<!-- 選擇相簿圖片 -->
<button bindtap="handleTap">選擇圖片</button>
<!-- 跳轉到 結果頁面 -->
<button bindtap="handleCreateFlag">生成小紅旗</button>
複製程式碼

pages/index/index.js

主要實現3個功能

  1. 點選 “選擇圖片” 將選擇的圖片列印到頁面上
  2. 將被 選擇的圖片 顯示的頁面上
  3. 點選 “生成紅旗”,跳轉到結果頁面(在結果頁面完成生成)
Page({
  data: {
    src: ""
  },
  // 選擇圖片
  handleTap() {
    wx.chooseImage({
      count: 1,
      sizeType: ['original', 'compressed'],
      sourceType: ['album', 'camera'],
      success: (result) => {
        this.setData({
          src: result.tempFilePaths[0]
        })
        // 儲存圖片路徑
        wx.setStorageSync('src', this.data.src);
      }
    });
  },
  // 生成紅旗
  handleCreateFlag() {
    // 跳轉到結果頁面
    wx.navigateTo({
      url: '/pages/result/index'
    });
  }
})
複製程式碼

結果頁面 result

result

1569681567259

result/index.wxml

3個標籤

  1. canvas 標籤,通過定位將其隱藏
  2. image 標籤,用來顯示合成的圖片
  3. button 標籤,用來點選 下載圖片
<!-- canvas 標籤-->
<canvas class="cas" style="width:{{canvasWidth}};height:{{canvasHeight}};" canvas-id="firstCanvas"></canvas>
<!-- 用來顯示 合成成功的圖片 -->
<image class="res_image" mode="widthFix" src="{{resSrc}}"></image>
<!-- 點選下載圖片 -->
<button bindtap="handleSave">下載圖片</button>
複製程式碼

result/index.wxss

兩個樣式

  1. 把canvas藏起來(因為是 原生元件,所以它的層級比一般的標籤都要高(定位+zindex也無法解決))
  2. 設定圖片標籤的樣式
page {
  overflow-x: hidden;
  overflow-y: auto;
  width: 100vw;
  height: 100vh;
}
.cas {
  position: absolute;
  top: 1000vw;
  left: 1000vh;
  z-index: -1;
  opacity: 0;
}
.res_image {
  width: 100%;
  display: block;
}

複製程式碼

result/index.js

易錯點:

  1. 外網的圖片,需要先將圖片伺服器新增到白名單中(否則真機除錯會失敗)
  2. 沒有動態設定 canvas的寬和高(參照第29、31行)

import regeneratorRuntime from '../../lib/runtime/runtime';
import { getImageInfo, canvasToTempFilePath, saveImageToPhotosAlbum } from "../../wxAsync/index.js";
Page({
  data: {
    // 預設的canvas的寬度
    canvasWidth: 1,
    // 預設的canvas高度
    canvasHeight: 1,
    // 最終生成的圖片路徑
    resSrc: ""
  },
  // 全域性變數
  saveImgSrc: "",
  async onLoad() {
    // 紅旗圖片
    const flagSrc = "https://632d-c-73e071-1252056196.tcb.qcloud.la/3434.jpg?sign=a4f1c2106d1e61551829c2f99820c0ba&t=1569678566";
    // const baseSrc = "https://632d-c-73e071-1252056196.tcb.qcloud.la/92637.jpg?sign=8952d1eaa69a35510418fe25dc25d6c5&t=1569678606";
    // 上個頁面選擇的圖片路徑 柯南圖片
    const baseSrc = wx.getStorageSync("src");
    // 裝置畫素比
    const { pixelRatio } = wx.getSystemInfoSync();

    // 獲取 畫布例項
    const context = wx.createCanvasContext('firstCanvas');
    console.log(context);
    // 下載到本地的 柯南圖片
    const baseImg = await getImageInfo(baseSrc);
    // 下載到本地的 紅旗圖片
    const flagImg = await getImageInfo(flagSrc);
    // 將canvas的寬度設定中 圖片的寬度
    const canvasWidth = baseImg.width + "px";
    // 將canvas的寬度設定中 圖片的高度
    const canvasHeight = baseImg.height + "px";
    //  setData 函式用於將資料從邏輯層傳送到檢視層(非同步),同時改變對應的 this.data 的值(同步)。
    // 因此需要將 描繪 圖片的步驟寫在回撥中,否則 真機除錯有bug!
    this.setData({ canvasWidth, canvasHeight }, () => {
      // 如果個別機型出現圖片失敗錯誤,可以加上定時器。
      setTimeout(() => {
        // 先將柯南 描繪到畫布上
        context.drawImage(baseImg.path, 0, 0, baseImg.width, baseImg.height);
        // 把紅旗 描繪到畫布上
        context.drawImage(flagImg.path, baseImg.width - (pixelRatio * 50), baseImg.height - (pixelRatio * 50), (pixelRatio * 50), (pixelRatio * 50));
        context.draw(true, async () => {
          // 將 畫布生成 成圖片
          const res1 = await canvasToTempFilePath({
            canvasId: "firstCanvas"
          });
          // 讓圖片顯示 合成後的效果
          this.setData({ resSrc: res1.tempFilePath })
          // 儲存起來,當點選儲存圖片時呼叫
          this.saveImgSrc = res1.tempFilePath;
        });
      }, 100);
    });
  },

  // 點選儲存圖片
  handleSave() {
    saveImageToPhotosAlbum(this.saveImgSrc);
  }
})

複製程式碼

github地址

https://github.com/itcastWsy/AppletPoster.git
複製程式碼

相關文章