我正在使用画布创建一个六边形网格,我正在尝试用图像中的特定图案填充每个图块。以下代码是我正在使用的代码。
最终的结果是一个六边形的网格,其瓷砖图案都具有相同的图像......它不应该是。我认为正在发生的事情是,它为每个图块创建了一个图案叠加层,但该图像基本上覆盖了所有图块...而且我最终只看到最后一个图像被调用。
我的印象是我的填充()只填充了那个小六边形......而不是全部。我怎样才能做到这一点,以便每个单独的六角形可以拥有自己的图像?
此代码在for循环中运行以创建网格。这是我的drawHex()方法。我无法想象每个瓷砖需要一个全新的画布来实现这一目标。
var numberOfSides = 6,
size = hex.properties.radius,
Xcenter = hexObj.x + (hex.properties.width / 2),
Ycenter = hexObj.y + (hex.properties.height / 2);
var img = new Image();
if (hexObj.t == "grassland"){
img.src = "/static/grass.jpg";
}else{
img.src = "/static/mountain.jpg";
}
var pattern = context.createPattern(img, "repeat");
context.fillStyle = pattern;
context.beginPath();
context.moveTo (Xcenter + size * Math.cos(0), Ycenter + size * Math.sin(0));
for (var i = 1; i <= numberOfSides;i += 1) {
context.lineTo (Xcenter + size * Math.cos(i * 2 * Math.PI / numberOfSides), Ycenter + size * Math.sin(i * 2 * Math.PI / numberOfSides));
}
context.fill();
context.closePath();
context.stroke();
答案 0 :(得分:4)
首先要了解图像加载是如何工作的。这是一个异步操作,这意味着您必须等待图像在继续之前加载。
这种策略决定了每次想要更改图像时设置图像源的策略也会迫使你处理它的异步方面会延迟等等。此外,如果在创建图案时图像没有加载,则样式将无法呈现。
因此,更好的方法是预加载您将使用的所有图像(或单个精灵表)。然后在记忆中坚持下去(如果图像不是很大的话,今天通常不会出现问题)。
然后,您可以存储引用每个图像的多个CanvasPattern
个对象。一种方法是构建一个tile对象,其中包含有关该tile包含的所有信息。它的模式。
例如:
function Tile(ctx, x, y, radius, img) {
this.pattern = ctx.createPattern(img, "repeat");
// store other properties here like x, y, radius etc.
}
Tile.prototype.render = function(ctx) {
ctx.beginPath();
// create hex shape here
ctx.fillStyle = this.pattern;
ctx.fill();
}
现在您可以创建一个Tile
对象并将其保存在数组(或母对象)中:
var tiles = [];
tiles.push(new Tile(ctx, x1, y1, radius, img1); // img1 has loaded (onload)
tiles.push(new Tile(ctx, x2, y2, radius, img2); // img2 has also loaded (onload)
// etc.
然后简单地渲染它们,如:
tiles.forEach(function(tile) { tile.render(ctx) });
您需要一个图像加载器来加载所有图像。缺点是用户必须等待图像加载,除非您的前端可以占用用户,同时图像在后台加载。
加载器不必复杂,但对于生产,您可能希望处理错误(onerror
/ onabort
)。当所有图像都已加载时,此示例将调用函数start()
:
var images = [];
var urls = ["//image1.jpg", "//image2.jpg", ...];
var count = urls.length;
function handler() {
if (!--count) start();
}
urls.forEach(function(url) {
var img = new Image;
images.push(img);
img.onload = handler;
//img.onerror = ... // handler for error and abort here
//img.onabort = ...
img.src = url;
})
var images = [], urls = ["//i.imgur.com/DAg71N5.jpg?1", "//i.imgur.com/ZO3XQpj.jpg?1"],
tiles = [], count = urls.length, ctx = c.getContext("2d");
function handler() {if (!--count) start()}
function Tile(ctx, x, y, radius, img) {
this.pattern = ctx.createPattern(img, "repeat");
this.x = x;
this.y = y;
this.radius = radius;
}
Tile.prototype.render = function(ctx) {
ctx.beginPath();
for(var i = 0; i < Math.PI*2; i += Math.PI/3)
ctx.lineTo(this.x + Math.cos(i) * this.radius, this.y + Math.sin(i) * this.radius);
ctx.fillStyle = this.pattern;
ctx.fill();
}
urls.forEach(function(url) {
var img = new Image;
images.push(img);
img.onload = handler;
img.src = url;
});
function start() {
tiles.push(new Tile(ctx, 50, 50, 50, images[0]));
tiles.push(new Tile(ctx, 130, 95, 50, images[1]));
tiles.forEach(function(tile) { tile.render(ctx) });
}
&#13;
<canvas id=c></canvas>
&#13;
可以通过这种方式绘制十六进制形状:
for(var i = 0; i < Math.PI*2; i += Math.PI/3)
ctx.lineTo(this.x + Math.cos(i) * this.radius, this.y + Math.sin(i) * this.radius);
请注意,这需要beginPath()
。这允许我们传递moveTo()
,因为新路径上的第一个lineTo()
会将路径光标移动到其起点。
为了保持相对于形状的图案,您可以在上下文中使用translate()
,然后相对于(0,0)绘制它们,这也简化了十六进制绘图:
ctx.translate(this.x, this.y);
ctx.moveTo(this.radius, 0);
for(var i = 0; i < Math.PI*2; i += Math.PI/3)
ctx.lineTo(Math.cos(i) * this.radius, Math.sin(i) * this.radius);
// cancel transforms here if needed
在较新的浏览器中,您可以在模式本身上使用setTransform()
。并非所有浏览器都支持此功能,因此请小心..
迷你更新为了重复使用,您可以考虑在加载过程中创建对象外部 模式,以便您只使用对模式的引用每个瓷砖。
specs are a little unclear关于图像复制的图像。唯一的要求是对图像源的更改在创建后不得影响图案,这可能意味着也可能并不意味着图像是在内部复制的,总是或只是在条件要求时:
修改之后创建CanvasPattern对象时使用的图像 调用createPattern()方法不得影响模式 由CanvasPattern对象渲染。
在任何情况下,上面的例子都应该有足够的肉来为你提供如何解决问题的基础和理解。根据需要修改!