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 相关文章推荐
js中回调函数的学习笔记
Jul 31 Javascript
jQuery中:last-child选择器用法实例
Dec 31 Javascript
javascript实现按回车键切换焦点
Feb 09 Javascript
理解和运用JavaScript的闭包机制
Aug 13 Javascript
js判断复选框是否选中及选中个数的实现代码
May 30 Javascript
JavaScript数据结构之二叉树的遍历算法示例
Apr 13 Javascript
JavaScript模块详解
Dec 18 Javascript
vue.js element-ui validate中代码不执行问题解决方法
Dec 18 Javascript
结合Vue控制字符和字节的显示个数的示例
May 17 Javascript
原生js实现抽奖小游戏
Jun 27 Javascript
Node.js设置定时任务之node-schedule模块的使用详解
Apr 28 Javascript
vue实现在data里引入相对路径
Jun 05 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判断变量类型常用方法
2012/04/24 PHP
PHP实现格式化文件数据大小显示的方法
2015/01/03 PHP
简单理解PHP的面向对象编程方式
2016/05/17 PHP
PHP中define() 与 const定义常量的区别详解
2019/06/25 PHP
PHP 面向对象程序设计之类属性与类常量实现方法分析
2020/04/13 PHP
Javascript之旅 对象的原型链之由来
2010/08/25 Javascript
js简单实现根据身份证号码识别性别年龄生日
2013/11/29 Javascript
jquery实现将获取的颜色值转换为十六进制形式的方法
2014/12/20 Javascript
jQuery插件slicebox实现3D动画图片轮播切换特效
2015/04/12 Javascript
js实现一个可以兼容PC端和移动端的div拖动效果实例
2016/12/09 Javascript
详解JS去重及字符串奇数位小写转大写
2016/12/29 Javascript
js实现倒计时效果(小于10补零)
2017/03/08 Javascript
jQuery模拟实现天猫购物车动画效果实例代码
2017/05/25 jQuery
node.js中使用Export和Import的方法
2017/09/18 Javascript
探秘vue-rx 2.0(推荐)
2018/09/21 Javascript
Vue源码探究之虚拟节点的实现
2019/04/17 Javascript
JS面向对象实现飞机大战
2020/08/26 Javascript
jenkins自动构建发布vue项目的方法步骤
2021/01/04 Vue.js
flask中使用SQLAlchemy进行辅助开发的代码
2013/02/10 Python
Python实现图片尺寸缩放脚本
2018/03/10 Python
对python过滤器和lambda函数的用法详解
2019/01/21 Python
Python中的self用法详解
2019/08/06 Python
Pytorch使用MNIST数据集实现CGAN和生成指定的数字方式
2020/01/10 Python
军用级手机壳,专为冒险而建:Zizo Wireless
2019/08/07 全球购物
什么是反射
2012/03/17 面试题
接口可以包含哪些成员
2012/09/30 面试题
会计自我鉴定范文
2013/10/06 职场文书
周年庆典主持词
2014/04/02 职场文书
党员干部反四风民主生活会对照检查材料思想汇报
2014/10/12 职场文书
学校运动会开幕词
2016/03/03 职场文书
日本读研:怎样写好一篇日本研究计划书?
2019/07/15 职场文书
幽默口才训练经典句子(48句)
2019/08/19 职场文书
导游词之四川武侯祠
2019/10/21 职场文书
Mysql效率优化定位较低sql的两种方式
2021/05/26 MySQL
CSS实现五种常用的2D转换
2021/12/06 HTML / CSS
Vue3实现简易音乐播放器组件
2022/08/14 Vue.js