JAVASCRIPT CHAPTER 5 2018-12-19T12:52:35+00:00

JAVASCRIPT  Chapter 5

Topics:- (Project: A Platform Game, SVG, The canvas, Lines & surfaces, Paths, Curves, Drawing a pie chart, Text, Images, Transformation, Choosing a graphic interface, The protocol, Browser & HTTP, Fetch Security & HTTPS, Forms, Focus, Checkbox & Radio buttons, Select fields, File Fields, Storing data client-side )

Project: A Platform Game

Much of our initial fascination with computers, like that of many nerdy kids, had to do with computer games.We were drawn into the tiny simulated worlds that we could manipulate and in which stories (sort of) unfolded—more, we suppose, because of the way we projected our imagination into them than because of the possibilities they actually offered. We don’t wish a career in game programming on anyone. Much like the music industry, the discrepancy between the number of eager young people wanting to work in it and the actual demand for such people creates a rather unhealthy environment. But writing games for fun is amusing. This  will walk through the implementation of a small platform game. Platform games (or “jump and run” games) are games that expect the player to move a figure through a world, which is usually two-dimensional and viewed from the side, while jumping over and onto things.

The game

Our game will be roughly based on Dark Blue (www.lessmilk.com/games/10) by Thomas Palef. We chose that game because it is both entertaining and minimalist and because it can be built without too much code. It looks like this:

The dark box represents the player, whose task is to collect the yellow boxes (coins) while avoiding the red stuff (lava). A level is completed when all coins have been collected. The player can walk around with the left and right arrow keys and can jump with the up arrow. Jumping is a specialty of this game character. It can reach several times its own height and can change direction in midair. This may not be entirely realistic, but it helps give the player the feeling of being in direct control of the on-screen avatar. The game consists of a static background, laid out like a grid, with the moving elements overlaid on that background. Each field on the grid is either empty, solid, or lava. The moving elements are the player, coins, and certain pieces of lava. The positions of these elements are not constrained to the grid—their coordinates may be fractional, allowing smooth motion.

The technology

We will use the browser DOM to display the game, and we’ll read user input by handling key events. The screen- and keyboard-related code is only a small part of the work we need to do to build this game. Since everything looks like colored boxes, drawing is uncomplicated: we create DOM elements and use styling to give them a background color, size, and position. We can represent the background as a table since it is an unchanging grid of squares. The free-moving elements can be overlaid using absolutely positioned elements.

In games and other programs that should animate graphics and respond to user input without noticeable delay, efficiency is important. Although the DOM was not originally designed for high-performance graphics, it is actually better at this than you would expect. On a modern machine, a simple game like this performs well, even if we don’t worry about optimization very much.Later, we will explore another browser technology, the tag, which provides a more traditional way to draw graphics, working in terms of shapes and pixels rather than DOM elements.

Levels

We’ll want a human-readable, human-editable way to specify levels. Since it is okay for everything to start out on a grid, we could use big strings in which each character represents an element—either a part of the background grid or a moving element. The plan for a small level might look like this:

Periods are empty space, hash (#) characters are walls, and plus signs are lava. The player’s starting position is the at sign (@). Every O character is a coin, and the equal sign (=) at the top is a block of lava that moves back and forth horizontally.

We’ll support two additional kinds of moving lava: the pipe character (|) creates vertically moving blobs, and v indicates dripping lava—vertically moving lava that doesn’t bounce back and forth but only moves down, jumping back to its start position when it hits the floor. A whole game consists of multiple levels that the player must complete. A level is completed when all coins have been collected. If the player touches lava, the current level is restored to its starting position, and the player may try again.

Reading a level

The following class stores a level object. Its argument should be the string that defines the level.

class Level {

constructor(plan) {

      let rows = plan.trim().split(“\n”).map(l => […l]);

      this.height = rows.length;

      this.width = rows[0].length;

      this.startActors = []

      this.rows = rows.map((row, y) => {

return row.map((ch, x) => {

let type = levelChars[ch];

if (typeof type == “string”) return type;

this.startActors.push(

           type.create(new Vec(x, y), ch));

return “empty”;

             });

      });

    }

}

The trim method is used to remove whitespace at the start and end of the plan string. This allows our example plan to start with a newline so that all the lines are directly below each other. The remaining string is split on newline characters, and each line is spread into an array, producing arrays of characters. So rows holds an array of arrays of characters, the rows of the plan. We can derive the level’s width and height from these. But we must still separate the moving elements from the background grid. We’ll call moving elements actors. They’ll be stored in an array of objects. The background will be an array of arrays of strings, holding field types such as “empty”, “wall”, or “lava”.

To create these arrays, we map over the rows and then over their content. Remember that map passes the array index as a second argument to the mapping function, which tells us the x- and y-coordinates of a given character. Positions in the game will be stored as pairs of coordinates, with the top left being 0,0 and each background square being 1 unit high and wide. To interpret the characters in the plan, the Level constructor uses the levelChars object, which maps background elements to strings and actor characters to classes. When type is an actor class, its static create method is used to create an object, which is added to startActors, and the mapping function returns “empty” for this background square. The position of the actor is stored as a Vec object. This is a two-dimensional vector, an object with x and y propertiesAs the game runs, actors will end up in different places or even disappear entirely (as coins do when collected). We’ll use a State class to track the state of a running game.

class State {

   constructor(level, actors, status) {

      this.level = level;

      this.actors = actors;

      this.status = status;

}

static start(level) {

     return new State(level, level.startActors, “playing”);

}

get player() {

     return this.actors.find(a => a.type == “player”);

     }

}

The status property will switch to “lost” or “won” when the game has ended. This is again a persistent data structure—updating the game state creates a new state and leaves the old one intact.

Actors

Actor objects represent the current position and state of a given moving element in our game. All actor objects conform to the same interface. Their pos property holds the coordinates of the element’s top-left corner, and their size property holds its size. Then they have an update method, which is used to compute their new state and position after a given time step. It simulates the thing the actor does— moving in response to the arrow keys for the player and bouncing back and forth for the lava—and returns a new, updated actor object. A type property contains a string that identifies the type of the actor— player”, “coin”, or “lava”. This is useful when drawing the game—the look of the rectangle drawn for an actor is based on its type. Actor classes have a static create method that is used by the Level constructor to create an actor from a character in the level plan. It is given the coordinates of the character and the character itself, which is needed because the Lava class handles several different characters. This is the Vec class that we’ll use for our two-dimensional values, such as the position and size of actors.

class Vec {

  constructor(x, y) {

        this.x = x; this.y = y;

}

plus(other) {

       return new Vec(this.x + other.x, this.y + other.y);

}

times(factor) {

    return new Vec(this.x * factor, this.y * factor);

     }

}

The times method scales a vector by a given number. It will be useful when we need to multiply a speed vector by a time interval to get the distance traveled during that time. The different types of actors get their own classes since their behavior is very different. Let’s define these classes. We’ll get to their update methods later. The player class has a property speed that stores its current speed to simulate momentum and gravity.

class Player {

  constructor(pos, speed) {

     this.pos = pos;

     this.speed = speed;

}

get type() { return “player”; }

static create(pos) {

     return new Player(pos.plus(new Vec(0, -0.5)),

                                            new Vec(0, 0));

     }

}

Player.prototype.size = new Vec(0.8, 1.5);

Because a player is one-and-a-half squares high, its initial position is set to be half a square above the position where the @ character appeared. This way, its bottom aligns with the bottom of the square it appeared in.

The size property is the same for all instances of Player, so we store it on the prototype rather than on the instances themselves. We could have used a getter like type, but that would create and return a new Vec object every time the property is read, which would be wasteful. (Strings, being immutable, don’t have to be recreated every time they are evaluated.) When constructing a Lava actor, we need to initialize the object differently depending on the character it is based on. Dynamic lava moves along at its current speed until it hits an obstacle. At that point, if it has a reset property, it will jump back to its start position (dripping). If it does not, it will invert its speed and continue in the other direction (bouncing).

The create method looks at the character that the Level constructor passes and creates the appropriate lava actor.

class Lava {

   constructor(pos, speed, reset) {

     this.pos = pos;

     this.speed = speed;

     this.reset = reset;

}

get type() { return “lava”; }

static create(pos, ch) {

if (ch == “=”) {

    return new Lava(pos, new Vec(2, 0));

} else if (ch == “|”) {

    return new Lava(pos, new Vec(0, 2));

} else if (ch == “v”) {

    return new Lava(pos, new Vec(0, 3), pos);

        }

    } 

}

Lava.prototype.size = new Vec(1, 1);

Coin actors are relatively simple. They mostly just sit in their place. But to liven up the game a little, they are given a “wobble”, a slight vertical back-and-forth motion. To track this, a coin object stores a base position as well as a wobble property that tracks the phase of the bouncing motion. Together, these determine the coin’s actual position (stored in the pos property).

class Coin {

  constructor(pos, basePos, wobble) {

   this.pos = pos;

   this.basePos = basePos;

   this.wobble = wobble;

}

get type() { return “coin”; }

static create(pos) {

    let basePos = pos.plus(new Vec(0.2, 0.1));

return new Coin(basePos, basePos,

                            Math.random() * Math.PI * 2);

        }

}

Coin.prototype.size = new Vec(0.6, 0.6);

We saw that Math.sin gives us the y-coordinate of a point on a circle. That coordinate goes back and forth in a smooth wave form as we move along the circle, which makes the sine function useful for modeling a wavy motion. To avoid a situation where all coins move up and down synchronously, the starting phase of each coin is randomized. The phase of Math.sin’s wave, the width of a wave it produces, is 2 . We multiply the value returned by Math.random by that number to give the coin a random starting position on the wave. We can now define the levelChars object that maps plan characters to either background grid types or actor classes.

const levelChars = {

“.”: “empty”, “#”: “wall”, “+”: “lava”,

“@”: Player, “o”: Coin,

“=”: Lava, “|”: Lava, “v”: Lava

};

That gives us all the parts needed to create a Level instance.

let simpleLevel = new Level(simpleLevelPlan);

console.log(`${simpleLevel.width} by ${simpleLevel.height}`);

// → 22 by 9

The task ahead is to display such levels on the screen and to model time and motion inside them.

Encapsulation as a burden

Most of the code does not worry about encapsulation very much for two reasons. First, encapsulation takes extra effort. It makes programs bigger and requires additional concepts and interfaces to be introduced. Since there is only so much code you can throw at a reader before their eyes glaze over, we’ve made an effort to keep the program small.

Second, the various elements in this game are so closely tied together that if the behavior of one of them changed, it is unlikely that any of the others would be able to stay the same. Interfaces between the elements would end up encoding a lot of assumptions about the way the game works. This makes them a lot less effective—whenever you change one part of the system, you still have to worry about the way it impacts the other parts because their interfaces wouldn’t cover the new situation. Some cutting points in a system lend themselves well to separation through rigorous interfaces, but others don’t. Trying to encapsulate something that isn’t a suitable boundary is a sure way to waste a lot of energy. When you are making this mistake, you’ll usually notice that your interfaces are getting awkwardly large and detailed and that they need to be changed often, as the program evolves. There is one thing that we will encapsulate, and that is the drawing subsystem. The reason for this is that we’ll display the same game in a different way later. By putting the drawing behind an interface, we can load the same game program there and plug in a new display module.

Drawing

The encapsulation of the drawing code is done by defining a display object, which displays a given level and state. The display type we define here is called DOMDisplay because it uses DOM elements to show the level. We’ll be using a style sheet to set the actual colors and other fixed properties of the elements that make up the game. It would also be possible to directly assign to the elements’ style property when we create them, but that would produce more verbose programs. The following helper function provides a succinct way to create an element and give it some attributes and child nodes:

function elt(name, attrs, …children) {

let dom = document.createElement(name);

for (let attr of Object.keys(attrs)) {

dom.setAttribute(attr, attrs[attr]);

}

for (let child of children) {

     dom.appendChild(child);

     }

return dom;

}

A display is created by giving it a parent element to which it should append itself and a level object.

class DOMDisplay {

   constructor(parent, level) {

     this.dom = elt(“div”, {class: “game”}, drawGrid(level));

    this.actorLayer = null;

    parent.appendChild(this.dom);

     }

clear() { this.dom.remove(); }

}

The level’s background grid, which never changes, is drawn once. Actors are redrawn every time the display is updated with a given state. The actorLayer property will be used to track the element that holds the actors so that they can be easily removed and replaced. Our coordinates and sizes are tracked in grid units, where a size or distance of 1 means one grid block. When setting pixel sizes, we will have to scale these coordinates up—everything in the game would be ridiculously small at a single pixel per square. The scale constant gives the number of pixels that a single unit takes up on the screen.

const scale = 20;

function drawGrid(level) {

    return elt(“table”, {

       class: “background”,

      style: `width: ${level.width * scale}px`

}, …level.rows.map(row =>

     elt(“tr”, {style: `height: ${scale}px`},

          …row.map(type => elt(“td”, {class: type})))

     ));

}

As mentioned, the background is drawn as a

element. This nicely corresponds to the structure of the rows property of the level—each row of the grid is turned into a table row ( element). The strings in the grid are used as class names for the table cell (

) elements. The spread (triple dot) operator is used to pass arrays of child nodes to elt as separate arguments.The following CSS makes the table look like the background we want:

 .background    { background: rgb(52, 166, 251);
                  table-layout: fixed;
                  border-spacing: 0;              }
 .background td { padding: 0;                     }
 .lava          { background: rgb(255, 100, 100); }
 .wall          { background: white;              }

Some of these (table-layout, border-spacing, and padding) are used to suppress unwanted default behavior. We don’t want the layout of the table to depend upon the contents of its cells, and we don’t want space between the table cells or padding inside them. The background rule sets the background color. CSS allows colors to be specified both as words (white) or with a format such as rgb(R, G, B), where the red, green, and blue components of the color are separated into three numbers from 0 to 255. So, in rgb(52, 166, 251), the red component is 52, green is 166, and blue is 251. Since the blue component is the largest, the resulting color will be bluish. You can see that in the .lava rule, the first number (red) is the largest. We draw each actor by creating a DOM element for it and setting that element’s position and size based on the actor’s properties. The values have to be multiplied by scale to go from game units to pixels.function drawActors(actors) {return elt(“div”, {}, …actors.map(actor => {let rect = elt(“div”, {class: `actor ${actor.type}`}); rect.style.width = `${actor.size.x * scale}px`;rect.style.height = `${actor.size.y * scale}px`; rect.style.left = `${actor.pos.x * scale}px`; rect.style.top = `${actor.pos.y * scale}px`; return rect;  }));}To give an element more than one class, we separate the class names by spaces. In the CSS code shown next, the actor class gives the actors their absolute position. Their type name is used as an extra class to give them a color. We don’t have to define the lava class again because we’re reusing the class for the lava grid squares we defined earlier.

 .actor  { position: absolute;            }
 .coin   { background: rgb(241, 229, 89); }
 .player { background: rgb(64, 64, 64);   }

The syncState method is used to make the display show a given state. It first removes the old actor graphics, if any, and then redraws the actors in their new positions. It may be tempting to try to reuse the DOM elements for actors, but to make that work, we would need a lot of additional bookkeeping to associate actors with DOM elements and to make sure we remove elements when their actors vanish. Since there will typically be only a handful of actors in the game, redrawing all of them is not expensive.DOMDisplay.prototype.syncState = function(state) {if (this.actorLayer) this.actorLayer.remove();this.actorLayer = drawActors(state.actors); this.dom.appendChild(this.actorLayer); this.dom.className = `game ${state.status}`; this.scrollPlayerIntoView(state);};By adding the level’s current status as a class name to the wrapper, we can style the player actor slightly differently when the game is won or lost by adding a CSS rule that takes effect only when the player has an ancestor element with a given class..lost .player {background: rgb(160, 64, 64);}.won .player {box-shadow: -4px -7px 8px white, 4px -7px 8px white;}After touching lava, the player’s color turns dark red, suggesting scorching. When the last coin has been collected, we add two blurred white shadows—one to the top left and one to the top right—to create a white halo effect.We can’t assume that the level always fits in the viewport—the element into which we draw the game. That is why the scrollPlayerIntoView call is needed. It ensures that if the level is protruding outside the viewport, we scroll that viewport to make sure the player is near its center. The following CSS gives the game’s wrapping DOM element a maximum size and ensures that anything that sticks out of the element’s box is not visible. We also give it a relative position so that the actors inside it are positioned relative to the level’s top-left corner..game {overflow: hidden;max-width: 600px;max-height: 450px;position: relative;}In the scrollPlayerIntoView method, we find the player’s position and up-date the wrapping element’s scroll position. We change the scroll position by manipulating that element’s scrollLeft and scrollTop properties when the player is too close to the edge.

DOMDisplay.prototype.scrollPlayerIntoView = function(state) {

let width = this.dom.clientWidth;

let height = this.dom.clientHeight;

let margin = width / 3;

// The viewport

let left = this.dom.scrollLeft, right = left + width;

let top = this.dom.scrollTop, bottom = top + height;

let player = state.player;

let center = player.pos.plus(player.size.times(0.5))

                                         .times(scale);

if (center.x < left + margin) {

     this.dom.scrollLeft = center.x – margin;

} else if (center.x > right – margin) {

    this.dom.scrollLeft = center.x + margin – width;

}

if (center.y < top + margin) {

    this.dom.scrollTop = center.y – margin;

} else if (center.y > bottom – margin) {

    this.dom.scrollTop = center.y + margin – height;

    }

};

The way the player’s center is found shows how the methods on our Vec type allow computations with objects to be written in a relatively readable way. To find the actor’s center, we add its position (its top-left corner) and half its size. That is the center in level coordinates, but we need it in pixel coordinates, so we then multiply the resulting vector by our display scale. Next, a series of checks verifies that the player position isn’t outside of the allowed range. Note that sometimes this will set nonsense scroll coordinates that are below zero or beyond the element’s scrollable area. This is okay—the DOM will constrain them to acceptable values. Setting scrollLeft to -10 will cause it to become 0.It would have been slightly simpler to always try to scroll the player to the center of the viewport. But this creates a rather jarring effect. As you are jumping, the view will constantly shift up and down. It is more pleasant to have a “neutral” area in the middle of the screen where you can move around without causing any scrolling. We are now able to display our tiny level.The tag, when used with rel=”stylesheet”, is a way to load a CSS file into a page. The file game.css contains the styles necessary for our game.Motion and collisionNow we’re at the point where we can start adding motion—the most interesting aspect of the game. The basic approach, taken by most games like this, is to split time into small steps and, for each step, move the actors by a distance corresponding to their speed multiplied by the size of the time step. We’ll measure time in seconds, so speeds are expressed in units per second. Moving things is easy. The difficult part is dealing with the interactions between the elements. When the player hits a wall or floor, they should not simply move through it. The game must notice when a given motion causes an object to hit another object and respond accordingly. For walls, the motion must be stopped. When hitting a coin, it must be collected. When touching lava, the game should be lost.Solving this for the general case is a big task. You can find libraries, usually called physics engines, that simulate interaction between physical objects in two or three dimensions. We’ll take a more modest approach in this project, handling only collisions between rectangular objects and handling them in a rather simplistic way. Before moving the player or a block of lava, we test whether the motion would take it inside of a wall. If it does, we simply cancel the motion altogether. The response to such a collision depends on the type of actor—the player will stop, whereas a lava block will bounce back.This approach requires our time steps to be rather small since it will cause motion to stop before the objects actually touch. If the time steps (and thus the motion steps) are too big, the player would end up hovering a noticeable distance above the ground. Another approach, arguably better but more complicated, would be to find the exact collision spot and move there. We will take the simple approach and hide its problems by ensuring the animation proceeds in small steps. This method tells us whether a rectangle (specified by a position and a size) touches a grid element of the given type.

Level.prototype.touches = function(pos, size, type) {

var xStart = Math.floor(pos.x);

var xEnd = Math.ceil(pos.x + size.x);

var yStart = Math.floor(pos.y);

var yEnd = Math.ceil(pos.y + size.y);

for (var y = yStart; y < yEnd; y++) {

      for (var x = xStart; x < xEnd; x++) {

          let isOutside = x < 0 || x >= this.width || y < 0 || y >= this.height;

let here = isOutside ? “wall” : this.rows[y][x];

if (here == type) return true;

       }

}

return false;

};

The method computes the set of grid squares that the body overlaps with by using Math.floor and Math.ceil on its coordinates. Remember that grid squares are 1 by 1 units in size. By rounding the sides of a box up and down, we get the range of background squares that the box touches. We loop over the block of grid squares found by rounding the coordinates and return true when a matching square is found. Squares outside of the level are always treated as “wall” to ensure that the player can’t leave the world and that we won’t accidentally try to read outside of the bounds of our rows array. The state update method uses touches to figure out whether the player is touching lava.

State.prototype.update = function(time, keys) {

let actors = this.actors

      .map(actor => actor.update(time, this, keys));

let newState = new State(this.level, actors, this.status);

if (newState.status != “playing”) return newState;

let player = newState.player;

if (this.level.touches(player.pos, player.size, “lava”)) {

      return new State(this.level, actors, “lost”);

}

for (let actor of actors) {

   if (actor != player && overlap(actor, player)) {

    newState = actor.collide(newState);

    }

}

return newState;

};

The method is passed a time step and a data structure that tells it which keys are being held down. The first thing it does is call the update method on all actors, producing an array of updated actors. The actors also get the time step, the keys, and the state, so that they can base their update on those. Only the player will actually read keys, since that’s the only actor that’s controlled by the keyboard.If the game is already over, no further processing has to be done (the game can’t be won after being lost, or vice versa). Otherwise, the method tests whether the player is touching background lava. If so, the game is lost, and we’re done. Finally, if the game really is still going on, it sees whether any other actors overlap the player. Overlap between actors is detected with the overlap function. It takes two actor objects and returns true when they touch—which is the case when they overlap both along the x-axis and along the y-axis.function overlap(actor1, actor2) {return actor1.pos.x + actor1.size.x > actor2.pos.x && actor1.pos.x < actor2.pos.x + actor2.size.x && actor1.pos.y + actor1.size.y > actor2.pos.y && actor1.pos.y < actor2.pos.y + actor2.size.y;}If any actor does overlap, its collide method gets a chance to update the state. Touching a lava actor sets the game status to “lost”. Coins vanish when you touch them and set the status to “won” when they are the last coin of the level.Lava.prototype.collide = function(state) {     return new State(state.level, state.actors, “lost”);};Coin.prototype.collide = function(state) {    let filtered = state.actors.filter(a => a != this);    let status = state.status;   if (!filtered.some(a => a.type == “coin”)) status = “won”;  return new State(state.level, filtered, status);};Actor updatesActor objects’ update methods take as arguments the time step, the state object, and a keys object. The one for the Lava actor type ignores the keys object.

Lava.prototype.update = function(time, state) {

let newPos = this.pos.plus(this.speed.times(time));

if (!state.level.touches(newPos, this.size, “wall”)) {

    return new Lava(newPos, this.speed, this.reset);

} else if (this.reset) {

  return new Lava(this.reset, this.speed, this.reset);

} else {

return new Lava(this.pos, this.speed.times(-1));

  }

};

This update method computes a new position by adding the product of the time step and the current speed to its old position. If no obstacle blocks that new position, it moves there. If there is an obstacle, the behavior depends on the type of the lava block—dripping lava has a reset position, to which it jumps back when it hits something. Bouncing lava inverts its speed by multiplying it by -1 so that it starts moving in the opposite direction. Coins use their update method to wobble. They ignore collisions with the grid since they are simply wobbling around inside of their own square.const wobbleSpeed = 8, wobbleDist = 0.07;Coin.prototype.update = function(time) {      let wobble = this.wobble + time * wobbleSpeed;      let wobblePos = Math.sin(wobble) * wobbleDist;return new Coin(this.basePos.plus(new Vec(0, wobblePos)),                              this.basePos, wobble);};The wobble property is incremented to track time and then used as an argument to Math.sin to find the new position on the wave. The coin’s current position is then computed from its base position and an offset based on this wave. That leaves the player itself. Player motion is handled separately per axis because hitting the floor should not prevent horizontal motion, and hitting a wall should not stop falling or jumping motion.

const playerXSpeed = 7;

const gravity = 30;

const jumpSpeed = 17;

Player.prototype.update = function(time, state, keys) {

     let xSpeed = 0;

if (keys.ArrowLeft) xSpeed -= playerXSpeed;

      if (keys.ArrowRight) xSpeed += playerXSpeed;

let pos = this.pos;

let movedX = pos.plus(new Vec(xSpeed * time, 0));

if (!state.level.touches(movedX, this.size, “wall”)) {

      pos = movedX;

}

let ySpeed = this.speed.y + time * gravity;

let movedY = pos.plus(new Vec(0, ySpeed * time));

if (!state.level.touches(movedY, this.size, “wall”)) {

     pos = movedY;

} else if (keys.ArrowUp && ySpeed > 0) {

     ySpeed = -jumpSpeed;

} else {

     ySpeed = 0;

}

return new Player(pos, new Vec(xSpeed, ySpeed));

};

The horizontal motion is computed based on the state of the left and right arrow keys. When there’s no wall blocking the new position created by this motion, it is used. Otherwise, the old position is kept. Vertical motion works in a similar way but has to simulate jumping and gravity. The player’s vertical speed (ySpeed) is first accelerated to account for gravity.We check for walls again. If we don’t hit any, the new position is used. If there is a wall, there are two possible outcomes. When the up arrow is pressed and we are moving down (meaning the thing we hit is below us), the speed is set to a relatively large, negative value. This causes the player to jump. If that is not the case, the player simply bumped into something, and the speed is set to zero. The gravity strength, jumping speed, and pretty much all other constants in this game have been set by trial and error. I tested values until I found a combination I liked.Tracking keysFor a game like this, we do not want keys to take effect once per keypress. Rather, we want their effect (moving the player figure) to stay active as long as they are held. We need to set up a key handler that stores the current state of the left, right, and up arrow keys. We will also want to call preventDefault for those keys so that they don’t end up scrolling the page. The following function, when given an array of key names, will return an object that tracks the current position of those keys. It registers event handlers for “keydown” and “keyup” events and, when the key code in the event is present in the set of codes that it is tracking, updates the object.

function trackKeys(keys) {

   let down = Object.create(null);

   function track(event) {

        if (keys.includes(event.key)) {

               down[event.key] = event.type == “keydown”;

               event.preventDefault();

         }

}

window.addEventListener(“keydown”, track);

window.addEventListener(“keyup”, track);

return down;

}

const arrowKeys =

trackKeys([“ArrowLeft”, “ArrowRight”, “ArrowUp”]);

The same handler function is used for both event types. It looks at the event object’s type property to determine whether the key state should be updated to true (“keydown”) or false (“keyup”).Running the gameThe requestAnimationFrame function, provides a good way to animate a game. But its interface is quite primitive—using it requires us to track the time at which our function was called the last time around and call requestAnimationFrame again after every frame. Let’s define a helper function that wraps those boring parts in a convenient interface and allows us to simply call runAnimation, giving it a function that expects a time difference as an argument and draws a single frame. When the frame function returns the value false, the animation stops.

function runAnimation(frameFunc) {

let lastTime = null;

function frame(time) {

    if (lastTime != null) {

          let timeStep = Math.min(time – lastTime, 100) / 1000;

         if (frameFunc(timeStep) === false) return;

}

lastTime = time;

requestAnimationFrame(frame);

}

requestAnimationFrame(frame);

}

We have set a maximum frame step of 100 milliseconds (one-tenth of a second). When the browser tab or window with our page is hidden, requestAnimationFrame calls will be suspended until the tab or window is shown again. In this case, the difference between lastTime and time will be the entire time in which the page was hidden. Advancing the game by that much in a single step will look silly and might cause weird side effects, such as the player falling through the floor.The function also converts the time steps to seconds, which are an easier quantity to think about than milliseconds. The runLevel function takes a Level object and a display constructor and returns a promise. It displays the level (in document.body) and lets the user play through it. When the level is finished (lost or won), runLevel waits one more second (to let the user see what happens) and then clears the display, stops the animation, and resolves the promise to the game’s end status.

function runLevel(level, Display) {

    let display = new Display(document.body, level);

    let state = State.start(level);

     let ending = 1;

return new Promise(resolve => {

      runAnimation(time => {

         state = state.update(time, arrowKeys);

         display.syncState(state);

if (state.status == “playing”) {

        return true;

} else if (ending > 0) {

       ending -= time;

       return true;

} else {

    display.clear();

    resolve(state.status);

    return false;

        }

    });

  });

}

A game is a sequence of levels. Whenever the player dies, the current level is restarted. When a level is completed, we move on to the next level. This can be expressed by the following function, which takes an array of level plans (strings) and a display constructor:async function runGame(plans, Display) {       for (let level = 0; level < plans.length;) {             let status = await runLevel(new Level(plans[level]),                                                                   Display);      if (status == “won”) level++;     }console.log(“You’ve won!”);}Because we made runLevel return a promise, runGame can be written using an async function. It returns another promise, which resolves when the player finishes the game.

When a monster touches the player, the effect depends on whether the player is jumping on top of them or not. You can approximate this by checking whether the player’s bottom is near the monster’s top. If this is the case, the monster disappears. If not, the game is lost.

Drawing on Canvas

Browsers give us several ways to display graphics. The simplest way is to use styles to position and color regular DOM elements. This can get you quite far, as the game in the previous section showed. By adding partially transparent background images to the nodes, we can make them look exactly the way we want. It is even possible to rotate or skew nodes with the transform style. But we’d be using the DOM for something that it wasn’t originally designed for. Some tasks, such as drawing a line between arbitrary points, are extremely awkward to do with regular HTML elements. There are two alternatives. The first is DOM-based but utilizes Scalable Vector Graphics (SVG), rather than HTML. Think of SVG as a document-markup dialect that focuses on shapes rather than text. You can embed an SVG document directly in an HTML document or include it with an <img> tag.

The second alternative is called a canvas. A canvas is a single DOM element that encapsulates a picture. It provides a programming interface for drawing shapes onto the space taken up by the node. The main difference between a canvas and an SVG picture is that in SVG the original description of the shapes is preserved so that they can be moved or resized at any time. A canvas, on the other hand, converts the shapes to pixels (colored dots on a raster) as soon as they are drawn and does not remember what these pixels represent. The only way to move a shape on a canvas is to clear the canvas (or the part of the canvas around the shape) and redraw it with the shape in a new position.

SVG

We will not go into SVG in detail, but we will briefly explain how it works.We’ll come back to the trade-offs that you must consider when deciding which drawing mechanism is appropriate for a given application. This is an HTML document with a simple SVG picture in it:

<p>Normal HTML here.</p>

<svg xmlns=”http://www.w3.org/2000/svg”>

<circle r=”50″ cx=”50″ cy=”50″ fill=”red”/>

<rect x=”120″ y=”5″ width=”90″ height=”90″

stroke=”blue” fill=”none”/>

</svg>

The xmlns attribute changes an element (and its children) to a different XML namespace. This namespace, identified by a URL, specifies the dialect that we are currently speaking. The <circle> and <rect> tags, which do not exist in HTML, do have a meaning in SVG—they draw shapes using the style and position specified by their attributes.

The document is displayed like this:

These tags create DOM elements, just like HTML tags, that scripts can interact with. For example, this changes the <circle> element to be colored cyan instead:

let circle = document.querySelector(“circle”);

circle.setAttribute(“fill”, “cyan”);

The canvas element

Canvas graphics can be drawn onto a <canvas> element. You can give such an element width and height attributes to determine its size in pixels. A new canvas is empty, meaning it is entirely transparent and thus shows up as empty space in the document. The <canvas> tag is intended to allow different styles of drawing. To get access to an actual drawing interface, we first need to create a context, an object whose methods provide the drawing interface. There are currently two widely supported drawing styles: “2d” for two-dimensional graphics and “webgl” for three-dimensional graphics through the OpenGL interface.

We won’t discuss WebGL—we’ll stick to two dimensions. But if you are interested in three-dimensional graphics, we do encourage you to look into WebGL. It provides a direct interface to graphics hardware and allows you to render even complicated scenes efficiently, using JavaScript. You create a context with the getContext method on the <canvas> DOM element.

<p>Before canvas.</p>

<canvas width=”120″ height=”60″></canvas>

<p>After canvas.</p>

<script>

let canvas = document.querySelector(“canvas”);

let context = canvas.getContext(“2d”);

context.fillStyle = “red”;

context.fillRect(10, 10, 100, 50);

</script>

After creating the context object, the example draws a red rectangle 100 pixels wide and 50 pixels high, with its top-left corner at coordinates (10,10).

Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0,0) at the top-left corner, and the positive y-axis goes down from there. So (10,10) is 10 pixels below and to the right of the top-left corner.

Lines and surfaces

In the canvas interface, a shape can be filled, meaning its area is given a certain color or pattern, or it can be stroked, which means a line is drawn along its edge. The same terminology is used by SVG. The fillRect method fills a rectangle. It takes first the x- and y-coordinates of the rectangle’s top-left corner, then its width, and then its height. A similar method, strokeRect, draws the outline of a rectangle.

Neither method takes any further parameters. The color of the fill, thickness of the stroke, and so on, are not determined by an argument to the method (as you might reasonably expect) but rather by properties of the context object. The fillStyle property controls the way shapes are filled. It can be set to a string that specifies a color, using the color notation used by CSS.The strokeStyle property works similarly but determines the color used for a stroked line. The width of that line is determined by the lineWidth property, which may contain any positive number.

<canvas></canvas>

<script>

let cx = document.querySelector(“canvas”).getContext(“2d”);

cx.strokeStyle = “blue”;

cx.strokeRect(5, 5, 50, 50);

cx.lineWidth = 5;

cx.strokeRect(135, 5, 50, 50);

</script>

This code draws two blue squares, using a thicker line for the second one.

When no width or height attribute is specified, as in the example, a canvas element gets a default width of 300 pixels and height of 150 pixels.

Paths

A path is a sequence of lines. The 2D canvas interface takes a peculiar approach to describing such a path. It is done entirely through side effects. Paths are not values that can be stored and passed around. Instead, if you want to do something with a path, you make a sequence of method calls to describe its shape.

<canvas></canvas>

<script>

let cx = document.querySelector(“canvas”).getContext(“2d”);

cx.beginPath();

for (let y = 10; y < 100; y += 10) {

    cx.moveTo(10, y);

    cx.lineTo(90, y);

}

cx.stroke();

</script>

This example creates a path with a number of horizontal line segments and then strokes it using the stroke method. Each segment created with lineTo starts at the path’s current position. That position is usually the end of the last segment, unless moveTo was called. In that case, the next segment would start at the position passed to moveToThe path described by the previous program looks like this:

When filling a path (using the fill method), each shape is filled separately. A path can contain multiple shapes—each moveTo motion starts a new one. But the path needs to be closed (meaning its start and end are in the same position) before it can be filled. If the path is not already closed, a line is added from its end to its start, and the shape enclosed by the completed path is filled.

<canvas></canvas>

<script>

let cx = document.querySelector(“canvas”).getContext(“2d”);

cx.beginPath();

cx.moveTo(50, 10);

cx.lineTo(10, 70);

cx.lineTo(90, 70);

cx.fill();

</script>

This example draws a filled triangle. Note that only two of the triangle’s sides are explicitly drawn. The third, from the bottom-right corner back to the top, is implied and wouldn’t be there when you stroke the path.

You could also use the closePath method to explicitly close a path by adding an actual line segment back to the path’s start. This segment is drawn when stroking the path.

Curves

A path may also contain curved lines. These are unfortunately a bit more involved to draw. The quadraticCurveTo method draws a curve to a given point. To determine the curvature of the line, the method is given a control point as well as a destination point. Imagine this control point as attracting the line, giving it its curve. The line won’t go through the control point, but its direction at the start and end points will be such that a straight line in that direction would point toward the control point. The following example illustrates this:

<canvas></canvas>

<script>

let cx = document.querySelector(“canvas”).getContext(“2d”);

cx.beginPath();

cx.moveTo(10, 90);

// control=(60,10) goal=(90,90)

cx.quadraticCurveTo(60, 10, 90, 90);

cx.lineTo(60, 10);

cx.closePath();

cx.stroke();

</script>

It produces a path that looks like this:

We draw a quadratic curve from the left to the right, with (60,10) as control point, and then draw two line segments going through that control point and back to the start of the line. The result somewhat resembles a Star Trek insignia. You can see the effect of the control point: the lines leaving the lower corners start off in the direction of the control point and then curve toward their target. The bezierCurveTo method draws a similar kind of curve. Instead of a single control point, this one has two—one for each of the line’s endpoints. Here is a similar sketch to illustrate the behavior of such a curve:

<canvas></canvas>

<script>

let cx = document.querySelector(“canvas”).getContext(“2d”);

cx.beginPath();

cx.moveTo(10, 90);

// control1=(10,10) control2=(90,10) goal=(50,90)

cx.bezierCurveTo(10, 10, 90, 10, 50, 90);

cx.lineTo(90, 10);

cx.lineTo(10, 10);

cx.closePath();

cx.stroke();

</script>

The two control points specify the direction at both ends of the curve. The farther they are away from their corresponding point, the more the curve will “bulge” in that direction.

Such curves can be hard to work with—it’s not always clear how to find the control points that provide the shape you are looking for. Sometimes you can compute them, and sometimes you’ll just have to find a suitable value by trial and error. The arc method is a way to draw a line that curves along the edge of a circle. It takes a pair of coordinates for the arc’s center, a radius, and then a start angle and end angle. Those last two parameters make it possible to draw only part of the circle. The angles are measured in radians, not degrees. This means a full circle has an angle of 2 , or 2 * Math.PI, which is about 6.28. The angle starts counting at the point to the right of the circle’s center and goes clockwise from there. You can use a start of 0 and an end bigger than 2 (say, 7) to draw a full circle.

<canvas></canvas>

<script>

let cx = document.querySelector(“canvas”).getContext(“2d”);

cx.beginPath();

// center=(50,50) radius=40 angle=0 to 7

cx.arc(50, 50, 40, 0, 7);

//center=(150,50) radius=40 angle=0 to Л½

cx.arc(150, 50, 40, 0, 0.5 * Math.PI);

cx.stroke();

</script>

The resulting picture contains a line from the right of the full circle (first call to arc) to the right of the quarter-circle (second call). Like other path-drawing methods, a line drawn with arc is connected to the previous path segment. You can call moveTo or start a new path to avoid this.

Drawing a pie chart

Imagine you’ve just taken a job at EconomiCorp, Inc., and your first assignment is to draw a pie chart of its customer satisfaction survey results. The results binding contains an array of objects that represent the survey responses.

const results = [

   {name: “Satisfied”, count: 1043, color: “lightblue”},

  {name: “Neutral”, count: 563, color: “lightgreen”},

  {name: “Unsatisfied”, count: 510, color: “pink”},

  {name: “No comment”, count: 175, color: “silver”}

];

To draw a pie chart, we draw a number of pie slices, each made up of an arc and a pair of lines to the center of that arc. We can compute the angle taken up by each arc by dividing a full circle (2 ) by the total number of responses and then multiplying that number (the angle per response) by the number of people who picked a given choice.

<canvas width=”200″ height=”200″></canvas>

<script>

let cx = document.querySelector(“canvas”).getContext(“2d”);

let total = results

.reduce((sum, {count}) => sum + count, 0);

// Start at the top

let currentAngle = -0.5 * Math.PI;

for (let result of results) {

     let sliceAngle = (result.count / total) * 2 * Math.PI;

     cx.beginPath();

//center=100,100, radius=100

//from current angle, clockwise by slice’s angle

cx.arc(100, 100, 100,

              currentAngle, currentAngle + sliceAngle);

currentAngle += sliceAngle;

cx.lineTo(100, 100);

cx.fillStyle = result.color;

cx.fill();

}

</script>

This draws the following chart:

But a chart that doesn’t tell us what the slices mean isn’t very helpful. We need a way to draw text to the canvas.

Text

A 2D canvas drawing context provides the methods fillText and strokeText. The latter can be useful for outlining letters, but usually fillText is what you need. It will fill the outline of the given text with the current fillStyle.

<canvas></canvas>

<script>

  let cx = document.querySelector(“canvas”).getContext(“2d”);

  cx.font = “28px Georgia”;

  cx.fillStyle = “fuchsia”;

  cx.fillText(“I can draw text, too!”, 10, 50);

</script>

You can specify the size, style, and font of the text with the font property. This example just gives a font size and family name. It is also possible to add italic or bold to the start of the string to select a style. The last two arguments to fillText and strokeText provide the position at which the font is drawn. By default, they indicate the position of the start of the text’s alphabetic baseline, which is the line that letters “stand” on, not counting hanging parts in letters such as j or p. You can change the horizontal position by setting the textAlign property to “end” or “center” and the vertical position by setting textBaseline to “top”, “middle”, or “bottom”.

Images

In computer graphics, a distinction is often made between vector graphics and bitmap graphics. The first is what we have been doing so far in this — specifying a picture by giving a logical description of shapes. Bitmap graphics, on the other hand, don’t specify actual shapes but rather work with pixel data (rasters of colored dots). The drawImage method allows us to draw pixel data onto a canvas. This pixel data can originate from an <img> element or from another canvas. The following example creates a detached <img> element and loads an image file into it. But it cannot immediately start drawing from this picture because the browser may not have loaded it yet. To deal with this, we register a “load” event handler and do the drawing after the image has loaded.

<canvas></canvas>

<script>

   let cx = document.querySelector(“canvas”).getContext(“2d”);

  let img = document.createElement(“img”);

  img.src = “img/hat.png”;

  img.addEventListener(“load”, () => {

     for (let x = 10; x < 200; x += 30) {

        cx.drawImage(img, x, 10);

    }

});

</script>

By default, drawImage will draw the image at its original size. You can also give it two additional arguments to set a different width and height. When drawImage is given nine arguments, it can be used to draw only a fragment of an image. The second through fifth arguments indicate the rectangle (x, y, width, and height) in the source image that should be copied, and the sixth to ninth arguments give the rectangle (on the canvas) into which it should be copied. This can be used to pack multiple sprites (image elements) into a single image file and then draw only the part you need. For example, we have this picture containing a game character in multiple poses:

By alternating which pose we draw, we can show an animation that looks like a walking character. To animate a picture on a canvas, the clearRect method is useful. It resembles fillRect, but instead of coloring the rectangle, it makes it transparent, removing the previously drawn pixels. We know that each sprite, each subpicture, is 24 pixels wide and 30 pixels high. The following code loads the image and then sets up an interval (repeated timer) to draw the next frame:

<canvas></canvas>

<script>

   let cx = document.querySelector(“canvas”).getContext(“2d”);

  let img = document.createElement(“img”);

  img.src = “img/player.png”;

  let spriteW = 24, spriteH = 30;

  img.addEventListener(“load”, () => {

    let cycle = 0;

   setInterval(() => {

      cx.clearRect(0, 0, spriteW, spriteH);

     cx.drawImage(img,

           // source rectangle

           cycle * spriteW, 0, spriteW, spriteH,

         // destination rectangle

              0, 0, spriteW, spriteH);

cycle = (cycle + 1) % 8;

    }, 120);

});

</script>

The cycle binding tracks our position in the animation. For each frame, it is incremented and then clipped back to the 0 to 7 range by using the remainder operator. This binding is then used to compute the x-coordinate that the sprite for the current pose has in the picture.

Transformation

But what if we want our character to walk to the left instead of to the right? We could draw another set of sprites, of course. But we can also instruct the canvas to draw the picture the other way round. Calling the scale method will cause anything drawn after it to be scaled. This method takes two parameters, one to set a horizontal scale and one to set a vertical scale.

<canvas></canvas>

<script>

   let cx = document.querySelector(“canvas”).getContext(“2d”);

   cx.scale(3, .5);

   cx.beginPath();

   cx.arc(50, 50, 40, 0, 7);

   cx.lineWidth = 3;

   cx.stroke();

</script>

Because of the call to scale, the circle is drawn three times as wide and half as high.

Scaling will cause everything about the drawn image, including the line width, to be stretched out or squeezed together as specified. Scaling by a negative amount will flip the picture around. The flipping happens around point (0,0), which means it will also flip the direction of the coordinate system. When a horizontal scaling of -1 is applied, a shape drawn at x position 100 will end up at what used to be position -100.

So to turn a picture around, we can’t simply add cx.scale(-1, 1) before the call to drawImage because that would move our picture outside of the canvas, where it won’t be visible. You could adjust the coordinates given to drawImage to compensate for this by drawing the image at x position -50 instead of 0. Another solution, which doesn’t require the code that does the drawing to know about the scale change, is to adjust the axis around which the scaling happens. There are several other methods besides scale that influence the coordinate system for a canvas. You can rotate subsequently drawn shapes with the rotate method and move them with the translate method. The interesting—and confusing—thing is that these transformations stack, meaning that each one happens relative to the previous transformations. So if we translate by 10 horizontal pixels twice, everything will be drawn 20 pixels to the right. If we first move the center of the coordinate system to (50,50) and then rotate by 20 degrees (about 0.1 radians), that rotation will happen around point (50,50).

But if we first rotate by 20 degrees and then translate by (50,50), the translation will happen in the rotated coordinate system and thus produce a different orientation. The order in which transformations are applied matters. To flip a picture around the vertical line at a given x position, we can do the following:

function flipHorizontally(context, around) {

  context.translate(around, 0);

  context.scale(-1, 1);

  context.translate(-around, 0);

}

We move the y-axis to where we want our mirror to be, apply the mirroring, and finally move the y-axis back to its proper place in the mirrored universe. The following picture explains why this works:

This shows the coordinate systems before and after mirroring across the central line. The triangles are numbered to illustrate each step. If we draw a triangle at a positive x position, it would, by default, be in the place where triangle 1 is. A call to flipHorizontally first does a translation to the right, which gets us to triangle 2. It then scales, flipping the triangle over to position 3. This is not where it should be, if it were mirrored in the given line. The second translate call fixes this—it “cancels” the initial translation and makes triangle 4 appear exactly where it should. We can now draw a mirrored character at position (100,0) by flipping the world around the character’s vertical center.

<canvas></canvas>

<script>

  let cx = document.querySelector(“canvas”).getContext(“2d”);

  let img = document.createElement(“img”);

  img.src = “img/player.png”;

  let spriteW = 24, spriteH = 30;

  img.addEventListener(“load”, () => {

      flipHorizontally(cx, 100 + spriteW / 2);

     cx.drawImage(img, 0, 0, spriteW, spriteH,

                              100, 0, spriteW, spriteH);

  });

</script>

Storing and clearing transformations

Transformations stick around. Everything else we draw after drawing that mirrored character would also be mirrored. That might be inconvenient.It is possible to save the current transformation, do some drawing and transforming, and then restore the old transformation. This is usually the proper thing to do for a function that needs to temporarily transform the coordinate system. First, we save whatever transformation the code that called the function was using. Then the function does its thing, adding more transformations on top of the current transformation. Finally, we revert to the transformation we started with.

The save and restore methods on the 2D canvas context do this transformation management. They conceptually keep a stack of transformation states. When you call save, the current state is pushed onto the stack, and when you call restore, the state on top of the stack is taken off and used as the context’s current transformation. You can also call resetTransform to fully reset the transformation. The branch function in the following example illustrates what you can do with a function that changes the transformation and then calls a function (in this case itself), which continues drawing with the given transformation. This function draws a treelike shape by drawing a line, moving the center of the coordinate system to the end of the line, and calling itself twice—first rotated to the left and then rotated to the right. Every call reduces the length of the branch drawn, and the recursion stops when the length drops below 8.

<canvas width=”600″ height=”300″></canvas>

<script>

let cx = document.querySelector(“canvas”).getContext(“2d”);

function branch(length, angle, scale) {

  cx.fillRect(0, 0, 1, length);

  if (length < 8) return;

  cx.save();

  cx.translate(0, length);

  cx.rotate(-angle);

  branch(length * scale, angle, scale);

  cx.rotate(2 * angle);

  branch(length * scale, angle, scale);

  cx.restore();

}

  cx.translate(300, 0);

  branch(60, 0.5, 0.8);

</script>

The result is a simple fractal.

If the calls to save and restore were not there, the second recursive call to branch would end up with the position and rotation created by the first call. It wouldn’t be connected to the current branch but rather to the innermost, rightmost branch drawn by the first call. The resulting shape might also be interesting, but it is definitely not a tree.

Back to the game

We now know enough about canvas drawing to start working on a canvas-based display system for the game from the previous section . The new display will no longer be showing just colored boxes. Instead, we’ll use drawImage to draw pictures that represent the game’s elements. We define another display object type called CanvasDisplay, supporting the same interface as DOMDisplay, namely, the methods syncState and clearThis object keeps a little more information than DOMDisplay. Rather than using the scroll position of its DOM element, it tracks its own viewport, which tells us what part of the level we are currently looking at. Finally, it keeps a flipPlayer property so that even when the player is standing still, it keeps facing the direction it last moved in.

class CanvasDisplay {

constructor(parent, level) {

   this.canvas = document.createElement(“canvas”);

   this.canvas.width = Math.min(600, level.width * scale);

   this.canvas.height = Math.min(450, level.height * scale);

   parent.appendChild(this.canvas);

   this.cx = this.canvas.getContext(“2d”);

   this.flipPlayer = false;

this.viewport = {

    left: 0,

   top: 0,

   width: this.canvas.width / scale,

   height: this.canvas.height / scale

  };

}

clear() {

  this.canvas.remove();

  }

}

The syncState method first computes a new viewport and then draws the game scene at the appropriate position.

CanvasDisplay.prototype.syncState = function(state) {

   this.updateViewport(state);

  this.clearDisplay(state.status);

  this.drawBackground(state.level);

  this.drawActors(state.actors);

};

Contrary to DOMDisplay, this display style does have to redraw the background on every update. Because shapes on a canvas are just pixels, after we draw them there is no good way to move them (or remove them). The only way to update the canvas display is to clear it and redraw the scene. We may also have scrolled, which requires the background to be in a different position. The updateViewport method is similar to DOMDisplay’s scrollPlayerIntoView method. It checks whether the player is too close to the edge of the screen and moves the viewport when this is the case.

CanvasDisplay.prototype.updateViewport = function(state) {

  let view = this.viewport, margin = view.width / 3;

  let player = state.player;

  let center = player.pos.plus(player.size.times(0.5));

if (center.x < view.left + margin) {

  view.left = Math.max(center.x – margin, 0);

} else if (center.x > view.left + view.width – margin) {

   view.left = Math.min(center.x + margin – view.width,

                                           state.level.width – view.width);

}

if (center.y < view.top + margin) {

  view.top = Math.max(center.y – margin, 0);

} else if (center.y > view.top + view.height – margin) {

  view.top = Math.min(center.y + margin – view.height,

                                         state.level.height – view.height);

      }

};

The calls to Math.max and Math.min ensure that the viewport does not end up showing space outside of the level. Math.max(x, 0) makes sure the resulting number is not less than zero. Math.min similarly guarantees that a value stays below a given bound.When clearing the display, we’ll use a slightly different color depending on whether the game is won (brighter) or lost (darker).

CanvasDisplay.prototype.clearDisplay = function(status) {

    if (status == “won”) {

          this.cx.fillStyle = “rgb(68, 191, 255)”;

} else if (status == “lost”) {

     this.cx.fillStyle = “rgb(44, 136, 214)”;

} else {

   this.cx.fillStyle = “rgb(52, 166, 251)”;

}

   this.cx.fillRect(0, 0,

                             this.canvas.width, this.canvas.height);

};

To draw the background, we run through the tiles that are visible in the current viewport, using the same trick used in the touches method.

let otherSprites = document.createElement(“img”);

otherSprites.src = “img/sprites.png”;

CanvasDisplay.prototype.drawBackground = function(level) {

    let {left, top, width, height} = this.viewport;

    let xStart = Math.floor(left);

   let xEnd = Math.ceil(left + width);

   let yStart = Math.floor(top);

  let yEnd = Math.ceil(top + height);

for (let y = yStart; y < yEnd; y++) {

     for (let x = xStart; x < xEnd; x++) {

         let tile = level.rows[y][x];

         if (tile == “empty”) continue;

         let screenX = (x – left) * scale;

         let screenY = (y – top) * scale;

         let tileX = tile == “lava” ? scale : 0;

         this.cx.drawImage(otherSprites,

                                             tileX,                    0, scale, scale,

                                             screenX, screenY, scale, scale);

        }

     }

};

Tiles that are not empty are drawn with drawImage. The otherSprites image contains the pictures used for elements other than the player. It contains, from left to right, the wall tile, the lava tile, and the sprite for a coin.

Background tiles are 20 by 20 pixels since we will use the same scale that we used in DOMDisplay. Thus, the offset for lava tiles is 20 (the value of the scale binding), and the offset for walls is 0.

We don’t bother waiting for the sprite image to load. Calling drawImage with an image that hasn’t been loaded yet will simply do nothing. Thus, we might fail to draw the game properly for the first few frames, while the image is still loading, but that is not a serious problem. Since we keep updating the screen, the correct scene will appear as soon as the loading finishes. The walking character shown earlier will be used to represent the player. The code that draws it needs to pick the right sprite and direction based on the player’s current motion. The first eight sprites contain a walking animation. When the player is moving along a floor, we cycle through them based on the current time. We want to switch frames every 60 milliseconds, so the time is divided by 60 first. When the player is standing still, we draw the ninth sprite. During jumps, which are recognized by the fact that the vertical speed is not zero, we use the tenth, rightmost sprite.

Because the sprites are slightly wider than the player object—24 instead of 16 pixels to allow some space for feet and arms—the method has to adjust the x-coordinate and width by a given amount (playerXOverlap).

let playerSprites = document.createElement(“img”);

playerSprites.src = “img/player.png”;

const playerXOverlap = 4;

CanvasDisplay.prototype.drawPlayer = function(player, x, y, width, height){

  width += playerXOverlap * 2;

  x -= playerXOverlap;

if (player.speed.x != 0) {

     this.flipPlayer = player.speed.x < 0;

}

let tile = 8;

if (player.speed.y != 0) {

    tile = 9;

} else if (player.speed.x != 0) {

      tile = Math.floor(Date.now() / 60) % 8;

}

this.cx.save();

if (this.flipPlayer) {

     flipHorizontally(this.cx, x + width / 2);

}

  let tileX = tile * width;

  this.cx.drawImage(playerSprites, tileX, 0, width, height,

                                                                  x,      y, width, height);

this.cx.restore();

};

The drawPlayer method is called by drawActors, which is responsible for drawing all the actors in the game.

CanvasDisplay.prototype.drawActors = function(actors) {

for (let actor of actors) {

let width = actor.size.x * scale;

let height = actor.size.y * scale;

let x = (actor.pos.x – this.viewport.left) * scale;

let y = (actor.pos.y – this.viewport.top) * scale;

if (actor.type == “player”) {

    this.drawPlayer(actor, x, y, width, height);

} else {

    let tileX = (actor.type == “coin” ? 2 : 1) * scale;

    this.cx.drawImage(otherSprites,

                                         tileX, 0, width, height,

                                         x,        y, width, height);

          }

     }

};

When drawing something that is not the player, we look at its type to find the offset of the correct sprite. The lava tile is found at offset 20, and the coin sprite is found at 40 (two times scale).We have to subtract the viewport’s position when computing the actor’s position since (0,0) on our canvas corresponds to the top left of the viewport, not the top left of the level. We could also have used translate for this. Either way works.That concludes the new display system. The resulting game looks something like this:

Choosing a graphics interface

So when you need to generate graphics in the browser, you can choose between plain HTML, SVG, and canvas. There is no single best approach that works in all situations. Each option has strengths and weaknesses. Plain HTML has the advantage of being simple. It also integrates well with text. Both SVG and canvas allow you to draw text, but they won’t help you position that text or wrap it when it takes up more than one line. In an HTML-based picture, it is much easier to include blocks of text. SVG can be used to produce crisp graphics that look good at any zoom level. Unlike HTML, it is designed for drawing and is thus more suitable for that purpose. Both SVG and HTML build up a data structure (the DOM) that represents your picture. This makes it possible to modify elements after they are drawn. If you need to repeatedly change a small part of a big picture in response to what the user is doing or as part of an animation, doing it in a canvas can be needlessly expensive. The DOM also allows us to register mouse event handlers on every element in the picture (even on shapes drawn with SVG). You can’t do that with canvas.

But canvas’s pixel-oriented approach can be an advantage when drawing a huge number of tiny elements. The fact that it does not build up a data structure but only repeatedly draws onto the same pixel surface gives canvas a lower cost per shape. There are also effects, such as rendering a scene one pixel at a time (for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting it), that can be realistically handled only by a pixel-based approach. In some cases, you may want to combine several of these techniques.  For example, you might draw a graph with SVG or canvas but show textual information by positioning an HTML element on top of the picture.

For nondemanding applications, it really doesn’t matter much which interface you choose. The display we built for our game could have been implemented using any of these three graphics technologies since it does not need to draw text, handle mouse interaction, or work with an extraordinarily large number of elements.

HTTP and Forms

The Hypertext Transfer Protocol, is the mechanism through which data is requested and provided on the World Wide Web. This section describes the protocol in more detail and explains the way browser JavaScript has access to it.

The protocol

If you type webaddress/filename.html into your browser’s address bar, the browser first looks up the address of the server associated with that web site and tries to open a TCP connection to it on port 80, the default port for HTTP traffic. If the server exists and accepts the connection, the browser might send something like this:

GET /filename.html HTTP/1.1

Host: Web Address

User-Agent: Your browser’s name

Then the server responds, through that same connection.

HTTP/1.1 200 OK

Content-Length: 65585

Content-Type: text/html

Last-Modified: Mon, 08 Jan 2018 10:29:45 GMT

<!doctype html>

… the rest of the document

The browser takes the part of the response after the blank line, its body (not to be confused with the HTML <body> tag), and displays it as an HTML document.The information sent by the client is called the request. It starts with this

line:

GET /18_http.html HTTP/1.1

The first word is the method of the request. GET means that we want to get the specified resource. Other common methods are DELETE to delete a resource, PUT to create or replace it, and POST to send information to it. Note that the server is not obliged to carry out every request it gets. If you walk up to a random website and tell it to DELETE its main page, it’ll probably refuse. The part after the method name is the path of the resource the request applies to. In the simplest case, a resource is simply a file on the server, but the protocol doesn’t require it to be. A resource may be anything that can be transferred as if it is a file. Many servers generate the responses they produce on the fly. For example, if you open https://github.com/marijnh, the server looks in its database for a user named “marijnh”, and if it finds one, it will generate a profile page for that user. After the resource path, the first line of the request mentions HTTP/1.1 to indicate the version of the HTTP protocol it is using.

In practice, many sites use HTTP version 2, which supports the same concepts as version 1.1 but is a lot more complicated so that it can be faster. Browsers will automatically switch to the appropriate protocol version when talking to a given server, and the outcome of a request is the same regardless of which version is used. Because version 1.1 is more straightforward and easier to play around with, we’ll focus on that. The server’s response will start with a version as well, followed by the status of the response, first as a three-digit status code and then as a human-readable string.

HTTP/1.1 200 OK

Status codes starting with a 2 indicate that the request succeeded. Codes starting with 4 mean there was something wrong with the request. 404 is probably the most famous HTTP status code—it means that the resource could not be found. Codes that start with 5 mean an error happened on the server and the request is not to blame. The first line of a request or response may be followed by any number of headers. These are lines in the form name: value that specify extra information about the request or response. These headers were part of the example response:

Content-Length: 65585

Content-Type: text/html

Last-Modified: Thu, 04 Jan 2018 14:05:30 GMT

This tells us the size and type of the response document. In this case, it is an HTML document of 65,585 bytes. It also tells us when that document was last modified.

For most headers, the client and server are free to decide whether to include them in a request or response. But a few are required. For example, the Host header, which specifies the hostname, should be included in a request because a server might be serving multiple hostnames on a single IP address, and without that header, the server won’t know which hostname the client is trying to talk to. After the headers, both requests and responses may include a blank line followed by a body, which contains the data being sent. GET and DELETE requests don’t send along any data, but PUT and POST requests do. Similarly, some response types, such as error responses, do not require a body.

Browsers and HTTP

As we saw in the example, a browser will make a request when we enter a URL in its address bar. When the resulting HTML page references other files, such as images and JavaScript files, those are also retrieved. A moderately complicated website can easily include anywhere from 10 to 200 resources. To be able to fetch those quickly, browsers will make several GET requests simultaneously, rather than waiting for the responses one at a time. HTML pages may include forms, which allow the user to fill out information and send it to the server. This is an example of a form:

<form method=”GET” action=”example/message.html”>

<p>Name: <input type=”text” name=”name”></p>

<p>Message:<br><textarea name=”message”></textarea></p>

<p><button type=”submit”>Send</button></p>

</form>

This code describes a form with two fields: a small one asking for a name and a larger one to write a message in. When you click the Send button, the form is submitted, meaning that the content of its field is packed into an HTTP request and the browser navigates to the result of that request. When the <form> element’s method attribute is GET (or is omitted), the information in the form is added to the end of the action URL as a query string. The browser might make a request to this URL:

GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1

The question mark indicates the end of the path part of the URL and the start of the query. It is followed by pairs of names and values, corresponding to the name attribute on the form field elements and the content of those elements, respectively. An ampersand character (&) is used to separate the pairs. The actual message encoded in the URL is “Yes?”, but the question mark is replaced by a strange code. Some characters in query strings must be escaped. The question mark, represented as %3F, is one of those. There seems to be an unwritten rule that every format needs its own way of escaping characters. This one, called URL encoding, uses a percent sign followed by two hexadecimal (base 16) digits that encode the character code. In this case, 3F, which is 63 in decimal notation, is the code of a question mark character. JavaScript provides the encodeURIComponent and decodeURIComponent functions to encode and decode this format.

console.log(encodeURIComponent(“Yes?”));

// → Yes%3F

console.log(decodeURIComponent(“Yes%3F”));

//→ Yes?

If we change the method attribute of the HTML form in the example we saw earlier to POST, the HTTP request made to submit the form will use the POST method and put the query string in the body of the request, rather than adding it to the URL.

POST /example/message.html HTTP/1.1

Content-length: 24

Content-type: application/x-www-form-urlencoded

name=Jean&message=Yes%3F

GET requests should be used for requests that do not have side effects but simply ask for information. Requests that change something on the server, for example creating a new account or posting a message, should be expressed with other methods, such as POST. Clientside software such as a browser knows that it shouldn’t blindly make POST requests but will often implicitly make GET requests—for example to prefetch a resource it believes the user will soon need.

Fetch

The interface through which browser JavaScript can make HTTP requests is called fetch. Since it is relatively new, it conveniently uses promises (which is rare for browser interfaces).

fetch(“example/data.txt”).then(response => {

console.log(response.status);

// → 200

console.log(response.headers.get(“Content-Type”));

// → text/plain

});

Calling fetch returns a promise that resolves to a Response object holding information about the server’s response, such as its status code and its headers. The headers are wrapped in a Map-like object that treats its keys (the header names) as case insensitive because header names are not supposed to be case sensitive. This means headers.get(“Content-Type”) and headers.get (“content-TYPE”) will return the same value. Note that the promise returned by fetch resolves successfully even if the server responded with an error code. It might also be rejected if there is a network error or if the server that the request is addressed to can’t be found.

The first argument to fetch is the URL that should be requested. When that URL doesn’t start with a protocol name (such as http:), it is treated as relative, which means it is interpreted relative to the current document. When it starts with a slash (/), it replaces the current path, which is the part after the server name. When it does not, the part of the current path up to and including its last slash character is put in front of the relative URL. To get at the actual content of a response, you can use its text method. Because the initial promise is resolved as soon as the response’s headers have been received and because reading the response body might take a while longer, this again returns a promise.

fetch(“example/data.txt”)

.then(resp => resp.text())

.then(text => console.log(text));

// → This is the content of data.txt

A similar method, called json, returns a promise that resolves to the value you get when parsing the body as JSON or rejects if it’s not valid JSON. By default, fetch uses the GET method to make its request and does not include a request body. You can configure it differently by passing an object with extra options as a second argument. For example, this request tries to delete example/data.txt:

fetch(“example/data.txt”, {method: “DELETE”}).then(resp => {

console.log(resp.status);

// → 405

});

The 405 status code means “method not allowed”, an HTTP server’s way of saying “I can’t do that”. To add a request body, you can include a body option. To set headers, there’s the headers option. For example, this request includes a Range header, which instructs the server to return only part of a response.

fetch(“example/data.txt”, {headers: {Range: “bytes=8-19”}})

.then(resp => resp.text())

.then(console.log);

// → the content

The browser will automatically add some request headers, such as “Host” and those needed for the server to figure out the size of the body. But adding your own headers is often useful to include things such as authentication information or to tell the server which file format you’d like to receive.

HTTP sandboxing

Making HTTP requests in web page scripts once again raises concerns about security. The person who controls the script might not have the same interests as the person on whose computer it is running. More specifically, if we visit the-mafia.org, we do not want its scripts to be able to make a request to mybank.com, using identifying information from my browser, with instructions to transfer all my money to some random account. For this reason, browsers protect us by disallowing scripts to make HTTP requests to other domains (names such as themafia.org and mybank.com). This can be an annoying problem when building systems that want to access several domains for legitimate reasons. Fortunately, servers can include a header like this in their response to explicitly indicate to the browser that it is okay for the request to come from another domain:

Access-Control-Allow-Origin: *

Appreciating HTTP

When building a system that requires communication between a JavaScript program running in the browser (client-side) and a program on a server (server-side), there are several different ways to model this communication. A commonly used model is that of remote procedure calls. In this model, communication follows the patterns of normal function calls, except that the function is actually running on another machine. Calling it involves making a request to the server that includes the function’s name and arguments. The response to that request contains the returned value. When thinking in terms of remote procedure calls, HTTP is just a vehicle for communication, and you will most likely write an abstraction layer that hides it entirely.

Another approach is to build your communication around the concept of resources and HTTP methods. Instead of a remote procedure called addUser, you use a PUT request to /users/larry. Instead of encoding that user’s properties in function arguments, you define a JSON document format (or use an existing format) that represents a user. The body of the PUT request to create a new resource is then such a document. A resource is fetched by making a GET request to the resource’s URL (for example, /user/larry), which again returns the document representing the resource. This second approach makes it easier to use some of the features that HTTP provides, such as support for caching resources (keeping a copy on the client for fast access). The concepts used in HTTP, which are well designed, can provide a helpful set of principles to design your server interface around.

Security and HTTPS

Data traveling over the Internet tends to follow a long, dangerous road. To get to its destination, it must hop through anything from coffee shop Wi-Fi hotspots to networks controlled by various companies and states. At any point along its route it may be inspected or even modified. If it is important that something remain secret, such as the password to your email account, or that it arrive at its destination unmodified, such as the account number you transfer money to via your bank’s website, plain HTTP is not good enough.

The secure HTTP protocol, used for URLs starting with https://, wraps HTTP traffic in a way that makes it harder to read and tamper with. Before exchanging data, the client verifies that the server is who it claims to be by asking it to prove that it has a cryptographic certificate issued by a certificate authority that the browser recognizes. Next, all data going over the connection is encrypted in a way that should prevent eavesdropping and tampering. Thus, when it works right, HTTPS prevents other people from impersonating the website you are trying to talk to and from snooping on your communication. It is not perfect, and there have been various incidents where HTTPS failed because of forged or stolen certificates and broken software, but it is a lot safer than plain HTTP.

Form fields

Forms were originally designed for the pre-JavaScript Web to allow web sites to send user-submitted information in an HTTP request. This design assumes that interaction with the server always happens by navigating to a new page. But their elements are part of the DOM like the rest of the page, and the DOM elements that represent form fields support a number of properties and events that are not present on other elements. These make it possible to inspect and control such input fields with JavaScript programs and do things such as adding new functionality to a form or using forms and fields as building blocks in a JavaScript application.

A web form consists of any number of input fields grouped in a <form> tag. HTML allows several different styles of fields, ranging from simple on/off checkboxes to drop-down menus and fields for text input.We won’t try to comprehensively discuss all field types, but we’ll start with a rough overview. A lot of field types use the <input> tag. This tag’s type attribute is used to select the field’s style. These are some commonly used <input> types:

text                     A single-line text field

password          Same as text but hides the text that is typed

checkbox          An on/off switch

radio                  (Part of) a multiple-choice field

file                     Allows the user to choose a file from their computer

Form fields do not necessarily have to appear in a <form> tag. You can put them anywhere in a page. Such formless fields cannot be submitted (only a form as a whole can), but when responding to input with JavaScript, we often don’t want to submit our fields normally anyway.

<p><input type=”text” value=”abc”> (text)</p>

<p><input type=”password” value=”abc”> (password)</p>

<p><input type=”checkbox” checked> (checkbox)</p>

<p><input type=”radio” value=”A” name=”choice”>

<input type=”radio” value=”B” name=”choice” checked>

<input type=”radio” value=”C” name=”choice”> (radio)</p>

<p><input type=”file”> (file)</p>

The fields created with this HTML code look like this:

The JavaScript interface for such elements differs with the type of the element. Multiline text fields have their own tag, <textarea>, mostly because using an attribute to specify a multiline starting value would be awkward. The < textarea> tag requires a matching </textarea> closing tag and uses the text between those two, instead of the value attribute, as starting text.

<textarea>

one

two

three

</textarea>

Finally, the <select> tag is used to create a field that allows the user to select from a number of predefined options.

<select>

<option>Pancakes</option>

<option>Pudding</option>

<option>Ice cream</option>

</select>

Such a field looks like this:

Whenever the value of a form field changes, it will fire a “change” event.

Focus

Unlike most elements in HTML documents, form fields can get keyboard focus. When clicked or activated in some other way, they become the currently active element and the recipient of keyboard input.Thus, you can type into a text field only when it is focused. Other fields respond differently to keyboard events. For example, a <select> menu tries to move to the option that contains the text the user typed and responds to the arrow keys by moving its selection up and down.

We can control focus from JavaScript with the focus and blur methods. The first moves focus to the DOM element it is called on, and the second removes focus. The value in document.activeElement corresponds to the currently focused element.

<input type=”text”>

<script>

document.querySelector(“input”).focus();

console.log(document.activeElement.tagName);

// → INPUT

document.querySelector(“input”).blur();

console.log(document.activeElement.tagName);

// → BODY

</script>

For some pages, the user is expected to want to interact with a form field immediately. JavaScript can be used to focus this field when the document is loaded, but HTML also provides the autofocus attribute, which produces the same effect while letting the browser know what we are trying to achieve. Thisgives the browser the option to disable the behavior when it is not appropriate, such as when the user has put the focus on something else. Browsers traditionally also allow the user to move the focus through the document by pressing the tab key. We can influence the order in which elements receive focus with the tabindex attribute. The following example document will let the focus jump from the text input to the OK button, rather than going through the help link first:

<input type=”text” tabindex=1> <a href=”.”>(help)</a>

<button onclick=”console.log(‘ok’)” tabindex=2>OK</button>

By default, most types of HTML elements cannot be focused. But you can add a tabindex attribute to any element that will make it focusable. A tabindex of -1 makes tabbing skip over an element, even if it is normally focusable.

Disabled fields

All form fields can be disabled through their disabled attribute. It is an attribute that can be specified without value—the fact that it is present at all disables the element.

<button>I’m all right</button>

<button disabled>I’m out</button>

Disabled fields cannot be focused or changed, and browsers make them look gray and faded.

When a program is in the process of handling an action caused by some button or other control that might require communication with the server and thus take a while, it can be a good idea to disable the control until the action finishes. That way, when the user gets impatient and clicks it again, they don’t accidentally repeat their action.

The form as a whole

When a field is contained in a <form> element, its DOM element will have a form property linking back to the form’s DOM element. The <form> element, in turn, has a property called elements that contains an array-like collection of the fields inside it. The name attribute of a form field determines the way its value will be identified when the form is submitted. It can also be used as a property name when accessing the form’s elements property, which acts both as an array-like object (accessible by number) and a map (accessible by name).

<form action=”example/submit.html”>

Name: <input type=”text” name=”name”><br>

Password: <input type=”password” name=”password”><br>

<button type=”submit”>Log in</button>

</form>

<script>

let form = document.querySelector(“form”);

console.log(form.elements[1].type);

// → password

console.log(form.elements.password.type);

// → password

console.log(form.elements.name.form == form);

// → true

</script>

A button with a type attribute of submit will, when pressed, cause the form to be submitted. Pressing enter when a form field is focused has the same effect. Submitting a form normally means that the browser navigates to the page indicated by the form’s action attribute, using either a GET or a POST request. But before that happens, a “submit” event is fired. You can handle this event with JavaScript and prevent this default behavior by calling preventDefault on the event object.

<form action=”example/submit.html”>

Value: <input type=”text” name=”value”>

<button type=”submit”>Save</button>

</form>

<script>

let form = document.querySelector(“form”);

form.addEventListener(“submit”, event => {

  console.log(“Saving value”, form.elements.value.value);

  event.preventDefault();

  });

</script>

Intercepting “submit” events in JavaScript has various uses. We can write code to verify that the values the user entered make sense and immediately show an error message instead of submitting the form. Or we can disable the regular way of submitting the form entirely, as in the example, and have our program handle the input, possibly using fetch to send it to a server without reloading the page.

Text fields

Fields created by <input> tags with a type of text or password, as well as < textarea> tags, share a common interface. Their DOM elements have a value property that holds their current content as a string value. Setting this property to another string changes the field’s content.

The selectionStart and selectionEnd properties of text fields give us information about the cursor and selection in the text. When nothing is selected, these two properties hold the same number, indicating the position of the cursor. For example, 0 indicates the start of the text, and 10 indicates the cursor is after the 10th character. When part of the field is selected, the two properties will differ, giving us the start and end of the selected text. Like value, these properties may also be written to. Imagine you are writing an article about Khasekhemwy but have some trouble spelling his name. The following code wires up a <textarea> tag with an event handler that, when you press F2, inserts the string “Khasekhemwy” for you.

<textarea></textarea>

<script>

  let textarea = document.querySelector(“textarea”);

  textarea.addEventListener(“keydown”, event => {

// The key code for F2 happens to be 113

if (event.keyCode == 113) {

     replaceSelection(textarea, “Khasekhemwy”);

     event.preventDefault();

     }

});

function replaceSelection(field, word) {

    let from = field.selectionStart, to = field.selectionEnd;

    field.value = field.value.slice(0, from) + word +

                           field.value.slice(to);

//Put the cursor after the word

field.selectionStart = from + word.length;

field.selectionEnd = from + word.length;

}

</script>

The replaceSelection function replaces the currently selected part of a text field’s content with the given word and then moves the cursor after that word so that the user can continue typing. The “change” event for a text field does not fire every time something is typed. Rather, it fires when the field loses focus after its content was changed. To respond immediately to changes in a text field, you should register a han-dler for the “input” event instead, which fires for every time the user types a character, deletes text, or otherwise manipulates the field’s content. The following example shows a text field and a counter displaying the current length of the text in the field:

<input type=”text”> length: <span id=”length”>0</span>

<script>

let text = document.querySelector(“input”);

let output = document.querySelector(“#length”);

text.addEventListener(“input”, () => {

   output.textContent = text.value.length; });

</script>

Checkboxes and radio buttons

A checkbox field is a binary toggle. Its value can be extracted or changed through its checked property, which holds a Boolean value.

<label>

  <input type=”checkbox” id=”purple”> Make this page purple </label>

<script>

let checkbox = document.querySelector(“#purple”);

 checkbox.addEventListener(“change”, () => {

      document.body.style.background = checkbox.checked ? “mediumpurple” : “”;

  });

</script>

The <label> tag associates a piece of document with an input field. Clicking anywhere on the label will activate the field, which focuses it and toggles its value when it is a checkbox or radio button. A radio button is similar to a checkbox, but it’s implicitly linked to other radio buttons with the same name attribute so that only one of them can be active at any time.

Color:

<label>

   <input type=”radio” name=”color” value=”orange”> Orange

</label>

<label>

   <input type=”radio” name=”color” value=”lightgreen”> Green

</label>

<label>

   <input type=”radio” name=”color” value=”lightblue”> Blue </label>

<script>

  let buttons = document.querySelectorAll(“[name=color]”);

  for (let button of Array.from(buttons)) {

       button.addEventListener(“change”, () => {

          document.body.style.background = button.value;

    });

}

</script>

The square brackets in the CSS query given to querySelectorAll are used to match attributes. It selects elements whose name attribute is “color”.

Select fields

Select fields are conceptually similar to radio buttons—they also allow the user to choose from a set of options. But where a radio button puts the layout of the options under our control, the appearance of a <select> tag is determined by the browser. Select fields also have a variant that is more akin to a list of checkboxes, rather than radio boxes. When given the multiple attribute, a <select> tag will allow the user to select any number of options, rather than just a single option. This will, in most browsers, show up differently than a normal select field, which is typically drawn as a drop-down control that shows the options only when you open it. Each <option> tag has a value. This value can be defined with a value attribute. When that is not given, the text inside the option will count as its value. The value property of a <select> element reflects the currently selected option. For a multiple field, though, this property doesn’t mean much since it will give the value of only one of the currently selected options.

The <option> tags for a <select> field can be accessed as an array-like object through the field’s options property. Each option has a property called selected, which indicates whether that option is currently selected. The property can also be written to select or deselect an option. This example extracts the selected values from a multiple select field and uses them to compose a binary number from individual bits. Hold control (or command on a Mac) to select multiple options.

<select multiple>

  <option value=”1″>0001</option>

  <option value=”2″>0010</option>

  <option value=”4″>0100</option>

  <option value=”8″>1000</option>

</select> = <span id=”output”>0</span>

<script>

  let select = document.querySelector(“select”);

  let output = document.querySelector(“#output”);

  select.addEventListener(“change”, () => {

      let number = 0;

for (let option of Array.from(select.options)) {

  if (option.selected) {

      number += Number(option.value);

    }

}

output.textContent = number;

  });

</script>

File fields

File fields were originally designed as a way to upload files from the browser’s machine through a form. In modern browsers, they also provide a way to read such files from JavaScript programs. The field acts as a manner of gatekeeper. The script cannot simply start reading private files from the user’s computer, but if the user selects a file in such a field, the browser interprets that action to mean that the script may read the file. A file field usually looks like a button labeled with something like “choose file” or “browse”, with information about the chosen file next to it.

<input type=”file”>

<script>

   let input = document.querySelector(“input”);

  input.addEventListener(“change”, () => {

      if (input.files.length > 0) {

       let file = input.files[0];

       console.log(“You chose”, file.name);

if (file.type) console.log(“It has type”, file.type);

      }

  });

</script>

The files property of a file field element is an array-like object (again, not a real array) containing the files chosen in the field. It is initially empty. The reason there isn’t simply a file property is that file fields also support a multiple attribute, which makes it possible to select multiple files at the same time.Objects in the files object have properties such as name (the filename), size (the file’s size in bytes, which are chunks of 8 bits), and type (the media type of the file, such as text/plain or image/jpeg). What it does not have is a property that contains the content of the file. Getting at that is a little more involved. Since reading a file from disk can take time, the interface must be asynchronous to avoid freezing the document.

<input type=”file” multiple>

<script>

let input = document.querySelector(“input”);

input.addEventListener(“change”, () => {

for (let file of Array.from(input.files)) {

      let reader = new FileReader();

     reader.addEventListener(“load”, () => {

          console.log(“File”, file.name, “starts with”, reader.result.slice(0, 20));

    });

reader.readAsText(file);

      }

    });

</script>

Reading a file is done by creating a FileReader object, registering a “load” event handler for it, and calling its readAsText method, giving it the file we want to read. Once loading finishes, the reader’s result property contains the file’s content. FileReaders also fire an “error” event when reading the file fails for any reason. The error object itself will end up in the reader’s error property. This interface was designed before promises became part of the language. You could wrap it in a promise like this:

function readFileText(file) {

return new Promise((resolve, reject) => {

   let reader = new FileReader();

    reader.addEventListener(

“load”, () => resolve(reader.result));

     reader.addEventListener(

“error”, () => reject(reader.error));

   reader.readAsText(file);

    });

}

Storing data client-side

Simple HTML pages with a bit of JavaScript can be a great format for “mini applications”—small helper programs that automate basic tasks. By connecting a few form fields with event handlers, you can do anything from converting between centimeters and inches to computing passwords from a master password and a website name. When such an application needs to remember something between sessions, you cannot use JavaScript bindings—those are thrown away every time the page is closed. You could set up a server, connect it to the Internet, and have your application store something there.But that’s a lot of extra work and complexity. Sometimes it is enough to just keep the data in the browser. The localStorage object can be used to store data in a way that survives page reloads. This object allows you to file string values under names.

localStorage.setItem(“username”, “marijn”);

console.log(localStorage.getItem(“username”));

// → marijn

localStorage.removeItem(“username”);

A value in localStorage sticks around until it is overwritten, it is removed with removeItem, or the user clears their local data. Sites from different domains get different storage compartments. That means data stored in localStorage by a given website can, in principle, be read (and overwritten) only by scripts on that same site.

Browsers do enforce a limit on the size of the data a site can store in localStorage. That restriction, along with the fact that filling up people’s hard drives with junk is not really profitable, prevents the feature from eating up too much space. The following code implements a crude note-taking application. It keeps a set of named notes and allows the user to edit notes and create new ones.

Notes: <select></select> <button>Add</button><br>

<textarea style=”width: 100%”></textarea>

<script>

let list = document.querySelector(“select”);

let note = document.querySelector(“textarea”);

let state;

function setState(newState) {

list.textContent = “”;

for (let name of Object.keys(newState.notes)) {

    let option = document.createElement(“option”);

    option.textContent = name;

if (newState.selected == name) option.selected = true;

       list.appendChild(option);

}

note.value = newState.notes[newState.selected];

   localStorage.setItem(“Notes”, JSON.stringify(newState));

    state = newState;

}

setState(JSON.parse(localStorage.getItem(“Notes”)) || {

       notes: {“shopping list”: “Carrots\nRaisins”},

      selected: “shopping list”

});

list.addEventListener(“change”, () => {

    setState({notes: state.notes, selected: list.value});

});

note.addEventListener(“change”, () => {

setState({

      notes: Object.assign({}, state.notes,

                                           {[state.selected]: note.value}),

   selected: state.selected

     });

});

document.querySelector(“button”)

    .addEventListener(“click”, () => {

     let name = prompt(“Note name”);

     if (name) setState({

         notes: Object.assign({}, state.notes, {[name]: “”}),

selected: name

        });

   });

</script>

The script gets its starting state from the “Notes” value stored in localStorage or, if that is missing, creates an example state that has only a shopping list in it. Reading a field that does not exist from localStorage will yield null. Passing null to JSON.parse will make it parse the string “null” and return null. Thus, the || operator can be used to provide a default value in a situation like this. The setState method makes sure the DOM is showing a given state and stores the new state to localStorage. Event handlers call this function to move to a new state.

The use of Object.assign in the example is intended to create a new object that is a clone of the old state.notes, but with one property added or overwritten. Object.assign takes its first argument and adds all properties from any further arguments to it. Thus, giving it an empty object will cause it to fill a fresh object. The square brackets notation in the third argument is used to create a property whose name is based on some dynamic value. There is another object, similar to localStorage, called sessionStorage. The difference between the two is that the content of sessionStorage is forgotten at the end of each session, which for most browsers means whenever the browser is closed.

Summary

We discussed techniques for drawing graphics in the browser, focusing on the <canvas> element.A canvas node represents an area in a document that our program may draw on. This drawing is done through a drawing context object, created with the getContext method. The 2D drawing interface allows us to fill and stroke various shapes. The context’s fillStyle property determines how shapes are filled. The strokeStyle and lineWidth properties control the way lines are drawn. Rectangles and pieces of text can be drawn with a single method call. The fillRect and strokeRect methods draw rectangles, and the fillText and strokeText methods draw text. To create custom shapes, we must first build up a path. Calling beginPath starts a new path. A number of other methods add lines and curves to the current path. For example, lineTo can add a straight line. When a path is finished, it can be filled with the fill method or stroked with the stroke method.

Moving pixels from an image or another canvas onto our canvas is done with the drawImage method. By default, this method draws the whole source image, but by giving it more parameters, you can copy a specific area of the image. We used this for our game by copying individual poses of the game character out of an image that contained many such poses. Transformations allow you to draw a shape in multiple orientations. A 2D drawing context has a current transformation that can be changed with the translate, scale, and rotate methods. These will affect all subsequent drawing operations. A transformation state can be saved with the save method and restored with the restore method. When showing an animation on a canvas, the clearRect method can be used to clear part of the canvas before redrawing it.

We discussed how the HTTP protocol works. A client sends a request, which contains a method (usually GET) and a path that identifies a resource. The server then decides what to do with the request and responds with a status code and a response body. Both requests and responses may contain headers that provide additional information. The interface through which browser JavaScript can make HTTP requests is called fetch. Making a request looks like this:

fetch(“/18_http.html”).then(r => r.text()).then(text => {

console.log(`The page starts with ${text.slice(0, 15)}`);

});

Browsers make GET requests to fetch the resources needed to display a web page. A page may also contain forms, which allow information entered by the user to be sent as a request for a new page when the form is submitted. HTML can represent various types of form fields, such as text fields, check-boxes, multiple-choice fields, and file pickers. Such fields can be inspected and manipulated with JavaScript. They fire the “change” event when changed, fire the “input” event when text is typed, and receive keyboard events when they have keyboard focus. Properties like value (for text and select fields) or checked (for checkboxes and radio buttons) are used to read or set the field’s content.

When a form is submitted, a “submit” event is fired on it. A JavaScript handler can call preventDefault on that event to disable the browser’s default behavior. Form field elements may also occur outside of a form tag. When the user has selected a file from their local file system in a file picker field, the FileReader interface can be used to access the content of this file from a JavaScript program. The localStorage and sessionStorage objects can be used to save information in a way that survives page reloads. The first object saves the data forever (or until the user decides to clear it), and the second saves it until the browser is closed.

This Is A Custom Widget

This Sliding Bar can be switched on or off in theme options, and can take any widget you throw at it or even fill it with your custom HTML Code. Its perfect for grabbing the attention of your viewers. Choose between 1, 2, 3 or 4 columns, set the background color, widget divider color, activate transparency, a top border or fully disable it on desktop and mobile.

This Is A Custom Widget

This Sliding Bar can be switched on or off in theme options, and can take any widget you throw at it or even fill it with your custom HTML Code. Its perfect for grabbing the attention of your viewers. Choose between 1, 2, 3 or 4 columns, set the background color, widget divider color, activate transparency, a top border or fully disable it on desktop and mobile.