Canvas学习:坐标变换

发布于 大漠

Canvas里的坐标系统一节中,了解到Canvas的坐标系统如下图所示,它以Canvas画布的左上角为原点(也就是(0,0)),x坐标向右方增长,而y坐标则向下方延伸。

然而,Canvas的坐标系统并不是一尘不变的。可以对Canvas坐标系统进行移动旋转缩放等操作。而这些操作被称为坐标变换。如下图所示:

在很多场景中,Canvas的坐标变换可以让我们的操作变得更简单,更灵活。今天这节我们主要来了解Canvas中的坐标变换相关的知识。

移动Translating

先来看坐标变换中的移动,即translate方法,它用来移动Canvas和它的原点到一个不同的位置。

ctx.translate(x, y)

translate方法接受两个参数。x是左右偏移量,y是上下偏移量。当偏移量操作出Canvas的widthheight时,坐标将会移出Canvas的画布,这个时候你绘制的东西都将看不到。

来看一个移动的示例。为了后面更好的介绍坐标的变换,我先绘制一个坐标系统:

function drawGrid(ctx, w, h, stroke, steps){
    ctx.save();
    ctx.beginPath();
    for (var i = 0.5; i < w; i += steps) {
      ctx.moveTo(i, 0);
      ctx.lineTo(i, h);
    }
    for(var i = 0.5; i < h; i += steps) {
      ctx.moveTo(0, i);
      ctx.lineTo(w, i);
    }
    ctx.strokeStyle = stroke;
    ctx.stroke();
    ctx.restore();
}

通过drawGrid(ctx, w, h, '#eee', 10);绘制一个恢复坐标系统,然后我们通过translate()方法来移动坐标系统:

drawGrid(ctx, w, h, '#eee', 10);

ctx.translate(40, 40);
drawGrid(ctx, w - 40, h - 40, 'hsl(10,70%,80%)', 10);

看到的效果如下:

从上图可以看出,后面绘制出来的坐标(红色)向右向下偏移了40

可以在Canvas中移动坐标系统之后,有很多时候让我们的操作变得更为方便,比如下图这样的效果:

在不使用坐标变换之前,我们需要对每个矩形的起点坐标定位:

for(var i = 0; i < 4; i++) {
    for (var j = 0; j < 4; j++) {
        ctx.fillStyle = 'rgba(' + ( 120 + j * 10 ) + ',' + ( 185 - i * 10 ) + ',' + ( 10 + j * 3 ) + ',1)';

        ctx.fillRect(i * (w - 50) / 4 + (i + 1) * 10 , j * (h - 50) / 4 + (j + 1) * 10 , (w - 50) / 4, (h - 50) / 4);
    }
}

是不是感觉每次要算矩形的起点很麻烦,使用Canvas的坐标移动之后,就显得容易多了。

for(var i = 0; i < 4; i++) {
    for (var j = 0; j < 4; j++) {
        ctx.fillStyle = 'rgba(' + ( 120 + j * 10 ) + ',' + ( 185 - i * 10 ) + ',' + ( 10 + j * 3 ) + ',1)';
        ctx.save();

        // 通过translate移动坐标
        ctx.translate(i * (w - 50) / 4  + (i + 1) * 10, j * (h - 50) / 4  + (j + 1) * 10);
        // 每一个矩形的起点都是(0,0)
        ctx.fillRect(0, 0, (w - 50) / 4, (h - 50) / 4);
    }
}

得到的效果是一样的

旋转Rotating

上一节看到的是坐标变换中的移动,接下来看第二个方法rotate,即旋转,它用于以原点为中心旋转Canvas画布。

ctx.rotate(angle)

rotate()方法只接受一个参数,旋转的角度angle,它是顺时针方向的,以弧度为单位的值。

同样的,我们来看一个示例。

// 默认坐标系统
drawGrid(ctx, w, h, '#eee', 10);

// 旋转后坐标系统
ctx.rotate(Math.PI / 6);
drawGrid(ctx, w, h, 'hsl(10,70%,80%)', 10);

这个时候坐标系统旋转了Math.PI / 6也就是30度:

从上面的可以看出,坐标旋转它始终是围绕Canvas坐标原点(0,0)进行旋转,如果需要改变的话,就要借助坐标变换中的移动translate()方法。

比如下面这个效果:

先通过translate()方法将坐标移动到Canvas画布的中心,然后rotate()方法旋转的中心都在移动后的画布原点(也就是说,当初的(0,0)变成现在的(w/2,h/2))。

drawGrid(ctx, w, h, '#eee', 10);
// 将Canvas画布移动到画布的中心位置
ctx.translate(w / 2, h / 2);
    
for(var i = 0; i < 10; i++) {
    ctx.save();
    ctx.fillStyle = 'rgb(' + (51 * i ) + ',' + ( 255 - 51 * i ) + ',255)';
      
    for (var j = 0; j < i * 10; j++) {
    	// 旋转坐标
        ctx.rotate(Math.PI * 2 / (i * 10));
        ctx.beginPath();
        // 绘制圆
        ctx.arc(0, i * 20, 5, 0, Math.PI * 2, true);
        ctx.fill();
    }
    ctx.restore();
}

这样一来你将看到上图的效果

在很我实际场景中,我们对某个图形元素做旋转,默认情况之下,其旋转都会围绕Canvas坐标系统原点(0,0)进行旋转。但实际上,我们需要围绕元素中心点来做旋转。在CSS中,我们有一个transform-origin属性可以修改原点。但在Canvas中,就需要借助Canvas的坐标变换中的translate()方法来修改元素的原点,也就是将原点移动到元素的中心位置。

比如我们绘制一个矩形:

var x = 100;
var y = 100;
var width = 100;
var height = 100;
ctx.strokeRect(x, y, width, height);

上面将一个正方形绘制在100, 100位置处:

这个时候如果我们直接旋转的话,正方形只为围绕(0, 0)位置旋转,并不会围绕正形的中心点(150, 150)做旋转。接下来,使用ctx.translate()方法,将画布原点(0, 0)平移到正方形的中心点位置处。这个时候ctx.translate()方法中的xy对应的就是:

x + width / 2;
y + height / 2;

也就是说ctx.translate(x + width / 2; y + height / 2)。这个时候绘制正方形,我们就不能再是ctx.strokeRect(x, y, width, height)了。因为我们将坐标移动了。为了将元素的中心点和坐标点在同一个地方,此时绘制正方形应该是ctx.strokeRect(-width / 2, -height / 2, width, height)。如下图所示:

那么要围绕元素中心旋转就很方便了,在绘制矩形之前,再使用ctx.rotate()方法。我们就可以得到这样的效果:

缩放Scaling

接着是坐标变换中的缩放。我们用它来增减图形在Canvas中的像素数目,对形状,位图进行缩小或者放大。

ctx.scale(x, y)

scale()方法接受两个参数。xy分别是横轴和纵轴的缩放因子。其缩放因子默认是1,如果比1小是缩小,如果比1大则放大。

来看一个示例,将坐标系统放大两倍:

drawGrid(ctx, w, h, '#eee', 10);

ctx.scale(2, 2);
drawGrid(ctx, w, h, 'hsl(10,70%,80%)', 10);

效果如下:

前面我们介绍旋转的时候,配合ctx.translate()可以实现围绕元素的中心进行旋转,同样的,在这里也能实现元素围绕其中心进行缩放。

var x = 100;
var y = 100;
var width = 100;
var height = 100;
ctx.translate(x + width / 2, y + height / 2);
ctx.scale(2, 2);
ctx.strokeRect(width / 2, height / 2, width, height);

前面找元素中心位置看到的示例都是正方形的示例,那么对于任何形状的中心怎么来寻找呢?其实其他形状和正方形类似:只要在缩放、旋转或者组合旋转缩放前将原点平移到形状的中心,都可以得到想要的效果。记住,任何形状的中心点都是半宽的x值和半高的y值。这需要使用边界框理论找到中心点。

坐标变换中的缩放可以用于实现很多不同的效果,比如说,在绘制了某个图形后,可以调用ctx.scale(-1, 1)来绘制其水平镜像或者调用ctx.scale(1, -1)来绘制其垂直镜像。如下所示:

ctx.save();
ctx.strokeStyle = '#f36';
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(150, 150);
ctx.lineTo(100, 200);
ctx.closePath();
ctx.stroke();

ctx.translate(350, 0);
ctx.strokeStyle = 'lime';
ctx.beginPath();
ctx.moveTo(-175, 0.5);
ctx.lineTo(-175, 300.5);
ctx.stroke();
ctx.scale(-1, 1);
ctx.save();
ctx.strokeStyle = '#000';
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(150, 150);
ctx.lineTo(100, 200);
ctx.closePath();
ctx.stroke();

上面的代码得到一个水平镜像的效果:

缩合示例

前面分别介绍了Canvas坐标变换中的移动、旋转和缩放。在实际使用当中,可以将这三者结合起来,可以帮助我们轻易实现需要的效果。比如下面这个效果,就是将三者结合在一起。

总结

这篇文章主要介绍了Canvas坐标系统中的坐标变换。Canvas坐标变换有移动旋转缩放。在实际中,将其结合在一起,可以帮助我们轻易需要的效果。

方法 描述
rotate(angle) 按照给定的角度来旋转坐标系,angle是一个弧度值
scale(x, y) xy方向上分别按照给定的数值来缩放坐标系,默认值是1,小于1是缩小,反之是放大
translate(x, y) 将坐标平移到给定的xy坐标处

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.fedev.cn/canvas/transformation-coordinates.html