Изобретение собственных HTML-элементов для создания DOM-игры

Это небольшой эксперимент, показывающий, как изобретать собственные HTML-элементы для создания игры в DOM.

Что это за пользовательские элементы?

Пользовательский элемент — это HTML-элемент, который позволяет добавлять собственные свойства и методы. Например, базовый HTMLЭлемент имеет style имущество и click() метод. Расширяя элемент HTML, мы получаем всю существующую функциональность и можем добавлять свои собственные.

В этом эксперименте у нас есть элемент Car который имеет x и y имущество и update() метод:

class Car extends HTMLElement { 
  public x: number; 
  public y: number; 
  constructor(){ 
    super(); 
    console.log("A car was created!");
  } 
  public update() { 
    console.log("The car's update method was called!"); 
  }
}

Прежде чем мы сможем добавить наш элемент Car в DOM, мы должны зарегистрировать его, связав наш класс с HTML-тегом. Обратите внимание, что тег html должен содержать дефис:

window.customElements.define("car-component", Car);

Теперь вы можете добавлять автомобили в дом, расставляя теги:

<body> 
  <car-component></car-component>
</body>

В нашей игре мы предпочитаем добавлять автомобили по коду. Мы можем создать новый экземпляр Car и добавьте его в DOM одной строкой:

document.body.appendChild(new Car());

Это приведет к <car-component></car-component> добавляется в вашу структуру HTML, а сообщение A car was created! появится в консоли. Этот элемент HTML будет иметь x и y имущество и update() метод.

Манипуляции с DOM

Вы можете запросить свой HTML-документ для автомобильных компонентов и использовать новый for of петля для вызова их update() метод.

let cars : NodeListOf<Car> = document.getElementsByTagName("car-component") as NodeListOf<Car>;

for(let c of cars){
    c.update();
}

Жизненный цикл

Пользовательский элемент имеет хуки жизненного цикла: эти методы вызываются автоматически, когда Car добавляется или удаляется из DOM.

class Car extends HTMLElement { 
  public connectedCallback() { 
    console.log("A car was added to the DOM"); 
  } 
  public disconnectedCallback():void{ 
    console.log("hey! someone removed me from the DOM!"); 
  }
}

Цикл игры

Наш игровой класс создаст элемент player и запустит игровой цикл. Игровой цикл обновляет наши игровые элементы 60 раз в секунду, используя requestAnimationFrame.

  • Игровой цикл добавит new Car() в DOM каждую секунду с помощью оператора по модулю.
  • Игровой цикл найдет все <car-component> теги и вызвать их метод обновления.

Игра создается с помощью new Game() после загрузки окна. Обратите внимание, что самому классу Game не нужно расширять HTMLElement.

class Game { 
  private counter:number = 0; 
  constructor() { 
    document.body.appendChild(new Player()); 
    requestAnimationFrame(() => this.gameLoop()); 
  } 
  private gameLoop(){ 
    this.counter++; 
    if(this.counter%60 == 0) { 
      document.body.appendChild(new Car()); 
    } 
    let cars : NodeListOf<Car> = document.getElementsByTagName("car-component") as NodeListOf<Car>; 
    for(let c of cars){ 
      c.update(); 
    } 
    requestAnimationFrame(() => this.gameLoop()); 
  }
} 
window.addEventListener("load", () => new Game());

Удаление автомобилей

Когда автомобиль покидает экран или врезается в игрока, мы можем легко удалить его. Так как автомобиль наследуется от HTMLElement, мы можем использовать remove() метод, который удаляет его из DOM. Игровой цикл больше не будет вызывать метод update, и если мы хотим, мы можем использовать метод disconnectedCallback() чтобы выполнить какой-то окончательный код, прежде чем автомобиль будет полностью удален.

public disconnectedCallback():void{ 
  console.log("the car is removed from the game!");
} 
public update(): void { this.x += this.speed; if (this.x > window.innerWidth) {     this.remove(); }
}

Стилизация и анимация

Обратите внимание, что стили наших пользовательских элементов полностью выполняются в CSS. Сначала мы объявляем, что ВСЕ элементы документа будут использовать position:absoluteа затем объявляем размер и фоновое изображение для каждого отдельного элемента:

body * { 
  position: absolute; 
  display: block; 
  margin:0px; padding:0px; 
  box-sizing: border-box; 
  background-repeat: no-repeat;
} 
car-component { 
  width:168px; 
  height:108px; 
  background-image: url('../images/car.png');
}

Мы позиционируем наши элементы, используя css transformчтобы мы могли использовать GPU для плавной анимации.

this.style.transform = `translate(${this.x}px, ${this.y}px)`;

Расширение других элементов

В этом примере используется только extends HTMLElementно мы могли бы также расширить HTMLDivElement, HTMLButtonElementи т. д. В этом примере все наши элементы рассматриваются как <div> просто установив display:block в ССЦ.

Машинопись

Этот эксперимент построен с помощью Typescript, но вы можете легко перестроить его на чистом Javascript, удалив информацию о типе. Вы можете проверить main.js файл, чтобы увидеть эквивалент Javascript.

Чтобы скомпилировать этот проект, вы можете установить Typescript с помощью npm install -g typescriptа затем введите tsc -p в терминале в папке проекта.

Поддержка браузера

Приведенный выше эксперимент работает в Safari и Chrome. Расширение определенных элементов, таких как HTMLButtonElement, пока не работает ни в одном браузере. Использовать полифилл чтобы получить поддержку во всех браузерах.

Для цикла в NodeList

for of цикл еще не поддерживается для NodeList и HTMLCollection в Safari и Firefox, потому что технически они не являются массивами. Включите его с помощью:

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

Скачать рабочий проект по адресу:

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *