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

时间:2016-09-12 08:13:51

标签: javascript html5 canvas design-patterns html5-canvas

我开始使用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.
}

2 个答案:

答案 0 :(得分:2)

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

另外,如果您发现自己需要更复杂的形状,我建议您使用已定义的其他形状类来构建复杂的形状。比如笑脸形状,在其绘制功能中,为眼睛和头部使用三个预定义的圆形类,也许只是使用画布上下文来产生微笑。

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

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



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>
&#13;
&#13;
&#13;

答案 1 :(得分:0)

模块化设计。

似乎javascript在构建工具,接口(程序),框架库以及什么不是模块化方面的前进方向。

Javascript从来就不是一种链接库类型的语言,它的结构非常偶然,并且没有真正的范围保护机制可以依赖它来解决命名空间冲突,可用性,上下文定位等等。

Javascript依赖于一系列松散的约定,通过普遍使用的投票而不是辩论,量化,修正......标准达成一致。

这是模块化的approch。定义离散组件并将其添加到商定的注册表空间。您可以通过函数创建命名空间,并确保不会超出全局范围。

Node.JS和简单模块

NodeJs是模块化设计的一个示例,其中组件和接口作为模块,可通过简单的required接口访问。这将加载并将referance返回到所需的模块。

在NodeJS中加载模块的示例

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

模块需要的所有内容都会在您收到referance时加载并准备好使用,包括子模块,其他依赖项等。编写模块时,您专门导出界面

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

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

对于常规浏览器环境,importexport被定义为语言的一部分。不幸的是,尚未被任何浏览器实现。然而,它们可以通过Babel等许多转发器之一获得。

您编写模块导出接口并在您自己的命名空间内保护范围。编写代码时,可以导入所需的模块,并使用返回的模块参考来与其进行交互。

这个模块approch有很多种变化,非常值得研究。

模块作为单身人士

最基本的approch是单身人士。这是一个模块,作为通过脚本标记引用的js文件。它包含一个封装接口的立即调用的匿名函数。它只将命名空间变量公开给全局范围。

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>

使用它(onload之后)

canvasInterface.clearTheCanvas();

当文件加载时,立即执行该功能,并通过其定义的命名空间使接口可用。您在界面中所做的工作取决于您。

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

未来看起来不错。

现在Javascript在模块化设计方面有点乱,但模块化的approch正在蓬勃发展,希望在不久的将来,我们可以享受连接我们的应用程序的一致和可靠的方法。