Nested Frames of Reference
When we are in a city and someone asks us for directions to another part of
the city, we usually don't direct them using latitude and longitude as though
they were coming from a different part of the globe, never mind coordinates
to find the Earth in the solar system in the Milky Way Galaxy in the universe.
Same thing when we are in a mall or even in a single room.
It is usually much more convenient and less confusing to direct someone using
a local frame of reference rather than an absolute frame of reference.
In this example, we add details to the windows and the red door in the yellow
house, and we make drawing those details simpler by translating to a local frame
of reference.
We start by defining a
drawWindow()
function. In the yellow house, the first window is a rectangle positioned at
(15, 20) in the local coordinates of the house with a width of 20 and a height
of 40. As long as a window was just a rectangle, it made sense to use the
context.fillRect()
method to position the window. But now that a window is going to be a little drawing on
its own, with lots of rectangles inside of it, we are going to turn it into a
function and position it using the
context.translate()
method.
function drawWindow(x, y) {
context.save();
context.translate(x, y);
context.fillStyle = 'White';
context.fillRect(0, 0, 20, 40); // White border
context.fillStyle = '#99CCFF';
context.fillRect(2, 2, 7, 7); // Pane of glass
context.fillRect(11, 2, 7, 7);
context.fillRect(2, 11, 7, 7);
context.fillRect(11, 11, 7, 7);
context.fillRect(2, 20, 16, 18);
context.restore();
}
Note that the white rectangle we draw to create a white border for the window
is positioned at (0, 0) and the first pane of glass is positioned at (2, 2). Those
coordinates are in the local frame of reference of the window. We don't care where
the window is positioned in the house or where the house is positioned on the canvas.
Next, we define a
drawRedDoor()
function and draw the red door using local coordinates also:
function drawRedDoor(x, y) {
context.save();
context.translate(x, y);
context.fillStyle = '#FF0000';
context.fillRect(0, 0, 30, 60); // Red door
context.fillStyle = '#99CCFF';
context.fillRect(6, 6, 18, 12); // Pane of glass
context.fillStyle = '#FFD700';
context.fillRect(22, 28, 5, 5); // Door knob
context.fillRect(10, 45, 10, 5); // Mail slot
context.restore();
}
Again, note that the window in the door is positioned at (6, 6) in the
local coordinates of the door. If we were going to give the window a stained
glass design instead of making it a simple rectangle, we would draw the
stained glass design by translating into the local coordinates of the door's
window. It makes our drawing simpler and less confusing.
Now that we are using functions to draw our windows and red door, we update the
drawYellowHouse()
function:
function drawYellowHouse(x, y) {
context.save();
context.translate(x, y);
context.fillStyle = '#FFFF00';
context.fillRect(0, 0, 120, 100);
drawWindow(15, 20);
drawWindow(85, 20);
drawRedDoor(45, 40);
context.restore();
}
Finally, we draw two yellow houses, one at (100, 50) and the other at (200, 50).
drawYellowHouse(50, 100);
drawYellowHouse(200, 100);
Change the coordinates of one of the yellow houses to see the entire house move
relative to the canvas. Change the coordinates of one of the windows inside the
drawYellowHouse()
function to see an entire window move relative to the yellow house. Drawing nested
objects in local coordinates is much easier than drawing them in absolute coordinates.
Quick Reference:
Coordinates
Variables
Functions
fillRect()
fillStyle
save() / restore()