webpack环境已经搭好,我们开始分拆整个功能成到不同的文件里面。 首先,我们把ActionData和DrawTool分开,放在不同的文件里面,目录结构如下:
src
- data
ActionData.js
PathData.js
RectData.js
EllipseData.js
- tool
DrawingTool.js
PathTool.js
RectTool.js
EllipseTool.js
首先我们看看ActionData.js里面的代码:
export default class ActionData {
//
constructor(type, lineWidth, strokeStyle) {
this._type = type;
this._lineWidth = lineWidth;
this._strokeStyle = strokeStyle;
}
//
draw(ctx) { throw new Error('draw not implemented'); }
}
注意,我们直接通过export default导出了class ActionData 。 同样,PathData.js文件类似:
import ActionData from './ActionData';
export default class PathData extends ActionData {
//
constructor(points, lineWidth, strokeStyle) {
super('path', lineWidth, strokeStyle);
//
this._points = points;
}
//
draw(ctx) {
//
ctx.beginPath();
ctx.lineWidth = this._lineWidth;
ctx.strokeStyle = this._strokeStyle;
//
let points = this._points;
if (points.length == 0) {
return;
}
//
let firstPoint = points[0];
ctx.moveTo(firstPoint.x, firstPoint.y);
for (let j = 1; j < points.length; j++) {
const point = points[j];
ctx.lineTo(point.x, point.y);
}
ctx.stroke();
//
}
}
我们通过import语法,引入了ActionData,然后才可以继承这个class。整个语法和java类似。 其他文件Data和tool文件,也都是类似。 最后,我们修改以下index.js文件。 在之前的代码中,我们都直接把actions,undoCursor作为全局变量使用。通常情况,我们应该避免使用全局变量。因此,在这里,我们编写一个新的class HandwrittenPad。在这个class里面,我们把actions,undoCursor作为成员变量封装起来。同时对外提供选择选择各种工具,undo/redo等方法。 我们在src下面建立一个文件:HandwrittenPad.js,代码如下:
import PathTool from './tool/PathTool';
import RectTool from './tool/RectTool';
import EllipseTool from './tool/EllipseTool';
import { EventEmitter } from 'events';
export default class HandwrittenPad extends EventEmitter{
//
constructor(canvas) {
super();
//
this._actions = [];
this._undoCursor = -1;
this._canvas = canvas;
this._canvas.width = this._canvas.clientWidth;
this._canvas.height = this._canvas.clientHeight;
//
this._ctx = this._canvas.getContext('2d');
this._tool = new PathTool(this._ctx, 2, 'blue');
//
this._updateButtonStatus();
//
this._canvas.addEventListener('mousedown', this.handleMouseDown);
}
//
handleMouseDown = (event) => {
//
if (this._undoCursor != -1) {
this._actions = this._actions.slice(0, this._undoCursor);
}
this._undoCursor = -1;
//
this._canvas.addEventListener('mousemove', this.handleMouseMove);
this._canvas.addEventListener('mouseup', this.handleMouseUp);
//
this._tool.handleMouseDown(event.offsetX, event.offsetY);
}
//
handleMouseMove = (event) => {
this._tool.handleMouseMove(event.offsetX, event.offsetY);
}
//
handleMouseUp = (event) => {
this._canvas.removeEventListener('mousemove', this.handleMouseMove);
this._canvas.removeEventListener('mouseup', this.handleMouseUp);
//
let actionData = this._tool.handleMouseUp(event.offsetX, event.offsetY);
this._actions.push(actionData);
//
this._updateButtonStatus();
}
//
choosePencil() {
this._tool = new PathTool(this._ctx, 2, 'blue');
}
//
chooseHighlighter() {
this._tool = new PathTool(this._ctx, 8, 'rgba(255, 255, 0, 0.5)');
}
//
chooseEraser() {
this._tool = new PathTool(this._ctx, 8, 'white');
}
//
drawRect() {
this._tool = new RectTool(this._ctx, 2, 'red');
}
//
drawEllipse() {
this._tool = new EllipseTool(this._ctx, 2, 'green');
}
//
canUndo() {
if (this._actions.length == 0) {
return false;
}
//
if (this._undoCursor == 0) {
return false;
}
//
return true;
}
//
canRedo() {
//
if (this._actions.length == 0) {
return false;
}
//
if (this._undoCursor == -1 || this._undoCursor == this._actions.length) {
return false;
}
//
return true;
}
//
undo() {
if (!this.canUndo()) {
return;
}
//
if (this._undoCursor == -1) {
this._undoCursor = this._actions.length;
}
//
this._undoCursor--;
//
this._repaint();
//
this._updateButtonStatus();
}
//
redo() {
if (!this.canRedo()) {
return;
}
//
this._undoCursor++;
//
this._repaint();
//
this._updateButtonStatus();
}
//
//
_updateButtonStatus() {
this.emit('updateUndoRedo', this);
}
//
_repaint() {
const canvas = this._canvas;
this._ctx.clearRect(0, 0, canvas.width, canvas.height);
//
let toIndex = this._undoCursor == -1 ? this._actions.length : this._undoCursor;
for (let i = 0; i < toIndex; i++) {
//
let actionData = this._actions[i];
actionData.draw(this._ctx);
}
}
}
在这里,我们在构造函数里面,要求传入一个canvas。然后定义了几个成员变量:actions,undoCursor,canvas,ctx以及tool。在这里,我们可以看到,定义的这几个变量,前面都加了下划线。这表明这些变量都是私有的,不建议外部使用(虽然js无法强制禁止外部使用)。同样,如果方法前面也加了下划线,同样表示这是一个私有方法,不建议外部使用。 另外,我们还让HandwrittenPad 继承了 EventEmitter ,这样我们可以向外发送消息。目前我们向外发送了updateUndoRedo这个消息,表示undo/redo的状态改变了。 从代码中我们可以看到,HandwrittenPad已经不在和document有关联了,我们的代码中也不再使用document对象。 最后,我们修改index.js文件:
import HandwrittenPad from './HandwrittenPad';
function updateUndoRedo(pad) {
const undoButton = document.getElementById('undo');
const redoButton = document.getElementById('redo');
undoButton.disabled = !pad.canUndo();
redoButton.disabled = !pad.canRedo();
}
function init() {
//
const pad = new HandwrittenPad(document.getElementById('pad'));
//
document.getElementById('pencil').addEventListener('click', () => pad.choosePencil());
document.getElementById('highlighter').addEventListener('click', () => pad.chooseHighlighter());
document.getElementById('eraser').addEventListener('click', () => pad.chooseEraser());
document.getElementById('rect').addEventListener('click', () => pad.drawRect());
document.getElementById('ellipse').addEventListener('click', () => pad.drawEllipse());
document.getElementById('undo').addEventListener('click', () => pad.undo());
document.getElementById('redo').addEventListener('click', () => pad.redo());
//
pad.on('updateUndoRedo', (pad) => {
updateUndoRedo(pad);
});
//
updateUndoRedo(pad);
}
init();
在index.js里面,我们开始和html/dom交互。获取对象,绑定事件,更新状态。
至此,我们用来测试手写的功能已经基本完成,并且使用webpack,来把整个工程进行拆分,并使用了面向对象的思想,重构了代码,让代码可维护性更高。