Текстовый дождь, как в «Матрице»

Недав­но мы дела­ли про­ект про полёт в кос­мос в 3D, где исполь­зо­ва­ли для рисо­ва­ния биб­лио­те­ку p5 и прин­ци­пы ООП. Тогда мы огра­ни­чи­лись одним клас­сом и объ­ек­та­ми на осно­ве это­го класса.

Сего­дня будет инте­рес­нее — мы сде­ла­ем два клас­са, при­чём объ­ек­ты одно­го клас­са будут состо­ять из объ­ек­тов дру­го­го клас­са. Это нуж­но, что­бы вос­со­здать эффект «Мат­ри­цы»: когда бук­вы одно­вре­мен­но и пада­ют, и сменяются. 

Что делаем

Туч­ку, из кото­рой пада­ют бук­вы, как в филь­ме «Мат­ри­ца»:

Зачем мы это сде­ла­ем? Ради кра­со­ты, ради искус­ства, ради JavaScript.

После­до­ва­тель­ность дей­ствий будет такая:

  1. Под­го­тав­ли­ва­ем стра­ни­цу и настра­и­ва­ем стили.
  2. Рису­ем тучку.
  3. Про­грам­ми­ру­ем паде­ние и сме­ну символов.
  4. Объ­еди­ня­ем сим­во­лы в пото­ки, что­бы они пада­ли из туч­ки друг за другом.
  5. Запус­ка­ем.

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

Подготовка страницы

Исполь­зу­ем для про­ек­та стан­дарт­ный 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>

Что­бы сим­во­лы выгля­де­ли кра­си­во и мы мог­ли ими управ­лять, под­клю­чим сти­ли Font Awesome — это такой набор пра­вил по оформ­ле­нию шрифтов.

  <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css'>

Настраиваем стили

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

Затем зада­дим общие свой­ства для той обла­сти, где будет нари­со­ва­на туч­ка — раз­ме­ры и поло­же­ние. Отдель­но про­пи­шем свой­ство z-index — оно вир­ту­аль­но при­под­ни­мет слой с туч­кой выше осталь­ных, что­бы туч­ка закры­ва­ла те сим­во­лы, кото­рые появи­лись внут­ри неё, но кото­рым ещё рано появляться.

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

/* сделаем так, чтобы мы могли задавать размеры всех элементов в разных единицах одновременно, например, высоту в пикселях, а ширину в процентах */
*{
	box-sizing: border-box;
}
/* общие настройки для страницы */
body{
	/* убираем отступы */
	margin: 0;
	padding: 0;
	/* занимаем всю ширину и высоту окна браузера */
	width: 100%;
	height: 100vh;
	/* не рисуем то, что вылетело за границы */
	overflow: hidden;
	/* цвет фона */
	background: #2d3436;
	/* располагаем элементы страницы по центру*/
	display: flex;
	justify-content: center;
	align-items: center;
}

/* настройки области, где будет нарисована тучка */
.cloud-svg{
	/* высота и ширина области */
	width: 18rem;
	height: 18rem;
	position: fixed;
	/* центрируем по горизонтали */
	top: 120px;
	left: 50%;
	transform: translate(-50%, -50%);
	/* поднимаем тучку выше остальных элементов на странице, чтобы она прятала символы, которые ещё не появились*/
	z-index: 10;
}

/* настройки для отрисовки самой тучки */
.cloud{
	/* настройки фона и контура тучки */
	fill: #2ecc71;
	stroke: #27ae60;
	stroke-width: 2px;
	stroke-linecap: round;
	stroke-miterlimit: 10;
}

/* настройка области, где будут падающие буквы */
canvas{
	position: fixed;
	/* отступ сверху */
	top: 350px;
	/* центрируем по горизонтали*/
	left: 50%;
	transform: translate(-50%, -50%);
}

Рисуем тучку

Что­бы нари­со­вать туч­ку, исполь­зу­ем теги <svg> и <path>. Пер­вый тэг <svg> гово­рит бра­у­зе­ру, что в выбран­ном бло­ке будем рисо­вать век­тор­ную графику.

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

Тэг <path> рису­ет линию по задан­ным пра­ви­лам. Вкрат­це эти пра­ви­ла мож­но опи­сать так:

  1. Пере­ме­ща­ем­ся на нуж­ные координаты.
  2. Рису­ем линию от ста­рых коор­ди­нат до новых.
  3. Пере­ме­ща­ем­ся на новые коор­ди­на­ты, рису­ем линию до ста­рых и так повто­ря­ем мно­го раз.
  4. Если нуж­но, в самом кон­це соеди­ня­ем точ­ку с теку­щи­ми коор­ди­на­та­ми с началь­ной точкой.

У нас ещё будет отдель­ная ста­тья с раз­бо­ром, как рабо­та­ет SVG, а пока про­сто исполь­зу­ем гото­вый код:

<!-- рисуем тучку -->
<div id="wrapper">
	<svg class="cloud-svg" x="0px" y="0px" viewBox="0 0 60 60" style="enable-background:new 0 0 60 60;">
		<path class="cloud" d="M50.003,27 c-0.115-8.699-7.193-16-15.919-16c-5.559,0-10.779,3.005-13.661,7.336C19.157,17.493,17.636,17,16,17c-4.418,0-8,3.582-8,8 c0,0.153,0.014,0.302,0.023,0.454C8.013,25.636,8,25.82,8,26c-3.988,1.912-7,6.457-7,11.155C1,43.67,6.33,49,12.845,49h24.507 c0.138,0,0.272-0.016,0.408-0.021C37.897,48.984,38.031,49,38.169,49h9.803C54.037,49,59,44.037,59,37.972 C59,32.601,55.106,27.961,50.003,27z"/>
		<!-- дополнительные штрихи на тучке для объёма -->
		<path class="cloud" d="M50.003,27 c0,0-2.535-0.375-5.003,0"/>
		<path class="cloud" d="M8,25c0-4.418,3.582-8,8-8 s8,3.582,8,8"/>
	</svg>
</div>
Полу­чи­лась про­сто туч­ка, пока без дождя. 

Что такое объекты, классы, методы и конструкторы? 

Далее мы будем исполь­зо­вать поня­тия «объ­ект», «метод», «класс» и «кон­струк­тор». Это базо­вые поня­тия ООП. Если хоти­те углу­бить­ся — читай­те наш цикл, а пока вот основ­ные положения: 

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

Метод — это дей­ствие, кото­рое может совер­шать объ­ект. Напри­мер, сим­вол из туч­ки может опус­кать­ся на сколько-то вниз. Если метод досту­пен извне объ­ек­та, то мы можем в про­грам­ме ска­зать, гру­бо гово­ря, так: Буква.капни(), где буква — это объ­ект, а капни() — это метод. 

Класс — это «чер­тёж», по кото­ро­му наша про­грам­ма может изго­то­вить мно­го оди­на­ко­вых объ­ек­тов, как на кон­вей­е­ре. В нашем слу­чае нам нуж­но, что­бы про­грам­ма рисо­ва­ла сот­ни букв в десят­ках пото­ков, и изго­тав­ли­вать их мы будем не вруч­ную, а с помо­щью класса. 

Кон­струк­тор — это дей­ствие, кото­рое совер­ша­ет про­грам­ма при созда­нии объ­ек­та. Без кон­струк­то­ра объ­ект про­сто висел бы в памя­ти, а с кон­струк­то­ром он сра­зу при созда­нии будет выпол­нять какое-то дей­ствие. В нашем слу­чае кон­струк­тор будет у клас­са «Поток», что­бы при созда­нии тако­го пото­ка в него сра­зу добав­ля­лось несколь­ко сме­ня­ю­щих­ся букв.

Готовим основной скрипт

Вся дож­де­вая магия будет про­ис­хо­дить в отдель­ном скрип­те, кото­рый мы напи­шем после кода с туч­кой. Что­бы рисо­вать было про­ще, под­клю­чим биб­лио­те­ку p5 и про­пи­шем основ­ные пере­мен­ные для скрипта:

<!-- подключаем p5 — библиотеку для рисования в JavaScrpt -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.min.js'></script>

<script type="text/javascript">
	// размеры области, где будет падать текст
	var height = 420, width = 250;
	// на старте массив символов пуст
	var symbols = [];
	// размер символа
	var symbolSize = 10;
	// переменная для таймера
	var timer = 0;
	// на старте массив с потоками из символов тоже пуст
	var streams = [];
</script>

Проектируем поведение символов

Что­бы не про­пи­сы­вать пове­де­ние для каж­до­го сим­во­ла в отдель­но­сти, а задать общие пра­ви­ла для всех, сде­ла­ем класс Symbol. Каж­дый объ­ект, создан­ный на осно­ве это­го клас­са, будет вести себя так, как напи­са­но в клас­се. Это даст нам пред­ска­зу­е­мое пове­де­ние для каж­до­го сим­во­ла и сокра­тит коли­че­ство кода.

В кон­струк­то­ре клас­са мы про­пи­шем началь­ные коор­ди­на­ты для сим­во­ла, его ско­рость, а так­же сме­ну сим­во­ла через неко­то­рое вре­мя, что­бы полу­чить эффект как в «Мат­ри­це». Что­бы все сим­во­лы не обнов­ля­лись одно­вре­мен­но, исполь­зу­ем в кон­струк­то­ре гене­ра­тор слу­чай­ных чисел — он выбе­рет слу­чай­ное вре­мя обнов­ле­ния для каж­до­го ново­го символа. 

Так­же сде­ла­ем метод rain() — он будет отве­чать за паде­ние сим­во­ла вниз, как кап­ля дождя. Логи­ка про­стая: если сим­вол ещё не уле­тел за край, сдви­га­ем его вниз на зна­че­ние скорости.

// класс, на основе которого будут сделаны все символы, падающие из тучки
class Symbol{
	// конструктор, который вызывается при создании каждого объекта на основе этого класса
	constructor(x, y, speed){
		// координаты и скорость нового символа
		this.x = x;
		this.y = y;
		this.speed = speed;
		// время переключения на новый символ
		this.switchInterval = round(random(5, 20));
		this.value;
		// метод, который выбирает символ из случайного диапазона 
		this.setRandomSymbol = function(){
			// если пришло время обновить символ —
			if(timer % this.switchInterval == 0){
				// берём новый из случайного диапазона
				this.value = String.fromCharCode(
					// если поменять стартовый диапазон — поменяется и язык символов
					0x10A0 + round(random(0, 96))
				);
			}
		}
	}
	// метод, который отвечает за падение символов
	rain(){
		// если ещё не достигли нижней границы — символ смещается вниз с установленной скоростью
		this.y = (this.y >= height) ? 0: this.y += this.speed;
	}
}

Собираем символы в потоки

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

Кон­струк­тор про­сто созда­ёт мас­сив слу­чай­но­го раз­ме­ра, а метод generateSymbols(x,y) как раз и напол­нит этот мас­сив нуж­ны­ми сим­во­ла­ми. При этом каж­дый сим­вол — это объ­ект клас­са Symbol, свой­ство кото­рых мы напи­са­ли выше. 

Отдель­ный метод render() будет отве­чать за отри­сов­ку пото­ка. Обра­ти­те вни­ма­ние — внут­ри это­го мето­да мы вызы­ва­ем метод из дру­го­го клас­са, кото­рый отве­ча­ет за паде­ние символов.

// класс, на основе которого будут сделаны все потоки, состоящие из нескольких символов
class Stream{
	// конструктор, который вызывается при создании каждого объекта на основе этого класса	
	constructor(){
		// на старте в каждом потоке ничего нет
		this.stream = [];
		// в каждом потоке будет от 2 до 5 символов
		this.streamLength = round(random(2,5));
		// выбираем скорость потока случайным образом
		this.speed = random(5, 8);
	}
	// метод, который создаёт последовательность символов в потоке
	generateSymbols(x,y){
		// пока есть свободные места в массиве с потоком
		for(let i = 0; i <= this.streamLength; i++){
			// создаём новый объект-символ на основе класса для символов
			var symbol = new Symbol(x, y, this.speed);
			// формируем его значение случайным образом встроенным методом
			symbol.setRandomSymbol();
			// отправляем этот символ в массив с потоком
			this.stream.push(symbol);
			// следующий символ в потоке добавится под уже существующими
			y -= symbolSize;
		}
	}
	// метод, который отрисовывает поток символов на экране
	render(){
		// обрабатываем каждый символ в потоке
		this.stream.forEach(symbol => {
			// устанавливаем цвет символов
			fill('#00b894');
			// пишем на экране нужный символ по заданным координатам
			text(symbol.value, symbol.x, symbol.y)
			// обновляем символы, чтобы они менялись, как в Матрице
			symbol.setRandomSymbol();
			// смещаем их вниз, чтобы создать эффект дождя
			symbol.rain();
		});
	}
}

Запуск

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

Для под­го­тов­ки нам нуж­но создать холст, где будем рисо­вать пада­ю­щие бук­вы и сфор­ми­ро­вать пото­ки. Пото­ки будем делать так:

  1. Возь­мём шири­ну тучки.
  2. Раз­де­лим её на шири­ну символа.
  3. Так мы полу­чим, сколь­ко пото­ков у нас визу­аль­но может выхо­дить из тучки.
  4. Сфор­ми­ру­ем нуж­ное коли­че­ство потоков.
  5. Сами пото­ки будем стар­то­вать внут­ри туч­ки — на стар­те они будут скры­ты самой туч­кой, но дадут хоро­ший эффект появ­ле­ния, когда будут падать вниз.

Для ани­ма­ции в функ­ции draw() мы будем делать все­го две вещи — посто­ян­но очи­щать фон и отри­со­вы­вать потоки.

// подготавливаем всё к запуску — то, что написано здесь выполнится автоматически сразу после загрузки
function setup(){
	// задаём размеры холста и рисуем его
	var height = 420, width = 220;
	var c = createCanvas(width, height);
	// находим блок с облачком по имени
	c.parent('wrapper')
	var x = 0;
	// пока у нас есть место по ширине для потоков из тучки
	for(let i = 0; i <= width/symbolSize; i++){
		// все потоки формируем внутри самой тучки, невидимо для зрителя
		var y = random(-300, 0);
		// создаём новый поток на основе класса
		var stream = new Stream();
		// заполняем его символами
		stream.generateSymbols(x, y);
		// и отправляем в массив с потоками
		streams.push(stream);
		// сдвигаем координаты вправо для следующего потока
		x += symbolSize;
	}
	// устанавливаем размер символов
	textSize(symbolSize)
}
// пока мы не закроем страницу, постоянно будет выполняться функция draw() 
function draw(){
	// цвет фона
	background('#2d343680');
	// берём каждый поток…
	streams.forEach(stream => {
		// … и отрисовываем его
		stream.render();
	})
	// при каждой отрисовке увеличиваем значение таймера (оно нам нужно для смены символов через определённое время)
	timer++;
}
Гото­вый тек­сто­вый дождь из тучки. 

Мож­но ско­пи­ро­вать и запу­стить у себя гото­вый код, а мож­но посмот­реть на туч­ку на стра­ни­це про­ек­та.

Готовый код

<!DOCTYPE html>
<html lang="ru" >
<head>
  <meta charset="UTF-8">
  <title>Текстовый дождь из тучки</title>
  <!-- подключаем стили для красивого оформления символов -->
  <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css'>
  <style type="text/css">
  	/* сделаем так, чтобы мы могли задавать размеры всех элементов в разных единицах одновременно, например, высоту в пикселях, а ширину в процентах */
  	*{
  		box-sizing: border-box;
  	}
  	/* общие настройки для страницы */
  	body{
  		/* убираем отступы */
  		margin: 0;
  		padding: 0;
  		/* занимаем всю ширину и высоту окна браузера */
  		width: 100%;
  		height: 100vh;
  		/* не рисуем то, что вылетело за границы */
  		overflow: hidden;
  		/* цвет фона */
  		background: #2d3436;
  		/* располагаем элементы страницы по центру*/
  		display: flex;
  		justify-content: center;
  		align-items: center;
  	}

  	/* настройки области, где будет нарисована тучка */
  	.cloud-svg{
  		/* высота и ширина области */
  		width: 18rem;
  		height: 18rem;
  		position: fixed;
  		/* центрируем по горизонтали */
  		top: 120px;
  		left: 50%;
  		transform: translate(-50%, -50%);
  		/* поднимаем тучку выше остальных элементов на странице, чтобы она прятала символы, которые ещё не появились*/
  		z-index: 10;
  	}

  	/* настройки для отрисовки самой тучки */
  	.cloud{
  		/* настройки фона и контура тучки */
  		fill: #2ecc71;
  		stroke: #27ae60;
  		stroke-width: 2px;
  		stroke-linecap: round;
  		stroke-miterlimit: 10;
  	}

  	/* настройка области, где будут падающие буквы */
  	canvas{
  		position: fixed;
  		/* отступ сверху */
  		top: 350px;
  		/* центрируем по горизонтали*/
  		left: 50%;
  		transform: translate(-50%, -50%);
  	}

  </style>

</head>
<body>

	<!-- рисуем тучку -->
	<div id="wrapper">
		<svg class="cloud-svg" x="0px" y="0px" viewBox="0 0 60 60" style="enable-background:new 0 0 60 60;">
			<path class="cloud" d="M50.003,27 c-0.115-8.699-7.193-16-15.919-16c-5.559,0-10.779,3.005-13.661,7.336C19.157,17.493,17.636,17,16,17c-4.418,0-8,3.582-8,8 c0,0.153,0.014,0.302,0.023,0.454C8.013,25.636,8,25.82,8,26c-3.988,1.912-7,6.457-7,11.155C1,43.67,6.33,49,12.845,49h24.507 c0.138,0,0.272-0.016,0.408-0.021C37.897,48.984,38.031,49,38.169,49h9.803C54.037,49,59,44.037,59,37.972 C59,32.601,55.106,27.961,50.003,27z"/>
			<!-- дополнительные штрихи на тучке для объёма -->
			<path class="cloud" d="M50.003,27 c0,0-2.535-0.375-5.003,0"/>
			<path class="cloud" d="M8,25c0-4.418,3.582-8,8-8 s8,3.582,8,8"/>
		</svg>
	</div>

	<!-- подключаем p5 — библиотеку для рисования в JavaScrpt -->
  <script src='https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.min.js'></script>

	<script type="text/javascript">
		// размеры области, где будет падать текст
		var height = 420, width = 250;
		// на старте массив символов пуст
		var symbols = [];
		// размер символа
		var symbolSize = 10;
		// переменная для таймера
		var timer = 0;
		// на старте массив с потоками из символов тоже пуст
		var streams = [];

		// класс, на основе которого будут сделаны все символы, падающие из тучки
		class Symbol{
			// конструктор, который вызывается при создании каждого объекта на основе этого класса
			constructor(x, y, speed){
				// координаты и скорость нового символа
				this.x = x;
				this.y = y;
				this.speed = speed;
				// время переключения на новый символ
				this.switchInterval = round(random(5, 20));
				this.value;
				// метод, который выбирает символ из случайного диапазона 
				this.setRandomSymbol = function(){
					// если пришло время обновить символ —
					if(timer % this.switchInterval == 0){
						// берём новый из случайного диапазона
						this.value = String.fromCharCode(
							// если поменять стартовый диапазон — поменяется и язык символов
							0x10A0 + round(random(0, 96))
						);
					}
				}
			}
			// метод, который отвечает за падение символов
			rain(){
				// если ещё не достигли нижней границы — символ смещается вниз с установленной скоростью
				this.y = (this.y >= height) ? 0: this.y += this.speed;
			}
		}

		// класс, на основе которого будут сделаны все потоки, состоящие из нескольких символов
		class Stream{
			// конструктор, который вызывается при создании каждого объекта на основе этого класса	
			constructor(){
				// на старте в каждом потоке ничего нет
				this.stream = [];
				// в каждом потоке будет от 2 до 5 символов
				this.streamLength = round(random(2,5));
				// выбираем скорость потока случайным образом
				this.speed = random(5, 8);
			}
			// метод, который создаёт последовательность символов в потоке
			generateSymbols(x,y){
				// пока есть свободные места в массиве с потоком
				for(let i = 0; i <= this.streamLength; i++){
					// создаём новый объект-символ на основе класса для символов
					var symbol = new Symbol(x, y, this.speed);
					// формируем его значение случайным образом встроенным методом
					symbol.setRandomSymbol();
					// отправляем этот символ в массив с потоком
					this.stream.push(symbol);
					// следующий символ в потоке добавится под уже существующими
					y -= symbolSize;
				}
			}
			// метод, который отрисовывает поток символов на экране
			render(){
				// обрабатываем каждый символ в потоке
				this.stream.forEach(symbol => {
					// устанавливаем цвет символов
					fill('#00b894');
					// пишем на экране нужный символ по заданным координатам
					text(symbol.value, symbol.x, symbol.y)
					// обновляем символы, чтобы они менялись, как в Матрице
					symbol.setRandomSymbol();
					// смещаем их вниз, чтобы создать эффект дождя
					symbol.rain();
				});
			}
		}

		// подготавливаем всё к запуску — то, что написано здесь выполнится автоматически сразу после загрузки
		function setup(){
			// задаём размеры холста и рисуем его
			var height = 420, width = 220;
			var c = createCanvas(width, height);
			// находим блок с облачком по имени
			c.parent('wrapper')
			var x = 0;
			// пока у нас есть место по ширине для потоков из тучки
			for(let i = 0; i <= width/symbolSize; i++){
				// все потоки формируем внутри самой тучки, невидимо для зрителя
				var y = random(-300, 0);
				// создаём новый поток на основе класса
				var stream = new Stream();
				// заполняем его символами
				stream.generateSymbols(x, y);
				// и отправляем в массив с потоками
				streams.push(stream);
				// сдвигаем координаты вправо для следующего потока
				x += symbolSize;
			}
			// устанавливаем размер символов
			textSize(symbolSize)
		}
		// пока мы не закроем страницу, постоянно будет выполняться функция draw() 
		function draw(){
			// цвет фона
			background('#2d343680');
			// берём каждый поток…
			streams.forEach(stream => {
				// … и отрисовываем его
				stream.render();
			})
			// при каждой отрисовке увеличиваем значение таймера (оно нам нужно для смены символов через определённое время)
			timer++;
		}

	</script>

</body>
</html>

Что дальше

Будем осва­и­вать SVG-рисование в бра­у­зе­ре — рисо­вать вся­кие класс­ные и кра­си­вые штуки.

Текст и код:

Миха­ил Полянин

Редак­ту­ра:

Мак­сим Ильяхов

Худож­ник:

Даня Бер­ков­ский

Кор­рек­тор:

Ири­на Михеева

Вёрст­ка:

Мария Дро­но­ва

Соц­се­ти:

Ана­ста­сия Гаврилова