如何从代码看出软件框架(协作绘图应用程序)
如何从代码看出软件框架(协作绘图应用程序)如果您只是稍微玩一下该应用程序,您最终可以弄清楚它是如何工作的。但是如果程序本身提供一些简单的指令不是很好吗?例如,在制作圆圈时,先选择中心,然后选择边缘并不明显。一些用户可能认为您应该单击圆圈的两个相对边缘。其他人可能认为您可以绘制多段线。该应用程序不这样做,但对于好奇的程序员来说,稍后添加它会是一个很好的改进。让我们添加一些方便的说明。显示说明现在将此方法添加到 Canvas 对象:displayMode() { const modeDiv = document.querySelector('#mode'); modeDiv.innerHTML = this.mode; }现在在构造函数和 setMode 的末尾调用它。 例如,完成后 setMode 应该如下所示:setMode() { this.mode = mode; this.di
在本文中,我们添加了一些方便的功能让用户知道:
- 他们在画什么形状
 - 画出他们所处的形状的步骤
 
显示模式
首先添加我们将向用户展示他们所处的形状模式的区域。在画布下添加以下行:
<b>Mode:</b> <span id="mode"></span><br />
现在将此方法添加到 Canvas 对象:
displayMode() {
    const modeDiv = document.querySelector('#mode');
    modeDiv.innerHTML = this.mode;
}
    
现在在构造函数和 setMode 的末尾调用它。 例如,完成后 setMode 应该如下所示:
setMode() {
    this.mode = mode;
    this.displayMode();
}
    
通过此更改,该模式将在应用程序启动时首先出现(默认为“Line”),并在您单击其中一个形状按钮时更新。
显示说明
如果您只是稍微玩一下该应用程序,您最终可以弄清楚它是如何工作的。但是如果程序本身提供一些简单的指令不是很好吗?例如,在制作圆圈时,先选择中心,然后选择边缘并不明显。一些用户可能认为您应该单击圆圈的两个相对边缘。其他人可能认为您可以绘制多段线。该应用程序不这样做,但对于好奇的程序员来说,稍后添加它会是一个很好的改进。让我们添加一些方便的说明。
首先,HTML。在显示模式的代码下,添加:
<b>Message:</b> <span id="message"></span>
在 Canvas 构造函数中,我们需要定义给用户什么指令。我们需要第一次单击形状的说明,以及第二次单击的说明。此外,由于圆形和矩形模式的工作方式相同,我们可以按形状类型概括说明。
this.shapeMessages = [
    { type: 'line'  start: 'Select the starting point'  end: 'Select the ending point' } 
    { type: 'rectangle'  start: 'Select the first corner'  end: 'Select the second corner' } 
    { type: 'circle'  start: 'Select the middle of the circle'  end: 'Select the edge of the circle' }
];
    
然后我们需要一种方法来确定模式是哪种形状类型:
getModeType(mode) {
    let type = '';
    switch (mode) {
        case 'Line':
            type = 'line';
            break;
        case 'Hollow Rectangle':
        case 'Filled Rectangle':
            type = 'rectangle';
            break;
        case 'Hollow Circle':
        case 'Filled Circle':
            type = 'circle';
            break;
    }
    return type;
}
    
然后是实际显示消息的方法:
displayMessage() {
    const type = this.getModeType(this.mode);
    const find = this.shapeMessages.find(m => m.type === type);
    const msgDiv = document.querySelector('#message');
    msgDiv.innerHTML = find[this.pointMode];
}
    
作为复习,this.pointMode 被设置为记录我们是在形状的起点还是终点。 displayMessage 方法因此找到我们想要使用 shapeMessages 数组的哪个元素,并根据需要返回开始或结束消息。
和 displayMode 一样,这个方法应该在构造函数的末尾和 setMode 的末尾调用。 它也应该在 handleDraw 结束时调用,因为每次点击后我们都想更新指令。
把它们放在一起
如下所示,模式和消息现在应该出现在画布下:

当您单击按钮选择形状并单击画布时,请注意“模式”和“消息”区域。 他们会改变以反映你在做什么。
下面是 HTML 和 Javascript 此时的样子:
<html>
<head>
    <title>Collaborative Drawing App</title>
    <style type="text/css">
        canvas {
            border: 1px solid black;
        }
        .palette {
            border: 1px solid black;
            display: inline-block;
            margin: 2px;
            height: 25px;
            width: 25px;
        }
    </style>
</head>
<body>
    <h1>Collaborative Drawing App</h1>
    <div>
        <canvas id="canvas" height="500px" width="500px"></canvas><br />
        <b>Mode:</b> <span id="mode"></span><br />
        <b>Message:</b> <span id="message"></span>
    </div>
    <div id="palette">
        <div id="row-1">
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
        </div>
        <div id="row-2">
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
        </div>
    </div> 
    <div id="draw-methods">
        <button onclick="canvas.setMode('Line')">Line</button>
        <button onclick="canvas.setMode('Hollow Rectangle')">Hollow Rectangle</button>
        <button onclick="canvas.setMode('Filled Rectangle')">Filled Rectangle</button>
        <button onclick="canvas.setMode('Hollow Circle')">Hollow Circle</button>
        <button onclick="canvas.setMode('Filled Circle')">Filled Circle</button>
    </div>
    <script type="text/javascript" src="./script.js"></script>
    <script type="text/javascript">
        const canvas = new Canvas();
        const palette = new Palette();
        palette.draw(canvas);
    </script>      
</body>
</html>
    
class Canvas {
    constructor() {
        this.canvas = document.querySelector('#canvas');
        this.ctx = this.canvas.getContext('2d');
        this.activeColor = '#000000';
        this.startPoint = null;
        this.endPoint = null;
        this.pointMode = 'start';
        this.mode = 'Line';  
        this.handleDraw = this.handleDraw.bind(this);
        this.canvas.addEventListener('click'  this.handleDraw);       
        this.shapeMessages = [
            { type: 'line'  start: 'Select the starting point'  end: 'Select the ending point' } 
            { type: 'rectangle'  start: 'Select the first corner'  end: 'Select the second corner' } 
            { type: 'circle'  start: 'Select the middle of the circle'  end: 'Select the edge of the circle' }
        ];
        this.displayMode(); 
        this.displayMessage();      
    }
    setColor(color) {
        this.activeColor = color;
        this.ctx.strokeStyle = color;
        this.ctx.fillStyle = color;
    }
    setMode(mode) {
        this.mode = mode;
        this.displayMode();
        this.displayMessage();
    }
    handleDraw(e) {
        const rect = this.canvas.getBoundingClientRect();
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;
        if (this.pointMode == 'start') {
            this.startPoint = [x  y];
            this.pointMode = 'end';
        } else if (this.pointMode == 'end') {
            this.pointMode = 'start';
            this.endPoint = [x  y];
            // do the drawing
            if (this.mode == 'Line') {
                this.drawLine(this.startPoint  this.endPoint);
            } else if (this.mode == 'Hollow Rectangle') {
                this.drawHollowRectangle(this.startPoint  this.endPoint);
            } else if (this.mode == 'Filled Rectangle') {
                this.drawFilledRectangle(this.startPoint  this.endPoint);
            } else if (this.mode == 'Hollow Circle') {
                this.drawHollowCircle(this.startPoint  this.endPoint);
            } else if (this.mode == 'Filled Circle') {
                this.drawFilledCircle(this.startPoint  this.endPoint);
            }
            this.startPoint = null;
            this.endPoint = null;
        }
        this.displayMessage();
    }
    
    drawLine(startPoint  endPoint) {
        this.ctx.beginPath();
        this.ctx.moveTo(startPoint[0]  startPoint[1]);
        this.ctx.lineTo(endPoint[0]  endPoint[1]);
        this.ctx.stroke();
    }
    drawHollowRectangle(startPoint  endPoint) {
        this.ctx.beginPath();
        this.ctx.strokeRect(
            startPoint[0] 
            startPoint[1] 
            endPoint[0] - startPoint[0] 
            endPoint[1] - startPoint[1]
        );
    }
    drawFilledRectangle(startPoint  endPoint) {
        this.ctx.beginPath();
        this.ctx.fillRect(
            startPoint[0] 
            startPoint[1] 
            endPoint[0] - startPoint[0] 
            endPoint[1] - startPoint[1]
        );
    }
    drawHollowCircle(startPoint  endPoint) {
        const x = startPoint[0] - endPoint[0];
        const y = startPoint[1] - endPoint[1];
        const radius = Math.sqrt(x * x   y * y);
        this.ctx.beginPath();
        this.ctx.arc(startPoint[0]  startPoint[1]  radius  0  2 * Math.PI  false);
        this.ctx.stroke();
    }
    drawFilledCircle(startPoint  endPoint) {
        const x = startPoint[0] - endPoint[0];
        const y = startPoint[1] - endPoint[1];
        const radius = Math.sqrt(x * x   y * y);
        this.ctx.beginPath();
        this.ctx.arc(startPoint[0]  startPoint[1]  radius  0  2 * Math.PI  false);
        this.ctx.fill();
    } 
    
    displayMode() {
        const modeDiv = document.querySelector('#mode');
        modeDiv.innerHTML = this.mode;
    }
    getModeType(mode) {
        let type = '';
        switch (mode) {
            case 'Line':
                type = 'line';
                break;
            case 'Hollow Rectangle':
            case 'Filled Rectangle':
                type = 'rectangle';
                break;
            case 'Hollow Circle':
            case 'Filled Circle':
                type = 'circle';
                break;
        }
        return type;
    }    
    displayMessage() {
        const type = this.getModeType(this.mode);
        const find = this.shapeMessages.find(m => m.type === type);
        const msgDiv = document.querySelector('#message');
        msgDiv.innerHTML = find[this.pointMode];
    }    
}
class Palette {
    constructor() {
        this.colors = [
            ['#000000'  '#FFFFFF'  '#7F7F7F'  '#C3C3C3'  '#880015'  '#B97A57'  '#ED1C24'  '#FFAEC9'  '#FF7F27'  '#FFC90E'] 
            ['#FFF200'  '#EFE4B0'  '#22B14C'  '#B5E61D'  '#00A2E8'  '#99D9EA'  '#3F48CC'  '#7092BE'  '#A349A4'  '#C8BFE7']
        ];
    }
    draw(canvas) {
        const row1 = document.querySelectorAll('#row-1 .palette');
        const row2 = document.querySelectorAll('#row-2 .palette');
        row1.forEach((div  idx) => {
            div.style.backgroundColor = this.colors[0][idx];
            div.onclick = e => canvas.setColor(this.colors[0][idx]);
        });
        row2.forEach((div  idx) => {
            div.style.backgroundColor = this.colors[1][idx];
            div.onclick = e => canvas.setColor(this.colors[1][idx]);
        });
    }
}
    
我们正在接近添加套接字和后端功能以使应用程序具有协作性。 不过,接下来,我们将添加几个按钮来清除绘图并将其下载为图像文件。 我们将在下一篇文章中介绍。




