RequireJS简易绘图程序开发


Posted in Javascript onOctober 28, 2016

前言

RequireJS的出现让前端代码模块化变得容易,当前端项目越来越大,代码越来越多的时候,模块化代码让项目结构更清晰,不仅在开发时让我们的思路更清晰,而且后期维护起来也更容易。下面是我学习RequireJS后使用RequireJS开发的一款简易绘图程序,运行在浏览器中如下图所示:

RequireJS简易绘图程序开发

如果你对RequireJS还不是很了解,可以看我的RequireJS学习笔记:http://blog.csdn.net/yubo_725/article/details/52913853

开始

这个简易绘图程序的项目结构如下图所示:

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(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();
    }
  }
})

直线模块的定义跟点模块的定义类似,都是在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, 0, 2 * 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 = 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中的位置。

源码

本篇博客中的源码已托管到github,点击这里查看源码

已知bug

在画圆形时需要鼠标从左上角拖到右下角画圆,如果不是这样,圆的位置会有问题。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
关于img的href和src取变量及赋值的方法
Apr 28 Javascript
jQuery提交多个表单的小技巧
Jul 27 Javascript
JavaScript原型及原型链终极详解
Jan 04 Javascript
基于JS实现新闻列表无缝向上滚动实例代码
Jan 22 Javascript
浅析JS获取url中的参数实例代码
Jun 14 Javascript
js生成随机数(指定范围)的实例代码
Jul 10 Javascript
onmouseover事件和onmouseout事件全面理解
Aug 15 Javascript
JavaScript中访问id对象 属性的方式访问属性(实例代码)
Oct 28 Javascript
详解Vue基于 Nuxt.js 实现服务端渲染(SSR)
Apr 05 Javascript
pm2启动ssr失败的解决方法
Jun 29 Javascript
js String.prototype.trim字符去前后空格的扩展
Aug 23 Javascript
解决vue中provide inject的响应式监听
Apr 19 Vue.js
jquery配合.NET实现点击指定绑定数据并且能够一键下载
Oct 28 #Javascript
微信小程序 页面跳转传参详解
Oct 28 #Javascript
微信小程序 wx:key详细介绍
Oct 28 #Javascript
微信小程序 使用picker封装省市区三级联动实例代码
Oct 28 #Javascript
基于Layer+jQuery的自定义弹框
May 26 #Javascript
微信开发 js实现tabs选项卡效果
Oct 28 #Javascript
微信开发 使用picker封装省市区三级联动模板
Oct 28 #Javascript
You might like
浅析PHP页面局部刷新功能的实现小结
2013/06/21 PHP
php一些错误处理的方法与技巧总结
2013/08/10 PHP
PHP简单实现DES加密解密的方法
2016/07/12 PHP
php操纵mysqli数据库的实现方法
2016/09/18 PHP
php利用递归实现删除文件目录的方法
2016/09/23 PHP
javascript concat数组累加 示例
2009/09/03 Javascript
JS在textarea光标处插入文本的小例子
2013/03/22 Javascript
IE8中动态创建script标签onload无效的解决方法
2014/12/22 Javascript
jQuery中map()方法用法实例
2015/01/06 Javascript
JS上传图片前实现图片预览效果的方法
2015/03/02 Javascript
基于jQuery+Cookie实现的防止刷新的在线考试倒计时
2015/06/19 Javascript
详解RequireJS按需加载样式文件
2017/04/12 Javascript
详解nodejs微信公众号开发——6.自定义菜单
2017/04/13 NodeJs
微信小程序和H5页面间相互跳转代码实例
2019/09/19 Javascript
微信小程序自定义导航栏(模板化)
2019/11/15 Javascript
javascript实现滚动条效果
2020/03/24 Javascript
解决qrcode.js生成二维码时必须定义一个空div的问题
2020/07/09 Javascript
vue点击按钮实现简单页面的切换
2020/09/08 Javascript
原生js实现贪吃蛇游戏
2020/10/26 Javascript
python字符串连接方法分析
2016/04/12 Python
Django rest framework jwt的使用方法详解
2019/08/08 Python
python实现将列表中各个值快速赋值给多个变量
2020/04/02 Python
整理HTML5移动端开发的常用触摸事件
2016/04/15 HTML / CSS
称象教学反思
2014/02/03 职场文书
学习党的群众路线实践活动思想汇报
2014/09/12 职场文书
夫妻房产协议书的格式
2014/10/11 职场文书
2014年乡镇卫生院工作总结
2014/11/24 职场文书
2014年卫生保健工作总结
2014/12/08 职场文书
2014年学生管理工作总结
2014/12/20 职场文书
中班下学期个人工作总结
2015/02/12 职场文书
跑出一片天观后感
2015/06/08 职场文书
田径运动会通讯稿
2015/07/18 职场文书
公司行政管理制度范本
2015/08/05 职场文书
详解如何修改nginx的默认端口
2021/03/31 Servers
PostgreSQL将数据加载到buffer cache中操作方法
2021/04/16 PostgreSQL
MySQL8.0的WITH查询详情
2021/08/30 MySQL