By using web components, we can create game objects that are also DOM objects. This makes it much easier to add and remove elements to a DOM Game.
A web component is a HTML element that allows us to add our own code. This example creates an HTML element that has a drive()
method. The element also adds itself to the DOM!
class Car extends HTMLElement {
constructor(){
super()
document.body.appendChild(this)
}
drive(){
console.log("VROOM!")
}
}
To use web components, they have to be registered. Note that the html tag needs to contain a hyphen:
window.customElements.define("car-component", Car);
In our Game class, we can now create new Car()
and it will automatically become a DOM element.
this.car = new Car()
this.car.drive()
This will result in a <car-component></car-component>
being added to your HTML structure, and the message VROOM!
will appear in the console.
Our game class will create a array of cars and start the game loop. A game loop updates our game elements 60 times per second using requestAnimationFrame
.
class Game {
cars : Car[]
constructor() {
this.cars = [new Car(), new Car(), new Car()]
}
update(){
for(let c of this.cars) {
c.drive()
}
requestAnimationFrame(()=>this.update())
}
}
A custom element has lifecycle hooks: these methods get called automatically when the Car is added to, or removed from, the DOM.
class Car extends HTMLElement {
public connectedCallback(): void {
console.log("A car was added to the DOM");
}
public disconnectedCallback():void{
console.log("hey! someone removed me from the DOM!");
}
}
Since the car extends from HTMLElement we can use the remove()
method, which removes it from the DOM.
let c = new Car() // adds car to the DOM
c.remove() // removes car from the DOM
We can use the disconnectedCallback()
to execute some final code before the car is removed from the DOM.
class Car {
public disconnectedCallback():void{
console.log("this car is about to be removed from the game!");
}
}
Note that if you keep references to the car in your Game
class, you need to remove those too!
this.cars = [new Car(), new Car(), new Car()]
// we will remove car 0 from the game
let index = 0
this.cars[index].remove() // remove element from DOM
this.cars.splice(index, 1) // remove reference
The styling of custom elements is done in CSS.
car-component {
position: absolute;
display: block;
width:168px; height:108px;
}
We position our elements using css transform
, so that we can use the GPU for smooth animation.
update() {
this.style.transform = `translate(${this.x}px, ${this.y}px)`
}
Ideally, your game keeps track of all existing cars, but you can also query the DOM for car components. Note that this query returns a NodeList instead of an Array.
let cars : NodeListOf<Car> = document.querySelector("car-component") as NodeListOf<Car>;
for(let c of cars){
c.drive()
}
To compile typescript with custom components, set the compile target to es6
in tsconfig.json
. Note that not all browsers support custom components fully.
{
"compilerOptions": {
"target": "es6"
}
}