前言
RequireJS的出現讓前端程式碼模組化變得容易,當前端專案越來越大,程式碼越來越多的時候,模組化程式碼讓專案結構更清晰,不僅在開發時讓我們的思路更清晰,而且後製維護起來也更容易。以下是我學習RequireJS後使用RequireJS開發的簡易繪圖程序,運行在瀏覽器中如下圖所示:
開始
這個簡易繪圖程式的專案架構如下圖所示:
這個簡易繪圖程式的專案結構如下圖所示:這個簡易繪圖程式的專案結構如下圖所示: .html是專案的主頁,js目錄下存放所有js文件,js/app目錄為我們自訂的模組文件,js/lib目錄中暫時沒有文件,當我們的專案裡用到一些其他前端框架如jquery等時,js/lib目錄就存放這些框架的js文件,js/main.js為requirejs的設定文件,主要是配置一些路徑,js/require.min.js是RequireJS框架的文件。下面請跟我一步一步完成這個簡易的繪圖程式吧!
一、設定requirejs
本專案的設定檔程式碼放在js/main.js中,程式碼內容如下:
require.config({ baseUrl: 'js/lib', paths: { app: '../app' } })
主要是設定了專案目錄為'js/lib'配置了一個名為'app'的路徑,路徑為'../app',即'js/app'目錄。
二、寫模組程式碼
這個專案中的模組主要有以下幾個:point.js, line.js, rect.js, arc.js, utils.js,下面一一說明:
point.js :
point.js這個模組代表一個點(x, y),程式碼如下:
/** 点 */ define(function() { return function(x, y) { this.x = x; this.y = y; this.equals = function(point) { return this.x === point.x && this.y === point.y; }; }; })
要使用這個模組,我們可以使用以下程式碼:
require(['app/point'], function(Point) { //新建一个点类的对象 var point = new Point(3, 5); })
這裡需要注意require()函數的第一個參數是一個數組,回呼函數中的Point就代表了我們的點類,透過new Point()的方式建立點類別的物件。
line.js:
line.js模組代表的是一條直線,程式碼如下:
/** 直线 */ define(function() { return function(startPoint, endPoint) { this.startPoint = startPoint; this.endPoint = endPoint; this.drawMe = function(context) { context.strokeStyle = "#000000"; context.beginPath(); context.moveTo(this.startPoint.x, this.startPoint.y); context.lineTo(this.endPoint.x, this.endPoint.y); context.closePath(); context.stroke(); } } })
直線模組的定義類,這個直線類的建構方法中有兩個點類的參數,代表直線的起點和終點,直線類還有一個drawMe方法,透過傳入一個context對象,將自己畫出來。
rect.js:
/** 矩形 */ define(['app/point'], function() { return function(startPoint, width, height) { this.startPoint = startPoint; this.width = width; this.height = height; this.drawMe = function(context) { context.strokeStyle = "#000000"; context.strokeRect(this.startPoint.x, this.startPoint.y, this.width, this.height); } } })
其中startPoint是矩形左上角的點的座標,是一個points,width和height分別代表矩形的寬高,同時還有一個drawMe方法將矩形自身畫出來。
arc.js:
arc.js模組代表一個圓形,代碼如下:
/** 圆形 */ define(function() { return function(startPoint, radius) { this.startPoint = startPoint; this.radius = radius; this.drawMe = function(context) { context.beginPath(); context.arc(this.startPoint.x, this.startPoint.y, this.radius, 0, 2 * Math.PI); context.closePath(); context.stroke(); } } })
在以上幾個模組中,直線類別、矩形類別、圓形類別都包含有drawMe()方法,這裡涉及到了canvas繪圖的知識,如果有不太清楚的,可以查一下文件:HTML 5 Canvas 參考手冊
utils.js
utils.js模組主要是用來處理各種圖形繪製的工具類,包括直線、矩形、圓形的繪製,也包括記錄繪製軌跡、清除繪製軌跡,程式碼如下:
/** 管理绘图的工具 */ define(function() { var history = []; //用来保存历史绘制记录的数组,里面存储的是直线类、矩形类或者圆形类的对象 function drawLine(context, line) { line.drawMe(context); } function drawRect(context, rect) { rect.drawMe(context); } function drawArc(context, arc) { arc.drawMe(context); } /** 添加一条绘制轨迹 */ function addHistory(item) { history.push(item); } /** 画出历史轨迹 */ function drawHistory(context) { for(var i = 0; i < history.length; i++) { var obj = history[i]; obj.drawMe(context); } } /** 清除历史轨迹 */ function clearHistory() { history = []; } return { drawLine: drawLine, drawRect: drawRect, drawArc: drawArc, addHistory: addHistory, drawHistory: drawHistory, clearHistory: clearHistory }; })
三、編寫介面程式碼,處理滑鼠事件
上面已經將本次簡易繪圖程式的模組都定義完了,在繪製圖形時用到的也就是上面幾個模組,下面要開始編寫主介面的程式碼了,主介面裡包含四個按鈕,還有一塊大的畫布用來繪圖,下面直接上index.html檔案的程式碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>简易绘图程序</title> <style type="text/css"> canvas { background-color: #ECECEC; cursor: default; /** 鼠标设置成默认的指针 */ } .tool-bar { margin-bottom: 10px; } </style> </head> <body> <div class="tool-bar"> <button id="btn-line">画直线</button> <button id="btn-rect">画矩形</button> <button id="btn-oval">画圆形</button> <button id="btn-clear">清空画布</button> <span id="hint" style="color: red;">当前操作:画直线</span> </div> <canvas id="canvas" width="800" height="600"></canvas> <script type="text/javascript" src="js/require.min.js" data-main="js/main"></script> <script type="text/javascript"> require(['app/point', 'app/line', 'app/rect', 'app/arc', 'app/utils'], function(Point, Line, Rect, Arc, Utils) { var canvas = document.getElementById("canvas"); var context = canvas.getContext('2d'); var canvasRect = canvas.getBoundingClientRect(); //得到canvas所在的矩形 canvas.addEventListener('mousedown', handleMouseDown); canvas.addEventListener('mousemove', handleMouseMove); canvas.addEventListener('mouseup', handleMouseUp); bindClick('btn-clear', menuBtnClicked); bindClick('btn-line', menuBtnClicked); bindClick('btn-rect', menuBtnClicked); bindClick('btn-oval', menuBtnClicked); var mouseDown = false; var selection = 1; // 0, 1, 2分别代表画直线、画矩形、画圆 var downPoint = new Point(0, 0), movePoint = new Point(0, 0), upPoint = new Point(0, 0); var line; var rect; var arc; /** 处理鼠标按下的事件 */ function handleMouseDown(event) { downPoint.x = event.clientX - canvasRect.left; downPoint.y = event.clientY - canvasRect.top; if(selection === 0) { line = new Line(downPoint, downPoint); line.startPoint = downPoint; } else if(selection === 1) { rect = new Rect(new Point(downPoint.x, downPoint.y), 0, 0); } else if(selection === 2) { arc = new Arc(new Point(downPoint.x, downPoint.y), 0); } mouseDown = true; } /** 处理鼠标移动的事件 */ function handleMouseMove(event) { movePoint.x = event.clientX - canvasRect.left; movePoint.y = event.clientY - canvasRect.top; if(movePoint.x == downPoint.x && movePoint.y == downPoint.y) { return ; } if(movePoint.x == upPoint.x && movePoint.y == upPoint.y) { return ; } if(mouseDown) { clearCanvas(); if(selection == 0) { line.endPoint = movePoint; Utils.drawLine(context, line); } else if(selection == 1) { rect.width = movePoint.x - downPoint.x; rect.height = movePoint.y - downPoint.y; Utils.drawRect(context, rect); } else if(selection == 2) { var x = movePoint.x - downPoint.x; var y = movePoint.y - downPoint.y; arc.radius = x > y ? (y / 2) : (x / 2); if(arc.radius < 0) { arc.radius = -arc.radius; } arc.startPoint.x = downPoint.x + arc.radius; arc.startPoint.y = downPoint.y + arc.radius; Utils.drawArc(context, arc); } Utils.drawHistory(context); } } /** 处理鼠标抬起的事件 */ function handleMouseUp(event) { upPoint.x = event.clientX - canvasRect.left; upPoint.y = event.clientY - canvasRect.top; if(mouseDown) { mouseDown = false; if(selection == 0) { line.endPoint = upPoint; if(!downPoint.equals(upPoint)) { Utils.addHistory(new Line(new Point(downPoint.x, downPoint.y), new Point(upPoint.x, upPoint.y))); } } else if(selection == 1) { rect.width = upPoint.x - downPoint.x; rect.height = upPoint.y - downPoint.y; Utils.addHistory(new Rect(new Point(downPoint.x, downPoint.y), rect.width, rect.height)); } else if(selection == 2) { Utils.addHistory(new Arc(new Point(arc.startPoint.x, arc.startPoint.y), arc.radius)); } clearCanvas(); Utils.drawHistory(context); } } /** 清空画布 */ function clearCanvas() { context.clearRect(0, 0, canvas.width, canvas.height); } /** 菜单按钮的点击事件处理 */ function menuBtnClicked(event) { var domID = event.srcElement.id; if(domID === 'btn-clear') { clearCanvas(); Utils.clearHistory(); } else if(domID == 'btn-line') { selection = 0; showHint('当前操作:画直线'); } else if(domID == 'btn-rect') { selection = 1; showHint('当前操作:画矩形'); } else if(domID == 'btn-oval') { selection = 2; showHint('当前操作:画圆形'); } } function showHint(msg) { document.getElementById('hint').innerHTML = msg; } /** 给对应id的dom元素绑定点击事件 */ function bindClick(domID, handler) { document.getElementById(domID).addEventListener('click', handler); } }); </script> </body> </html>
index.html檔案中的程式碼比較多,但最主要的程式碼還是對滑鼠按下、移動、抬起三種事件的監聽和處理,另外,取得滑鼠在canvas中的座標位置需要注意一點:由於event物件中取得的clientX和clientY是滑鼠相對於頁面的座標,為了取得滑鼠在canvas中的座標,需要取得canvas所在的矩形區域,然後用clientX-canvas.left,clientY-canvas.top,來取得滑鼠在canvas中的位置。
已知bug