我曾经用 Python 海龟图形生成过这个图像,并有用 JavaScript 复制它的强烈冲动。

对于那些不熟悉海龟图形的人来说,这是一个使用虚拟“海龟”绘制图形的概念,当海龟四处移动时,它的尾巴会在屏幕上留下痕迹。海龟有几个简单的命令:向前/向后移动 x 步,向左/向右转 x 度等。

所以例如这个序列:

  • 前进100步
  • 向左转90度
  • 前进100步
  • 向左转90度
  • 前进100步
  • 向左转90度
  • 前进100步

会画一个正方形。每次移动后,乌龟的位置和方向都会更新,下一步移动将相对于之前的位置。有点类似于Canvas Path(你可以有一系列的 lineTo),但 Path 只能使用笛卡尔坐标(x 和 y)而不是方向(左/右/前/后)。

如果海龟朝北开始,左转 90 度它将指向西。再左 90 度会指向南等。

可以在 Logo(自20世纪60年代)和 Python 中使用海龟图形,但不能在 JavaScript 中使用。

但真的是这样吗?我突然意识到 context.rotate 基本上模仿 “左转/右转”,而 context.translatemoveTo/drawTo 结合起来就像“前进/后退”一样。

*这绝对不是一种优雅或可扩展的图形编程方式 —— 有点像用蚯蚓绑鞋子*:它看起来很酷,但只适用于某些条件。这些只是我的奇怪代码集中的另一个小发明。如果你玩真正的海龟图形,我建议你使用提供这种功能的 JS 库、Python 或 Logo。或者至少先创建移动和旋转海龟的功能,以便使你的代码可读。

我的第一反应是创建一个具有自己的坐标和方向的海龟对象,然后使用 trig 函数计算移动,但是 rotate/translate 解决方案肯定更有趣,并允许我几乎逐行翻译 Python 程序:

首先,让我们看一下 rotate 和 translate 方法的工作原理。他们基本上改变了坐标系:

  • rotate 旋转一个角度
  • translate 通过向量移动它

It’s easiest to see it in an example: 通过下面这个例子中最容易理解:

img

<html>
<style>
	body {  background-color: black;}
</style>
<body>
  <canvas id='myCanvas' width='800' height='600'></canvas>
  <script>
    function line(x1, y1, x2, y2) {
      context.beginPath();
      context.moveTo(x1, y1);
      context.lineTo(x2, y2);
      context.stroke();
    }

    function drawAxles() {
      line(-length, 0, length, 0);  // x axis
      line(length * .9, length * .1, length, 0);
      line(length * .9, -length * .1, length, 0);
      line(0, -length, 0, length);  // y axis
      line(-length * .1, length * .9, 0, length);
      line(length * .1, length * .9, 0, length);
    }

    let length = 100;
    let canvas = document.getElementById('myCanvas');
    let context = canvas.getContext('2d');

    context.strokeStyle = 'white';
    drawAxles();
    context.translate(length * 3, length);
    context.strokeStyle = 'blue';
    drawAxles();
    context.rotate(Math.PI / 8);
    context.strokeStyle = 'red';
    drawAxles();
    context.translate(3 * length, 0);
    context.strokeStyle = 'green';
    drawAxles();
  </script>
</body>

左上角的白色(半)箭头是 HTML5 Canvas 的标准初始 X(水平)和 Y 轴(垂直)。注意,Y 轴指向下方 —— 与你在学校学到的笛卡尔坐标系相反。

轴的负部分位于屏幕之外。

现在我们用 translate 来向右和向下移动坐标系 —— 也就是这些蓝色箭头。

接下来,我们将坐标系旋转几度并绘制红色箭头。请注意,原点(0, 0)仍然与蓝色原点位于同一位置。

最后,我们将系统在 x 轴上移动并将其绘制为绿色。请注意,上一步的轮换仍然适用。

现在让我们看看原始 Python 代码的 JavaScript 版本:

<html>
<style>
	body {  background-color: black;}
</style>
<canvas id='myCanvas' width='800' height='600'></canvas>
  <body>
    <script>
      let colors = ['red', 'purple', 'blue', 'green', 'orange', 'yellow'];
      let canvas = document.getElementById('myCanvas');
      let context = canvas.getContext('2d');
      context.scale(.3, .3);
      context.translate(canvas.width, canvas.height);
      for (let i = 0; i < 360; i++) {
        context.strokeStyle = colors[i % 6];
        context.lineWidth = i / 100 + 1;
        context.beginPath();
        context.moveTo(0, 0);
        context.lineTo(0, i);
        context.stroke();
        context.translate(0, i);
        context.rotate(-59 * (2 * Math.PI / 360));
      }
    </script>
  </body>
</canvas>

在第[11]行中,我缩小了图像。否则如果我保留原始的 Python 维度,它将会非常大。

[12] 将“海龟”移到画布的右下角。

[13-22] 绘制螺旋的主循环

[14]通过颜色数组([8])循环

[15]随着螺旋的增长改变线宽。它几乎不可见。

[16-20] 通过 i 步长向前移动海龟。 [16-19] 画线,[20] 移动海龟。所以我们首先绘制线,并在事后更新“海龟”的位置。

当海龟离开中心时,线的长度变长。

[21] 将海龟旋转 59 度。负号只是为了保持螺旋方向。

现在让我们把螺旋旋转一下。只需几行代码就可以改变海龟转动的角度。我使用正弦函数[10]来实现,但如果你不是三角函数的粉丝,也可以使用不同的公式。甚至像 let rotation = counter / speed; 这样简单的东西产生有趣的结果(确保根据自己的喜好调整 [32] 中的速度)。

<html>
<style>
	body {  background-color: black;}
</style>
<body>
  <canvas id='myCanvas' width='800' height='600'></canvas>
    <script>

    function animate() {
      let rotation = (2 * Math.sin(counter / (3.14 * speed)));
      context.setTransform(scale, 0, 0, scale, canvas.width / 2, canvas.height / 2);
      context.clearRect(-canvas.width / 2, -canvas.height, canvas.width, canvas.height *2);
      for (let i = 0; i < 360; i++) {
        context.strokeStyle = colors[i % 6];
        context.lineWidth = i / 100 + 1;
        context.beginPath();
        context.moveTo(0, 0);
        context.lineTo(0, i);
        context.stroke();
        context.translate(0, i);
        context.rotate((-60 + rotation) * 2 * Math.PI / 360);
      }
      window.requestAnimationFrame(animate);
      counter++;
    }

    let colors = ['red', 'purple', 'blue', 'green', 'orange', 'yellow'];
    let canvas = document.getElementById('myCanvas');
    let context = canvas.getContext('2d');
    let counter = 0;
    let scale = .3;
    let speed = 20;
    animate();
    </script>
  </canvas>
</body>

以下这些参源可以帮你了解更多图形学方面的东西:

Optical illusion (18 lines)

Spinning squares - visual effect (25 lines)

Oldschool fire effect (20 lines)

Fireworks (60 lines)

Animated fractal (32 lines)

Minesweeper game (100 lines)

Physics engine for beginners

Physics engine - interactive sandbox

Physics engine - silly contraption

Starfield (21 lines)

Yin Yang with a twist (4 circles and 20 lines)

Tile map editor (70 lines)

Sine scroller (30 lines)

Interactive animated sprites

Image transition effect (16 lines)

Wholla lotta quadratic curves (50 lines)

Your first program in JavaScript: you need 5 minutes and a notepad