天天看點

Web前端項目實戰——使用RequireJS開發簡易繪圖程式

前言

RequireJS的出現讓前端代碼子產品化變得容易,目前端項目越來越大,代碼越來越多的時候,子產品化代碼讓項目結構更清晰,不僅在開發時讓我們的思路更清晰,而且後期維護起來也更容易。下面是我學習RequireJS後使用RequireJS開發的一款簡易繪圖程式,運作在浏覽器中如下圖所示:

Web前端項目實戰——使用RequireJS開發簡易繪圖程式

如果你對RequireJS還不是很了解,可以看我的RequireJS學習筆記:http://blog.csdn.net/yubo_725/article/details/52913853

開始

這個簡易繪圖程式的項目結構如下圖所示:

Web前端項目實戰——使用RequireJS開發簡易繪圖程式

其中index.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;
        };
    };
})
           

上面的代碼中使用define定義了點這個子產品,在回調函數中傳回了一個類,該類有兩個參數x,y,還有一個equals方法用于比較兩個點是否相等。

要使用這個子產品,我們可以使用如下代碼:

require(['app/point'], function(Point) {
    //建立一個點類的對象
    var point = new Point(, );
})
           

這裡需要注意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();
        }
    }
})
           

直線子產品的定義跟點子產品的定義類似,都是在define的回調函數中傳回一個類,這個直線類的構造方法中有兩個點類的參數,代表直線的起點和終點,直線類還有一個drawMe方法,通過傳入一個context對象,将自身畫出來。

rect.js:

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是矩形左上角的點的坐标,是一個point類,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, ,  * Math.PI);
            context.closePath();
            context.stroke();
        }
    }
})
           

其中startPoint代表圓形所在的矩形的左上角的點的坐标,radius代表圓的半徑,drawMe方法是畫圓的方法。

在以上幾個子產品中,直線類、矩形類、圓形類都包含有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 = ; 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: px;
        }
    </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 = ; // 0, 1, 2分别代表畫直線、畫矩形、畫圓

            var downPoint = new Point(, ),  
                movePoint = new Point(, ), 
                upPoint = new Point(, );
            var line;
            var rect;
            var arc;

            /** 處理滑鼠按下的事件 */
            function handleMouseDown(event) {
                downPoint.x = event.clientX - canvasRect.left;
                downPoint.y = event.clientY - canvasRect.top;
                if(selection === ) {  
                    line = new Line(downPoint, downPoint);
                    line.startPoint = downPoint;
                } else if(selection === ) {
                    rect = new Rect(new Point(downPoint.x, downPoint.y), , );
                } else if(selection === ) {
                    arc = new Arc(new Point(downPoint.x, downPoint.y), );
                }
                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 == ) {
                        line.endPoint = movePoint;  
                        Utils.drawLine(context, line);
                    } else if(selection == ) {
                        rect.width = movePoint.x - downPoint.x;
                        rect.height = movePoint.y - downPoint.y;
                        Utils.drawRect(context, rect);
                    } else if(selection == ) {
                        var x = movePoint.x - downPoint.x;
                        var y = movePoint.y - downPoint.y;
                        arc.radius = x > y ? (y / ) : (x / );
                        if(arc.radius < ) { 
                            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 == ) {
                        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 == ) {
                        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 == ) {
                        Utils.addHistory(new Arc(new Point(arc.startPoint.x, arc.startPoint.y), arc.radius));
                    }
                    clearCanvas();
                    Utils.drawHistory(context);
                }
            }

            /** 清空畫布 */
            function clearCanvas() {
                context.clearRect(, , 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 = ;
                    showHint('目前操作:畫直線');
                } else if(domID == 'btn-rect') {
                    selection = ;
                    showHint('目前操作:畫矩形');
                } else if(domID == 'btn-oval') {
                    selection = ;
                    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中的位置。

源碼

本篇部落格中的源碼已托管到github,點選這裡檢視源碼

已知bug

在畫圓形時需要滑鼠從左上角拖到右下角畫圓,如果不是這樣,圓的位置會有問題

繼續閱讀