需求
在一塊canvas畫布上,初始狀態畫布希麼都沒有,現在,我想給畫布加一點滑鼠事件,用滑鼠在畫布上寫字。具體的效果是滑鼠移動到畫布上任意一點,然後按住滑鼠,移動滑鼠的位置,就可以開始寫字啦!
本文作為學習canvas的第一篇收穫,很多人初學canvas做的第一個demo是實現一個“鐘”,當然,我也實現了一個,不過不講這個,而是講講一個更有趣、也更簡單的玩意。
滑鼠按住繪製軌跡
原理
先簡單分析下思路,首先我們需要一個canvas畫布,然後計算滑鼠在畫布上的位置,給滑鼠綁定onmousedown事件和onmousemove事件,在移動過程中繪製出路徑,鬆開滑鼠的時候,繪製結束。
這個思路雖然很簡單,但是裡面有些地方需要小技巧實現。
1、需要一個html檔案,包含canvas元素。
這是一個寬度800,高度400的畫布。為什麼沒有寫px呢?哦,暫時沒搞懂,canvas文檔推薦的。
<!doctype html><html class="no-js" lang="zh"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <title>canvas學習</title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="manifest" href="site.webmanifest"> <link rel="apple-touch-icon" href="icon.png"> <link rel="stylesheet" href="css/main.css"> </head> <body> <canvas id="theCanvas" width="800" height="400"></canvas> <script src="js/main.js"></script> </body></html>
2、判斷當前環境是否支援canvas。
在main.js中,我們寫一個自執行函數,下面是相容性判斷的程式碼片段,“代碼主體”中將會是實現需求的核心。
(function() { let theCanvas = document.querySelector('#theCanvas') if (!theCanvas || !theCanvas.getContext) { //不相容canvas return false } else { //代碼主體 }})()
3、擷取2d對象。
let context = theCanvas.getContext('2d')
4、擷取當前滑鼠相對於canvas的座標。
為什麼要擷取這個座標呢?因為滑鼠預設是擷取當前視窗的相對座標,而canvas可以位於頁面上的任何位置,所以需要通過計算才能得到真實的滑鼠座標。
將擷取滑鼠相對於canvas的真實座標封裝成了一個函數,如果你覺得抽象,可以在草稿紙上畫圖來理解為什麼要這麼運算。
通常情況下,可以是x - rect.left和y - rect.top。但為什麼實際上卻是x - rect.left * (canvas.width/rect.width)呢?
canvas.width/rect.width表示判斷canvas中存在的縮允許存取為,求出縮放的倍數。
const windowToCanvas = (canvas, x, y) => { //擷取canvas元素距離視窗的一些屬性,MDN上有解釋 let rect = canvas.getBoundingClientRect() //x和y參數分別傳入的是滑鼠距離視窗的座標,然後減去canvas距離視窗左邊和頂部的距離。 return { x: x - rect.left * (canvas.width/rect.width), y: y - rect.top * (canvas.height/rect.height) }}
5、有了第4步的利器函數,我們可以給canvas加上滑鼠事件了!
先給滑鼠綁定按下onmousedown事件,用moveTo繪製座標起點。
theCanvas.onmousedown = function(e) { //獲得滑鼠按下的點相對canvas的座標。 let ele = windowToCanvas(theCanvas, e.clientX, e.clientY) //es6的解構賦值 let { x, y } = ele //繪製起點。 context.moveTo(x, y)}
6、移動滑鼠的時候,沒有滑鼠長按事件,又該怎麼監聽呢?
這裡用到的小技巧是在onmousedown內部再執行一個onmousemove(滑鼠移動)事件,這樣就能監聽按住滑鼠並且移動了。
theCanvas.onmousedown = function(e) { //獲得滑鼠按下的點相對canvas的座標。 let ele = windowToCanvas(theCanvas, e.clientX, e.clientY) //es6的解構賦值 let { x, y } = ele //繪製起點。 context.moveTo(x, y) //滑鼠移動事件 theCanvas.onmousemove = (e) => { //移動時擷取新的座標位置,用lineTo記錄當前的座標,然後stroke繪製上一個點到當前點的路徑 let ele = windowToCanvas(theCanvas, e.clientX, e.clientY) let { x, y } = ele context.lineTo(x, y) context.stroke() }}
7、滑鼠鬆開的時候,不再繪製路徑。
有什麼辦法可以讓onmouseup事件中阻止掉上面監聽的2種事件呢?方法挺多的,設定onmousedown和onmousemove為null算是一種,我這裡用到了“開關”。isAllowDrawLine設定為bool值,來控制函數是否執行,具體代碼可以看下面完整的源碼。
源碼
分為3個檔案,index.html、main.js、utils.js,這裡用到了es6的文法,我是使用parcle配置好了開發環境,所以不會有報錯,如果你直接複製代碼,啟動並執行時候出現錯誤,在無法升級瀏覽器的情況下,可以將es6文法改成es5.
1、index.html
上面已經展示了,不再複述。
2、main.js
import { windowToCanvas } from './utils'(function() { let theCanvas = document.querySelector('#theCanvas') if (!theCanvas || !theCanvas.getContext) { return false } else { let context = theCanvas.getContext('2d') let isAllowDrawLine = false theCanvas.onmousedown = function(e) { isAllowDrawLine = true let ele = windowToCanvas(theCanvas, e.clientX, e.clientY) let { x, y } = ele context.moveTo(x, y) theCanvas.onmousemove = (e) => { if (isAllowDrawLine) { let ele = windowToCanvas(theCanvas, e.clientX, e.clientY) let { x, y } = ele context.lineTo(x, y) context.stroke() } } } theCanvas.onmouseup = function() { isAllowDrawLine = false } }})()
3、utils.js
/** 擷取滑鼠在canvas上的座標* */const windowToCanvas = (canvas, x, y) => { let rect = canvas.getBoundingClientRect() return { x: x - rect.left * (canvas.width/rect.width), y: y - rect.top * (canvas.height/rect.height) }}export { windowToCanvas}