Здравствуйте, уважаемые читатели сайта Sumlab.ru!

В этой статье будет показано, как можно с помощью JavaScript создать веб-приложение, которое будет работать в браузере и имитировать движения рыб. Говоря проще, будет создан простой аквариум. В дальнейшем его можно будет установить на заставку в браузере или разместить на вашем сайте, чтобы он развлекал посетителей.

Конечный результат можно посмотреть здесь. Ссылки на исходный код будут указаны в конце статьи.

А теперь пройдёмся по порядку по всем этапам создания этого приложения.

Подготавливаем основу для рисования

Рисовать наш аквариум будем с помощью технологии canvas, которая доступна в html5, поэтому нам потребуется подготовить соответствующий html-документ. Обязательно в нём следует указать <!DOCTYPE html> и тег <canvas></canvas>. В нашем случае простой пример такой страницы будет выглядеть так:


<!DOCTYPE html>
<html lang="en-us">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Aquarium</title>
    <link href="./style.css" rel="stylesheet">
    <script defer src="./app.js"></script>
</head>

<body>
    <canvas></canvas>
</body>

</html>

Объявляем стартовые значения

Теперь переходим к созданию js-кода. Он будет написан в файле ./app.js, который ранее мы подключили на html-странице и который лежит в общей для нашего проекта директории. В этом файле мы сперва объявим основные константы, с которыми будем работать дальше.


// получаем ссылку на элемент canvas по его селектору 
const canvas = document.querySelector('canvas');
// получаем контекст для рисования
const ctx = canvas.getContext('2d');

// получаем ширину и высоту доступного окна
const width = canvas.width = window.innerWidth;
const height = canvas.height = window.innerHeight;

// указываем количество рыб
const COUNT_FISH = 50;
// создаём пустой массив, где будут храниться объекты всех рыб
const allFish = [];

Создаём служебную функцию

Также нам потребуется создать служебную функцию, которая будет возвращать случайное число в заданном диапазоне.


function random(min, max, withZero = true) {
    // генерируем случайное число
    let num = Math.floor(Math.random() * (max - min)) + min;
    
    // проверяем значение withZero. Если он true, то сразу возвращаем число num,
    // даже если там ноль
    if(withZero){
        return num;
    }
    
    // если значение withZero равно false, то запускаем цикл while до тех пор,
    // пока в num не будет значение отличное от нуля. После этого возвращаем его 
    while(num === 0){
        num = Math.floor(Math.random() * (max - min)) + min;
    }
    return num;
};

Функция random имеет три входных параметра: min - минимальное число; max - максимальное число; withZero - флаг, который указывает возвращать ноль или нет, если он попадает в заданный диапазон от min до max.

Создаём класс Fish

Переходим к одной из самых важных частей, созданию общего класса Fish. На основе этого класса мы будем генерировать объекты всех наших рыб.

В конструкторе класса происходит базовая инициализация объекта. Поэтому нам нужно заполнить полученными и сгенерированными значениями следующие свойств:

  • this.x - положение объекта на оси x;
  • this.y - положение объекта на оси y;
  • this.velX - скорость перемещения по оси x;
  • this.velY - скорость перемещения по оси y;
  • this.img_fish - создаём пустое изображение для текущего объекта;
  • this.fish - генерируем случайное название для текущей рыбы в диапазоне от fish1 до fish8 (понадобится для отображения разных видов рыб).

class Fish {
    constructor(x, y, velX, velY) {
        this.x = x;
        this.y = y;
        this.velX = velX;
        this.velY = velY;
        this.img_fish = new Image();
        this.fish = `fish${random(1, 8)}`;
    }
}

Добавляем в класс Fish метод draw. Он необходим для отрисовки изображения рыбы по текущим координатам x, y для вызываемого объекта. Само изображение рыбы будет меняться в зависимости от скорости движения velX (положительная или отрицательная). Если скорость движения рыбы меньше нуля, значит перемещение объекта происходит в левую сторону от текущего положения, а следовательно подключаем левостороннее изображение. В противном случае подключаем правостороннее изображение.


draw() {
    if(this.velX < 0){
        // подключаем левостороннее изображение
        this.img_fish.src = `./img/${this.fish}-l.png`;
    }else{
        // подключаем правосторонее изображение
        this.img_fish.src = `./img/${this.fish}-r.png`;
    }
    ctx.drawImage(this.img_fish, this.x, this.y);
}

На этом этапе мы уже настроили инициализацию объекта и его отображение. Теперь нам необходимо реализовать перемещение (движение) объекта. Для этого создадим метод update, который будет сдвигать объект по оси координат, в зависимости от значения скорости. А когда объект будет достигать какой-то границы, то знак скорости будет меняться на противоположный, и объект начнёт двигаться в обратную сторону.


update() {
    // если объект достиг правой границы, то меняем его скорость
    // в this.velX на противоположную
    if ((this.x + this.img_fish.width) >= width) {
        this.velX = -(this.velX);
    }

    // если объект достиг левой границы, то меняем его скорость
    // в this.velX на противоположную
    if (this.x <= 0) {
        this.velX = -(this.velX);
    }

    // если объект достиг нижней границы, то меняем его скорость
    // в this.velY на противоположную
    if ((this.y + this.img_fish.height) >= height) {
        this.velY = -(this.velY);
    }

    // если объект достиг верхней границы, то меняем его скорость
    // в this.velY на противоположную
    if (this.y <= 0) {
        this.velY = -(this.velY);
    }
    
    // когда определились с направлением движения, то меняем текущее положение объекта
    this.x += this.velX;
    this.y += this.velY;
}

Генерируем всех рыб

С созданием класса Fish на этом закончили. Теперь на его основе нужно создать всех наших рыб и заполнить ими массив allFish.


while (allFish.length < COUNT_FISH) {
    const fish = new Fish(
        random(0 + 100, width - 100),
        random(0 + 100, height - 100),
        random(-3, 3, false),
        random(-1, 1, false),
    );
    allFish.push(fish);
}

При создании экземпляра класса в его конструктор мы передаём следующие значения:

  • стартовое положение объекта рыбы по оси x. Чтобы рыба не создалась на краю области для рисования, делаем смещение на 100 пикселей от левой и правой границы;
  • стартовое положение объекта рыбы по оси y. Также делаем смещение на 100 пикселей по краям;
  • скорость перемещения объекта по оси x. От и до. Третий параметр указываем как false, чтобы исключить нулевую скорость;
  • скорость перемещения объекта по оси y. От и до. Третий параметр указываем как false, чтобы исключить нулевую скорость.

Когда получили готовый объект, добавляем его в конец массива allFish с помощью метода push.

Запускаем цикл

В завершении нам осталось создать функцию, которая будет повторяться циклически с помощью метода requestAnimationFrame, который предназначен для запуска анимации.


function loop() {
    // создаём фоновое изображение
    img_fon = new Image();
    img_fon.src = `./img/fon.jpg`;
    
    // заполняем контекст фоновым изображением 
    ctx.fillStyle = ctx.createPattern(img_fon, "repeat");
    ctx.fillRect(0, 0, width, height);
    
    // перебираем в цикле все объекты рыб. Для каждого запускаем методы draw и update, которые отрисовывают его и сдвигают.
    for (const fish of allFish) {
        fish.draw();
        fish.update();
    }
    
    // передаём запуск функции методу
    requestAnimationFrame(loop);
}
loop();

На этом создание аквариума на JavaScript можно считать завершённым. Ниже прикладываю скриншот с полученным результатом. Здесь же вы можете ознакомиться с живым примером, а здесь посмотреть на весь код в репозитории на GitHub.

Аквариум на JavaScript

Аквариум на JavaScript

А на этом всё! Всем спасибо за внимание.

Если вам понравилась статья, тогда не забывайте делиться ею в социальных сетях и писать ваши комментарии.