Программируем скринсейвер для Илона

Попро­бу­ем новую для нас биб­лио­те­ку в JavaScript — p5. Её исполь­зу­ют, что­бы рас­ши­рить стан­дарт­ные воз­мож­но­сти рисо­ва­ния в JavaScript и писать для это­го более кру­тые скрипты. 

Сего­дняш­ний про­ект — полёт в кос­мо­се к звёз­дам, как буд­то мы летим на ракете:

Попробуем новую для нас библиотеку в JavaScript — p5

Логика работы

Что­бы всё это нари­со­вать, нам нужно:

  1. Под­го­то­вить стра­ни­цу так, что­бы содер­жи­мое зани­ма­ло весь раз­мер окна браузера.
  2. Запро­грам­ми­ро­вать общее пове­де­ние для звёзд — раз­ле­тать­ся к краю экра­на, посте­пен­но уве­ли­чи­ва­ясь в размере.
  3. Преду­смот­реть такое: если звез­да уле­те­ла за гра­ни­цы окна — создать новую звез­ду с таким же поведением.
  4. Создать мно­го звёзд с раз­ны­ми координатами.
  5. Запу­стить анимацию.

Боль­шин­ство функ­ций реа­ли­зу­ем с помо­щью биб­лио­те­ки p5 — в ней мно­го гото­вых команд, кото­рые упро­ща­ют раз­ра­бот­ку и отри­сов­ку любых объектов.

Что за P5 и зачем она нужна

Биб­лио­те­ка P5.js — это про­дви­ну­тая биб­лио­те­ка для рисо­ва­ния чего угод­но в бра­у­зе­ре. Она осно­ва­на на фрейм­вор­ке Processing — а он, в свою оче­редь, созда­вал­ся, что­бы помочь худож­ни­кам исполь­зо­вать для сво­их худо­же­ствен­ных дел инстру­мен­ты программирования.

Коро­че: P5.js — это удоб­ная рисо­вал­ка. Сей­час увидите. 

Подготовка

Исполь­зу­ем для про­ек­та стан­дарт­ный HTML-шаблон. Един­ствен­ное, что мы доба­вим ново­го в шаб­лон — про­пи­шем назва­ние проекта.

<!DOCTYPE html>
<html lang="ru" >
<head>
  <meta charset="UTF-8">
  <title>Полёт в космос</title>
 
  <style type="text/css">

  </style>

</head>
<body>

	<!-- основной скрипт -->
	<script type="text/javascript">
	
	</script>

</body>
</html>

Подготавливаем стили

Что­бы во всех бра­у­зе­рах наши звёз­ды выгля­де­ли оди­на­ко­во, под­клю­чим нор­ма­ли­за­тор стилей:

  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/
5.0.0/normalize.min.css">

Зада­ча нор­ма­ли­за­то­ра — при­ве­сти все сти­ли по умол­ча­нию во всех бра­у­зе­рах к одно­му обще­му виду.

Ещё нам нуж­но поме­нять настрой­ку управ­ле­ния раз­ме­ра­ми во всех эле­мен­тах на стра­ни­це. Сде­ла­ем так, что­бы мы мог­ли зада­вать раз­ме­ры всех эле­мен­тов в раз­ных еди­ни­цах одно­вре­мен­но, напри­мер, высо­ту в пик­се­лях, а шири­ну в про­цен­тах. Это помо­жет нам гиб­ко обра­ба­ты­вать поло­же­ния и раз­ме­ры звёзд.

Послед­нее, что оста­лось из сти­лей, — общие настрой­ки стра­ни­цы: убрать все отсту­пы и рас­тя­нуть содер­жи­мое на всю шири­ну окна. Но визу­аль­но на стра­ни­це не поме­ня­ет­ся ниче­го — она так же будет выгля­деть пустой страницей.

<style type="text/css">
  	*{
  		/* сделаем так, чтобы мы могли задавать размеры всех элементов в разных единицах одновременно, например, высоту в пикселях, а ширину в процентах */
		box-sizing: border-box;
	}
	html, body{
		/* убираем границы и отступы */
		margin: 0;
		padding: 0;
		/* разворачиваем космос на всю ширину окна браузера */
		width: 100%;
		height: 100vh;
		/* не отображаем звёзды, которые вылетели за пределы окна*/
		overflow: hidden;
	}
  </style>

Подключаем библиотеку p5 и создаём основной скрипт

Для под­клю­че­ния биб­лио­те­ки про­сто добав­ля­ем её вызов в тело страницы:

<!-- подключаем p5 — библиотеку для рисования в JavaScrpt -->

<script src='https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.min.js'></script>

После это­го созда­ём основ­ной скрипт, кото­рый будет отве­чать за всё, что про­ис­хо­дит на экране. Всё осталь­ное будем писать внут­ри него.

<!-- основной скрипт -->

<script type="text/javascript">

</script>

Программируем звёзды

Что­бы не опи­сы­вать каж­дую звез­ду отдель­но, а задать для них общее пове­де­ние, будем исполь­зо­вать клас­сы и мето­ды. Если вы не зна­е­те, что это такое, — почи­тай­те наш мини-цикл про объектно-ориентированное про­грам­ми­ро­ва­ние

Вот что мы сде­ла­ем с помо­щью класса:

  1. Про­пи­шем общую логи­ку созда­ния звез­ды — где имен­но она появит­ся и как близ­ко к вир­ту­аль­ной перед­ней гра­ни­це экра­на. За это в клас­сах отве­ча­ет кон­струк­тор — в нём пишут­ся коман­ды, что имен­но нуж­но сде­лать при созда­нии новой звезды.
  2. Запро­грам­ми­ру­ем, как будут менять­ся коор­ди­на­ты звез­ды во вре­мя дви­же­ния. Что­бы изме­не­ния про­изо­шли, нуж­но создать новый метод и вызвать его в нуж­ный момент.
  3. Доба­вим воз­мож­ность отри­сов­ки звез­ды по теку­щим коор­ди­на­там. За это тоже будет отве­чать свой метод.

Эти­ми свой­ства­ми и воз­мож­но­стя­ми будут обла­дать все звёз­ды на осно­ве это­го клас­са — в этом и смысл ООП. Даже если мы созда­дим мил­ли­он звёзд, нам не при­дёт­ся про­пи­сы­вать логи­ку каж­дой звез­ды — за нас это сде­ла­ет класс.

// сколько звёзд может быть одновременно на экране
var starsCount = 800;
// на старте массив со звёздами будет пустой
var stars = [];
// класс, на основе которого будут сделаны все звёзды
class Star{
	// конструктор, который вызывается при создании каждого объекта на основе этого класса
	constructor(){
		// у новой звезды будут случайные координаты
		this.x = random(-width, width);
		this.y = random(-height, height);
		// глубина — насколько виртуально близко к экрану появится звезда
		this.z = random(width);
	}
	// метод, который обновляет координаты звезды
	update(){
		// скорость полёта
		var speed = 12;
		// приближаем звезду к краю экрана, уменьшая глубину на значение скорости
		this.z -= speed;
		// если звезда вылетела за край экрана — делаем из неё новую звезду, для этого меняем координаты
		if(this.z < 1){
			this.x = random(-width, width);
			this.y = random(-height, height);
			// для новой звезды вместо старой глубину появления теперь выбираем не случайным образом, а задаём прямо
			this.z = width;
		}
	}

	// метод, который отрисовывает звезду на экране
	drawStar(){
		// каждая звезда — белого цвета
		fill(255);
		// и без контура
		noStroke();

		// с помощью функции map() из библиотеки p5.js получаем новые координаты для отрисовки звезды 
		var sx = map(this.x / this.z, 0, 1, 0, width);
		var sy = map(this.y / this.z, 0, 1, 0, height);

		// чем ближе к краю экрана (чем меньше глубина z) — тем больше радиус 
		var r = map(this.z, 0, width, 10, 0);
		// рисуем звезду в новых координатах и новым размером
		ellipse(sx, sy, r, r);
	}
}

Готовимся к запуску

В биб­лио­те­ке p5 есть спе­ци­аль­ная функ­ция setup() — код, кото­рый в ней напи­сан, выпол­нит­ся сра­зу после загруз­ки стра­ни­цы со скрип­том. Сде­ла­ем такую функ­цию в нашем скрип­те, что­бы в ней запро­грам­ми­ро­вать созда­ние хол­ста и всех звёзд.

// подготавливаем всё к запуску — то, что написано здесь выполнится автоматически сразу после загрузки
function setup(){
	// создаём холст, на котором будем рисовать
	createCanvas(innerWidth, innerHeight);
	// размещаем сразу все звёзды на холсте
	for (var i = 0; i < starsCount; i++) {
		// каждая новая звезда — объект класса Star и умеет то же самое, что и все остальные звёзды
		stars[i] = new Star();
	}
}

Рисуем и запускаем анимацию

Ещё одна спе­ци­аль­ная функ­ция из той же биб­лио­те­ки — draw(). Она отве­ча­ет за рисо­ва­ние и ани­ма­цию. Всё, что напи­са­но внут­ри этой функ­ции, будет выпол­нять­ся раз за разом, по кру­гу, пока мы не закро­ем стра­ни­цу. Исполь­зу­ем это, что­бы сде­лать ани­ма­цию для звёзд. 

👉 При каж­дом про­хо­де draw() не очи­ща­ет холст, нам нуж­но сде­лать это само­сто­я­тель­но. Для это­го будем про­сто закра­ши­вать всё чёр­ным и рисо­вать звёз­ды заново.

/ пока мы не закроем страницу, постоянно будет выполняться функция drw() 
function draw(){
	// ставим чёрный фон и указываем скорость обновления фона — чем меньше второе число, тем больший шлейф будут оставлять звёзды
	background(0, 180);
	// формируем центр экрана, куда «полетим» сквозь звёзды
	translate(width/2, height/2);

	// отрисовываем каждый раз все звёзды и меняем их положение
	for (var i = 0; i < starsCount; i++) {
		stars[i].drawStar();
		stars[i].update();
	}
}

Следим за размерами экрана

Что­бы у нас полёт рисо­вал­ся про­пор­ци­о­наль­но экра­ну даже при изме­не­нии раз­ме­ра окна, доба­вим обра­бот­чик это­го собы­тия. Он сра­бо­та­ет при каж­дом изме­не­нии раз­ме­ров окна и сра­зу изме­нит раз­мер холста.

// если поменяется размер окна браузера — сразу меняем размер холста

addEventListener('resize', () => {

resizeCanvas(innerWidth, innerHeight);

})

Результат

Посмот­реть, что полу­чи­лось с новой биб­лио­те­кой, мож­но на стра­ни­це про­ек­та. Если вы хоти­те запу­стить такое у себя на ком­пью­те­ре, ско­пи­руй­те гото­вый код и открой­те HTML-страницу в браузере.

Готовый код

<!DOCTYPE html>
<html lang="ru" >
<head>
  <meta charset="UTF-8">
  <title>Полёт в космос</title>
  <!-- сделаем так, чтобы в каждых браузерах результат выглядел одинаково — подключим нормализатор стилей -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
  <style type="text/css">
  	*{
  		/* сделаем так, чтобы мы в блоке управляли размерами не контента, а самого блока — это понадобится для звёзд */
		box-sizing: border-box;;
	}
	html, body{
		/* убираем границы и отступы */
		margin: 0;
		padding: 0;
		/* разворачиваем космос на всю ширину окна браузера */
		width: 100%;
		height: 100vh;
		/* не отображаем звёзды, которые вылетели за пределы окна*/
		overflow: hidden;
	}
  </style>

</head>
<body>
	<!-- подключаем p5 — библиотеку для рисования в JavaScrpt -->
	<script src='https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.min.js'></script>
	<!-- основной скрипт -->
	<script type="text/javascript">
	// сколько звёзд может быть одновременно на экране
	var starsCount = 800;
	// на старте массив со звёздами будет пустой
	var stars = [];
	// класс, на основе которого будут сделаны все звёзды
	class Star{
		// конструктор, который вызывается при создании каждого объекта на основе этого класса
		constructor(){
			// у новой звезды будут случайные координаты
			this.x = random(-width, width);
			this.y = random(-height, height);
			// глубина — насколько виртуально близко к экрану появится звезда
			this.z = random(width);
		}
		// метод, который обновляет координаты звезды
		update(){
			// скорость полёта
			var speed = 12;
			// приближаем звезду к краю экрана, уменьшая глубину на значение скорости
			this.z -= speed;
			// если звезда вылетела за край экрана — делаем из неё новую звезду, для этого меняем координаты
			if(this.z < 1){
				this.x = random(-width, width);
				this.y = random(-height, height);
				// для новой звезды вместо старой глубину появления теперь выбираем не случайным образом, а задаём прямо
				this.z = width;
			}
		}

		// метод, который отрисовывает звезду на экране
		drawStar(){
			// каждая звезда — белого цвета
			fill(255);
			// и без контура
			noStroke();

			// с помощью функции map() из библиотеки p5.js получаем новые координаты для отрисовки звезды 
			var sx = map(this.x / this.z, 0, 1, 0, width);
			var sy = map(this.y / this.z, 0, 1, 0, height);

			// чем ближе к краю экрана (чем меньше глубина z) — тем больше радиус 
			var r = map(this.z, 0, width, 10, 0);
			// рисуем звезду в новых координатах и новым размером
			ellipse(sx, sy, r, r);
		}
	}

	// подготавливаем всё к запуску — то, что написано здесь выполнится автоматически сразу после загрузки
	function setup(){
		// создаём холст, на котором будем рисовать
		createCanvas(innerWidth, innerHeight);
		// размещаем сразу все звёзды на холсте
		for (var i = 0; i < starsCount; i++) {
			// каждая новая звезда — объект класса Star и умеет то же самое, что и все остальные звёзды
			stars[i] = new Star();
		}
	}

// пока мы не закроем страницу, постоянно будет выполняться функция drw() 
function draw(){
	// ставим чёрный фон и указываем скорость обновления фона — чем меньше второе число, тем больший шлейф будут оставлять звёзды
	background(0, 180);
	// формируем центр экрана, куда «полетим» сквозь звёзды
	translate(width/2, height/2);

	// отрисовываем каждый раз все звёзды и меняем их положение
	for (var i = 0; i < starsCount; i++) {
		stars[i].drawStar();
		stars[i].update();
	}
}

	// если поменяется размер окна браузера — сразу меняем размер холста
	addEventListener('resize', () => {
		resizeCanvas(innerWidth, innerHeight);
	})	
	</script>

</body>
</html>

Что дальше

Даль­ше сде­ла­ем ещё несколь­ко про­ек­тов с биб­лио­те­кой p5. Заод­но попрак­ти­ку­ем­ся в ООП — созда­дим объ­ек­ты на осно­ве объ­ек­тов из дру­го­го класса.

Текст:
Миха­ил Полянин

Код:
Sikriti Dakua

Редак­тор:
Мак­сим Ильяхов

Худож­ник:
Даня Бер­ков­ский

Кор­рек­тор:
Ири­на Михеева

Вёрст­ка:
Мария Дро­но­ва

Соц­се­ти:
Олег Веш­кур­цев