Turtle graphics in JavaScript the goofball way
I ran across this image generated using Python turtle graphics:
(source: Geeks for geeks) and immediately felt an overwhelming urge to replicate it in JavaScript.
For those not familiar with turtle graphics, it is a concept of drawing graphics using a virtual 'turtle', whose tail leaves marks on the screen as the turtle moves around. The turtle understands several simple commands: move forward/back x steps, turn left/right x degrees etc.
So for example this sequence:
move forward 100 steps
turn left 90 degrees
move forward 100 steps
turn left 90 degrees
move forward 100 steps
turn left 90 degrees
move forward 100 steps
would draw a square. After every move, the position and direction of the turtle is updated and the next move is relative to the previous position. A bit similar to the Canvas Path (where you can have a sequence of lineTo's), but the Path can only use Cartesian coordinates (x and y) instead of directions (left/right/forward/back).
If the turtle starts out facing North, a left 90-degree turn would point him West. Another left 90 degrees would point him South etc.
Turtle graphics is available for example in Logo (since 1960s) and Python, but not in JavaScript.
Or is it? It dawned on me that context.rotate basically emulates 'turn left/right' and context.translate combined with moveTo/drawTo behaves just like 'move forward/back'.
This is definitely not an elegant or scalable way of programming graphics - kind of like tying your shoe with an earthworm*: it looks cool, but only works in certain conditions. Just another gizmo in my collection of weird code. If you to play with real turtle graphics, I suggest you use one of the many available JS libraries, Python or Logo. Or at least create actual functions that move and rotate the turtle to make your code readable.
My first reaction was to create a turtle object with its own coordinates and direction and then calculate the moves using trig functions, but the rotate/translate solution is definitely more interesting and allowed me to translate the Python program almost line-for-line:
First, let's look at how the rotate and translate methods work. They basically change the coordinate system:
- rotate rotates by an angle
- translate shifts it by a vector
It's easiest to see it in an example:
Here's the code that generated this image:
<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> |
The white (half-)arrows in the upper left corner are the standard initial X (horizontal) and Y axles (vertical) of the HTML5 Canvas. Note that the Y axis is pointing down - as opposed to the Cartesian system you learned in school.
The negative parts of the axles are outside of the screen.
Now we're using translate to move the coordinate system right and down - these are the blue arrows.
Next, we're rotating the coordinate system by several degrees and draw the red arrows. Note that the origin (0,0) is still in the same spot as for the blue one.
Finally, we're shifting the system on the x axis and draw it in green. Please note that the rotation from the previous step still applies.
Now let's see the JavaScript version of the original Python code:
<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> |
In line [11] I scaled our image down. Otherwise it would be very large if I kept the original Python dimensions.
[12] move the 'turtle' to the bottom right corner of the canvas.
[13-22] the main loop that draws the spiral
[14] cycle through the colors array ([8])
[15] change the line width as the spiral grows. It's barely visible, but (just like [11]), I kept it just to remain faithful to the Python version.
[16-20] move the turtle forward by 'i' steps. [16-19] draw the line, [20] move the turtle. So we really draw the line first and update the 'turtle' position after the fact.
The length of the line gets longer as the turtle moves away from the center.
[21] rotate the turtle by 59 degrees. The negative sign is only to keep the spiral direction faithful to the original.
Now let's take our spiral for a spin. With just a couple of additional lines, we can change the angle by which the turtle turns. I used the sine function [10] to achieve pulsation, but if you're not a fan of trigonometry, feel free to use a different formula. Even something as simple as 'let rotation = counter / speed;' leads to interesting results (make sure you adjust speed in [32] to your liking).
<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> |
</body> |
</canvas> |
* No animals were harmed in making of this tutorial.
Check out these programming tutorials:
JavaScript:
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
Fractals in Excel
Python in Blender 3d:
Domino effect (10 lines)
Wrecking ball effect (14 lines)
3d fractal in Blender Python