小程式 canvas
生成海報 - 解決螢幕圖片失真,解決無法使用外網圖片
原始碼在最下方
最終結果
canvas(畫布) 元素用於在網頁上繪製圖形。畫布是一個矩形區域,您可以控制其每一畫素。canvas 擁有多種繪製路徑、矩形、圓形、字元以及新增影像的方法。
注意
需要注意的是,目前的canvas可以簡單分為兩種。一種是傳統網頁中的canvas,一種是小程式中的canvas。兩者的功能是完全一樣的。只是標籤的樣式,和 api
略有區別而已。目前我們主要講解小程式中的canvas。
canvas 的應用場景
簡單體驗
我們來畫一條直線
在canvas中,把畫直線的步驟分解為以下幾步:
- 編寫標籤
- 獲取畫布例項
- 定起點
- 連線終點
- 連線 (也叫描邊)
- 上色
編寫標籤
預設的寬高 為 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內部座標而言
定個起點
// 定起點
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();
}
})
複製程式碼
效果
內建的其他規則圖形
canvas中還封裝了畫規則圖形的方法,如:
畫矩形
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();
複製程式碼
效果
畫圓弧
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;
}
複製程式碼
效果
畫實心的矩形
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();
複製程式碼
效果
畫文字
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();
複製程式碼
效果
設定樣式
經過以上的演示我們也發現,線條的顏色一直是黑色,這肯定是無法滿足我們騷跳的心的。現在來學習一下關於設定canvas線條樣式相關API。
- 設定線條顏色
- 設定線條粗細
- 設定填充顏色
- 設定文字大小
設定線條顏色
**特別要注意 **,setStrokeStyle
是個函式,1.9.90版本後停止維護,使用以下的方式來修改。
已過時,不推薦CanvasContext.setStrokeStyle("red")
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();
複製程式碼
效果
設定線條粗細
**特別要注意 **,setLineWidth
是個函式,1.9.90版本後停止維護,使用以下的方式來修改。
已過時,不推薦CanvasContext.setLineWidth(20)
CanvasContext.lineWidth=20;
正解
程式碼
const context = wx.createCanvasContext("firstCanvas");
context.moveTo(10, 10);
context.lineTo(300, 150);
// 設定線條寬度
context.lineWidth = 20;
context.stroke();
context.draw();
複製程式碼
效果
設定填充顏色
**特別要注意 **,setFillStyle
是個函式,1.9.90版本後停止維護,使用以下的方式來修改。
已過時,不推薦CanvasContext.setFillStyle("red")
CanvasContext.fillStyle="red";
正解
程式碼
const context = wx.createCanvasContext("firstCanvas");
// 設定填充顏色
context.fillStyle = "red";
context.fillRect(10, 10, 100, 100);
context.draw();
複製程式碼
效果
設定文字大小
**特別要注意 **,setFontSize
是個函式,1.9.90版本後停止維護,使用以下的方式來修改。
已過時,不推薦CanvasContext.setFontSize("20")
CanvasContext.font="sans-serif";
正解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();
複製程式碼
效果
進階
在本環節主要講解稍微複雜一點的功能。要實現以下功能
但是需要先做一點技術鋪墊
主要用到的api有:
- 獲取系統資訊
- 選擇相簿圖片
- 獲取網路圖片資訊
- canvas 描繪 圖片到畫布上
- 將畫布儲存成一張圖片
- 將圖片下載到本地
基本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
來描繪圖片時,就不存在 rpx
、vw
、%
這些相對單位了。只能依靠手動來計算。如,在 canvas中,畫出一個大小為 螢幕寬的一半 螢幕高的一半的矩形?
為什麼說不模糊
問題的原因還是因為 手機的螢幕 都是高清屏,具體的原因可以參照 連結
如我們想要生成圖片大小為 100px * 100px,那麼就需要將 canvas的大小設定為 width = 圖片的寬度 * 裝置畫素比
height = 圖片的高度 * 裝置畫素比
檔案目錄
index
首頁result
合成圖片的頁面
首頁 index
index
pages/index/index.wxml
<!-- 用來顯示 被選擇的圖片的 -->
<image mode="widthFix" src="{{src}}"></image>
<!-- 選擇相簿圖片 -->
<button bindtap="handleTap">選擇圖片</button>
<!-- 跳轉到 結果頁面 -->
<button bindtap="handleCreateFlag">生成小紅旗</button>
複製程式碼
pages/index/index.js
主要實現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
result/index.wxml
3個標籤
- canvas 標籤,通過定位將其隱藏
- image 標籤,用來顯示合成的圖片
- 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
兩個樣式
- 把canvas藏起來(因為是 原生元件,所以它的層級比一般的標籤都要高(定位+zindex也無法解決))
- 設定圖片標籤的樣式
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
易錯點:
- 外網的圖片,需要先將圖片伺服器新增到白名單中(否則真機除錯會失敗)
- 沒有動態設定 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
複製程式碼