使用Canvas API时有哪些典型的设计模式?

问题描述 投票:0回答:2

我开始使用 Canvas API 进行图形编程,但我还没有看到任何重用画布代码的设计模式。到目前为止,我只看到了使用全局画布对象和单个绘制函数的示例。

我来自 Python tkinter 背景,其中主要模式是为每个图形对象创建一个类,并将画布上下文传递到构造函数中,以便对象可以自行绘制。我正在考虑使用下面的模块模式来实现同样的事情,但我正在寻找更多的 JavaScript 方法(如果有的话)。

var MyShape = function (ctx) {
    // Assign the canvas context along with any other properties,
    // then draw the object.
    this.ctx = ctx;
    this.draw();
}

MyShape.prototype.draw = function () {
    // Use this.ctx to access the Canvas API and draw the object.
}
javascript html canvas design-patterns html5-canvas
2个回答
3
投票

到目前为止你的想法是正确的。除了您拥有的之外,我建议您为每个定义的可渲染对象添加一个更新函数。只要这些形状有可能随着时间的推移而移动。

此外,如果您发现自己需要更复杂的形状,我建议您用已经定义的其他形状类来组成复杂的形状。例如笑脸形状,在其绘制函数中,使用三个预定义的圆形类作为眼睛和头部,并且可能只使用画布上下文来生成微笑。

您可以在一系列绘制调用中从父形状调用每个子形状的绘制函数。

编辑:这是一个小例子,我忘了提到使用引用类型来存储形状父级位置的重要性。

var canvas = document.getElementById("game");
var ctx = canvas.getContext("2d");

var thing = new Thing(100, 100, 30, 40);

function Vector(x, y) {
    this.x = x;
    this.y = y;
}

function Rectangle(pos, w, h, color, parent) {
    // use a vector2 'pos' so that the position of this rectangle matches the
    // position of the parent because pos is a reference type
    this.pos = pos;
    this.w = w;
    this.h = h;
    this.color = color || "#000";
}

Rectangle.prototype.render = function () {
    ctx.fillStyle = this.color;
    ctx.fillRect(this.pos.x, this.pos.y, this.w, this.h);
};

function Circle(pos, r, color, parent) {
    // use a vector2 'pos' so that the position of this circle matches the
    // position of the parent because pos is a reference type
    this.pos = pos;
    this.r = r;
    this.color = color || "#000";
}

Circle.prototype.render = function () {
    ctx.beginPath();
    ctx.arc(this.pos.x, this.pos.y, this.r, 0, 2 * Math.PI, false);
    ctx.fillStyle = this.color;
    ctx.fill();
};

function Thing(x, y, w, h) {
    //create pos reference
    this.pos = new Vector(x, y);

    //use pos reference
    this.rect = new Rectangle(this.pos, w, h, "#00F");
    this.circle = new Circle(this.pos, w, "#F00");

    //for update() demonstration
    this.rotation = 0;
}

Thing.prototype.render = function () {
    this.circle.render();
    this.rect.render();
};

Thing.prototype.update = function () {
    // notice here i only have to modify the values of pos.x and pox.y, this is
    // important because if i had added fields x and y to the Thing class, they
    // would not be reference types and i could not have moved both the
    // rectangle and the circle if that was the case.
    this.pos.x = this.pos.x + Math.cos(this.rotation);
    this.pos.y = this.pos.y + Math.sin(this.rotation);
    this.rotation += 0.02;
};

function update() {
    thing.update();
}

function render() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    thing.render();
}

function main() {
    update();
    render();
    window.requestAnimationFrame(main);
}

main();
#game {
    border: 1px solid black;
}
<canvas id="game" width="500px" height="500px"></canvas>


0
投票

模块化设计。

JavaScript 在构建工具、接口(编程)、框架库以及其他方面的发展方向似乎是模块化的。

Javascript从来就不是一种链接库类型的语言,它的结构非常随意,并且没有真正的作用域保护机制可以用来解决命名空间冲突、可用性、上下文目标等问题。

Javascript 依赖于一组松散的约定,这些约定是由大众投票同意的,而不是经过辩论、量化、修改的……标准。

这就是模块化方法。离散组件被定义并添加到商定的注册表空间中。您可以通过函数创建命名空间,并确保不处于全局范围内。

Node.JS 和简单模块

NodeJs 是模块化设计的一个示例,其中组件和接口作为模块,可通过简单的

required
接口进行访问。这将加载并返回对所需模块的引用。

在 NodeJS 中加载模块的示例

const fileSystem = require("fs");
// use the module
fileSystem.stats("filepath",callbackFunction);

当您收到引用时,模块所需的所有内容都已加载并可供使用,这包括子模块、其他依赖项等。在编写模块时,您专门导出接口

export.doSomething = (thing) => { // do some to thing

**对于浏览器?????? **

对于一般浏览器环境,

import
export
定义为语言的一部分。不幸的是,尚未被任何浏览器实现。然而,它们可以通过 Babel 等众多转译器之一获得。

您编写模块导出接口并保护您自己的命名空间内的范围。当您编写代码时,您导入所需的模块并使用返回的模块引用与其交互。

此模块方法有多种变体,非常值得研究。

模块作为单例

最基本的方法是单例。这是一个作为 js 文件的模块,通过 script 标签引用。它包含一个立即调用的封装接口的匿名函数。它仅将名称空间变量公开到全局范围。

const canvasInterface = (function(){
    // define private and protected objects
    var mycanvasColour = "Black";
    // define private functions
    function createCanvas(){ ...
    }

    // define the interface 
    const canvasStuff = {
         clearTheCanvas : function(){
         },
    }

    // return a referance to the interface 
    return canvasStuff;
}

您将其包含在

<script src="../mylibs/myCanvasModual.js"></script>

使用它(加载后)

canvasInterface.clearTheCanvas();

当文件加载时,函数立即执行,并且接口通过其定义的命名空间变得可用。您在界面中执行的操作取决于您。

最主要的方法是仅公开使用模块所需的内容,并且永远不要在全局名称空间中声明模块中可以包含的任何内容。这将保护您免受全局命名空间冲突的影响,并使内部状态保持在您的控制之下(因此安全)。

未来看起来美好。

JavaScript 目前在模块化设计方面有点混乱,但模块化方法正在兴起,希望在不久的将来我们可以享受到一致且可靠的方式来链接我们的应用程序。

© www.soinside.com 2019 - 2024. All rights reserved.