"gui" 是 Graphical User Interface 的缩写,指的是图形用户界面。它是一种让用户可以通过图形化的方式与计算机交互的界面,通过鼠标、键盘等输入设备和图形化的界面来完成各种操作。与之相对的是命令行界面(CLI),它是一种通过输入命令来与计算机
以下是一个使用 JavaScript Canvas 实现简单的 GUI 程序的例子。这个程序可以让用户通过点击鼠标来画点、画线、画矩形等等,同时还提供了清空画布的功能。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Canvas GUI</title>
</head>
<body>
<canvas id="canvas" width="600" height="400" style="border:1px solid #000000;"></canvas>
<br />
<button onclick="clearCanvas()">Clear</button>
<script>
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var isDrawing = false;
var startX, startY;
var currentX, currentY;
canvas.addEventListener('mousedown', function(e) {
startX = e.clientX - canvas.offsetLeft;
startY = e.clientY - canvas.offsetTop;
isDrawing = true;
});
canvas.addEventListener('mousemove', function(e) {
if (isDrawing) {
currentX = e.clientX - canvas.offsetLeft;
currentY = e.clientY - canvas.offsetTop;
draw();
}
});
canvas.addEventListener('mouseup', function(e) {
isDrawing = false;
});
function draw() {
context.beginPath();
context.moveTo(startX, startY);
context.lineTo(currentX, currentY);
context.stroke();
context.closePath();
startX = currentX;
startY = currentY;
}
function clearCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
</script>
</body>
</html>
这个程序使用了 Canvas 元素来创建一个画布,通过监听鼠标事件来实现画图功能。当用户点击鼠标时,程序会记录下起始点的坐标,并将 isDrawing 变量设为 true,表示正在画图。随后,当用户移动鼠标时,程序会根据当前鼠标位置和起始点的坐标,调用 draw() 函数来画出相应的图形。当用户释放鼠标时,程序将 isDrawing 变量设为 false,表示画图结束。
在 draw() 函数中,我们使用 context.beginPath() 和 context.closePath() 方法来开始和结束路径,使用 context.moveTo() 和 context.lineTo() 方法来画线。除了画线之外,我们还可以使用 context.fillRect() 和 context.strokeRect() 方法来画矩形,使用 context.arc() 方法来画圆形等等。同时,我们还提供了一个 clearCanvas() 函数来清空画布,使用 context.clearRect() 方法来实现。
Q: 上面能否优化代码, 例如:可以分成
- canvas 类,里面可以 drawPoint,drawLine drawReact 等
- color 类,专门设置颜色
- point 设置位置信息
- size 设置宽高
class Canvas {
constructor(id, width, height) {
this.canvas = document.getElementById(id);
this.ctx = this.canvas.getContext("2d");
this.canvas.width = width;
this.canvas.height = height;
this.color = new Color(this.ctx);
}
clear() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
drawPoint(point, size = 1) {
this.ctx.beginPath();
this.ctx.arc(point.x, point.y, size, 0, 2 * Math.PI);
this.ctx.fill();
}
drawLine(point1, point2, width = 1) {
this.ctx.beginPath();
this.ctx.moveTo(point1.x, point1.y);
this.ctx.lineTo(point2.x, point2.y);
this.ctx.lineWidth = width;
this.ctx.stroke();
}
drawRect(point, size, fill = false, width = 1) {
if (fill) {
this.ctx.fillRect(point.x, point.y, size.width, size.height);
} else {
this.ctx.strokeRect(point.x, point.y, size.width, size.height);
}
}
}
class Color {
constructor(ctx) {
this.ctx = ctx;
}
set(fillColor = "#000000", strokeColor = "#000000") {
this.ctx.fillStyle = fillColor;
this.ctx.strokeStyle = strokeColor;
}
}
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class Size {
constructor(width, height) {
this.width = width;
this.height = height;
}
}
在这个优化后的示例中,我们创建了 Canvas 类来处理所有与画布相关的操作,包括清除画布,画点,画线和画矩形。我们还创建了一个名为 Color 的新类来处理颜色,这个类被 Canvas 类引用。此外,我们还创建了两个辅助类 Point 和 Size,它们用于存储有关点和大小的信息。
使用这些类,我们可以创建一个新的 Canvas 对象并调用其中的方法:
const canvas = new Canvas("myCanvas", 500, 500);
canvas.color.set("#ff0000", "#00ff00");
canvas.clear();
canvas.drawPoint(new Point(50, 50), 10);
canvas.drawLine(new Point(100, 100), new Point(200, 200), 5);
canvas.drawRect(new Point(300, 300), new Size(50, 50), true);
在这个例子中,我们首先创建了一个新的 Canvas 对象,并设置了它的大小和颜色。我们然后清除画布并使用 drawPoint、drawLine 和 drawRect 方法来画点、画线和画矩形。
Q: 上面drawLine 能否不使用lineTo, 使用 drawPoint 来实现,同理 drawRect 使用 drawLine 实现
A: 可以使用一些基本的图形元素来构建其他高级图形元素。下面是一个使用 drawPoint 绘制线段的示例代码:
class Canvas {
constructor(width, height) {
this.canvas = document.createElement('canvas');
this.canvas.width = width;
this.canvas.height = height;
this.ctx = this.canvas.getContext('2d');
}
setColor(color) {
this.ctx.strokeStyle = color;
}
setLineWidth(width) {
this.ctx.lineWidth = width;
}
drawPoint(x, y) {
this.ctx.beginPath();
this.ctx.moveTo(x, y);
this.ctx.lineTo(x + 1, y + 1);
this.ctx.stroke();
}
drawLine(x1, y1, x2, y2) {
this.ctx.beginPath();
this.ctx.moveTo(x1, y1);
this.drawPoint(x2, y2);
this.ctx.stroke();
}
drawRect(x, y, width, height) {
this.drawLine(x, y, x + width, y);
this.drawLine(x + width, y, x + width, y + height);
this.drawLine(x + width, y + height, x, y + height);
this.drawLine(x, y + height, x, y);
}
addToDOM(element) {
element.appendChild(this.canvas);
}
}
const canvas = new Canvas(400, 400);
canvas.setColor('#FF0000');
canvas.setLineWidth(5);
canvas.drawLine(100, 100, 300, 300);
canvas.addToDOM(document.body);
这里使用了 drawPoint 方法来实现绘制线段,通过在线段的两个端点绘制一个非常小的点来实现。同样,可以使用 drawLine 方法来实现绘制矩形。
Q: 过改变像素信息来实现 画点效果
A:
好的,下面是分别基于像素操作实现的 drawLine 和 drawRect 方法:
class Canvas {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.context = this.canvas.getContext("2d");
this.w = this.canvas.width;
this.h = this.canvas.height;
this.pixels = this.context.getImageData(0, 0, this.w, this.h);
}
drawPoint(x, y, color) {
const index = (y * this.w + x) * 4;
this.pixels.data[index] = color.r;
this.pixels.data[index + 1] = color.g;
this.pixels.data[index + 2] = color.b;
this.pixels.data[index + 3] = 255;
this.context.putImageData(this.pixels, 0, 0);
}
drawLine(x1, y1, x2, y2, color) {
const dx = x2 - x1;
const dy = y2 - y1;
const steps = Math.max(Math.abs(dx), Math.abs(dy));
const xIncrement = dx / steps;
const yIncrement = dy / steps;
let x = x1;
let y = y1;
for (let i = 0; i <= steps; i++) {
this.drawPoint(Math.round(x), Math.round(y), color);
x += xIncrement;
y += yIncrement;
}
}
drawRect(x, y, width, height, color) {
this.drawLine(x, y, x + width, y, color);
this.drawLine(x, y, x, y + height, color);
this.drawLine(x, y + height, x + width, y + height, color);
this.drawLine(x + width, y, x + width, y + height, color);
}
setColor(color) {
this.color = color;
}
}
class Color {
constructor(r, g, b) {
this.r = r;
this.g = g;
this.b = b;
}
}
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class Size {
constructor(w, h) {
this.w = w;
this.h = h;
}
}
这里的 drawLine 方法使用的是 Bresenham 直线算法,而 drawRect 方法则是基于 drawLine 方法实现的。这两个方法与之前的 drawPoint 方法一样,都是基于像素操作实现的。
Q: 加 clear
A:
以下是修改后的完整代码:
class Color {
static white() {
return { r: 255, g: 255, b: 255, a: 255 };
}
static black() {
return { r: 0, g: 0, b: 0, a: 255 };
}
}
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class Size {
constructor(w, h) {
this.w = w;
this.h = h;
}
}
class Canvas {
constructor(canvasId, size) {
this.canvas = document.getElementById(canvasId);
this.context = this.canvas.getContext('2d');
this.size = size;
this.canvas.width = size.w;
this.canvas.height = size.h;
this.pixels = this.context.getImageData(0, 0, size.w, size.h);
this.clear();
}
clear() {
const clearColor = Color.white();
for (let i = 0; i < this.size.w; i++) {
for (let j = 0; j < this.size.h; j++) {
const index = (i + j * this.size.w) * 4;
this.pixels.data[index] = clearColor.r;
this.pixels.data[index + 1] = clearColor.g;
this.pixels.data[index + 2] = clearColor.b;
this.pixels.data[index + 3] = clearColor.a;
}
}
this.context.putImageData(this.pixels, 0, 0);
}
drawPoint(point, color) {
const index = (point.x + point.y * this.size.w) * 4;
this.pixels.data[index] = color.r;
this.pixels.data[index + 1] = color.g;
this.pixels.data[index + 2] = color.b;
this.pixels.data[index + 3] = color.a;
this.context.putImageData(this.pixels, 0, 0);
}
drawLine(start, end, color) {
const dx = end.x - start.x;
const dy = end.y - start.y;
const steps = Math.max(Math.abs(dx), Math.abs(dy));
const xIncrement = dx / steps;
const yIncrement = dy / steps;
let x = start.x;
let y = start.y;
for (let i = 0; i <= steps; i++) {
const point = new Point(Math.round(x), Math.round(y));
this.drawPoint(point, color);
x += xIncrement;
y += yIncrement;
}
}
drawRect(point, size, color) {
const p1 = point;
const p2 = new Point(point.x + size.w, point.y);
const p3 = new Point(point.x + size.w, point.y + size.h);
const p4 = new Point(point.x, point.y + size.h);
this.drawLine(p1, p2, color);
this.drawLine(p2, p3, color);
this.drawLine(p3, p4, color);
this.drawLine(p4, p1, color);
}
}
const canvas = new Canvas('myCanvas', new Size(400, 400));
canvas.drawLine(new Point(50, 50), new Point(350, 350), Color.black());
canvas.drawRect(new Point(100, 100), new Size(200, 100), Color.black());
canvas.clear();