1. Вступ
У цьому розділі ви дізнаєтесь про креативне програмування, ознайомитесь з інформаційними джерелами, які використовувалися у процесі створення підручника і можуть бути корисними в навчанні. |
Часто програмування сприймається як дуже складний процес, що вимагає надзвичайних інтелектуальних здібностей. Звісно, вивчення програмування вимагає певних зусиль, втім, як і освоєння будь-якого іншого процесу.
Інтерес, мотивація, відкритість до нового - мінімальний набір для того, щоб чомусь навчитися, зокрема навчитися писати код.
Існує також стереотип, що програмування - процес, позбавлений творчості. Це не так. Програмування поєднує в собі елементи інженерії, фундаментальних наук, мистецтва та інших напрямків, у яких без творчого підходу у розв’язанні багатьох проблем важко досягти успіху.
У цьому підручнику ви познайомитесь з креативним програмуванням - програмуванням, метою якого переважно є створення чогось виразного, естетичного замість чогось функціонального.
Креативність (творчість) - це діяльність з використанням уяви для створення чогось якісно нового у світі. Вона «активується» щоразу, коли у вас з’являється можливість обміркувати нову ідею, рішення або підхід. |
Креативне програмування використовує код і обчислювальні процеси, щоб створювати форми мистецтва, і безпосередньо застосовується для створення зображень, анімації, візуального і звукового дизайну, реклами, інтерактивних інсталяцій та багато іншого.
Креативне програмування - це
сучасного художника.Креативне програмування - це процес, заснований на дослідженні, ітерації, рефлексії та відкритті, де код використовується як основний засіб для створення широкого спектра медіаартефактів.
Towards a creativity support tool in Processing. Understanding the needs of creative coders.
Цікавимось
2. Цифрове мистецтво та творчість
У цьому розділі ви дізнаєтесь, як комп’ютерні технології вплинули на мистецтво, яку роль програмування відіграє у творчості та познайомитесь із середовищем розробки Processing IDE .
|
2.1. Цифрове мистецтво
Стрімкий розвиток комп’ютерних технологій дав поштовх до змін у багатьох галузях людської діяльності. Зокрема такі зміни відбулися й у мистецтві.
У всі часи митці намагались удосконалити наявні інструменти для творчості чи створити нові з метою досягнення власних унікальних результатів.
Технології принесли в мистецтво усвідомлення програмного забезпечення як інструмента для створення творів мистецтва і відкрили нову область творчості - цифрове мистецтво.
Використання програмування для створення мистецтва - це практика, яка була започаткована ще в 1960-х роках. Протягом наступних десятиліть відбувалися дослідження програмування для мистецьких цілей.
Починаючи з 1980-х років, експерти-програмісти перевіряли свої навички у змаганнях один проти одного, створюючи «демонстраційні програми» - високотехнологічні візуальні твори.
Із середини 1990-х розпочалися експерименти з аплетами HTML , Shockwave , Flash та Java як із середовищами для креативності.
Справжня революція у цифровому мистецтві сталася із появою цифрових інструментів з відкритим кодом на зразок Processing .
Нині поняття комп’ютерного мистецтва охоплює як твори традиційного мистецтва, перенесені в нове середовище на цифрову основу (наприклад, цифрова фотографія), що імітують первісний матеріальний носій, так і принципово нові види художніх творів, основним середовищем існування яких є комп’ютерне середовище.
Під таке визначення поняття «комп’ютерне мистецтво» добре вписуються цифровий живопис, фрактальне мистецтво, pixel art та інші різновиди цифрового мистецтва.
Переглядаємо Аналізуємо
Технології цифрового мистецтва відкривають принципово новий рівень обробки інформації та інтерактивної взаємодії людини з комп’ютером.
2.1.2. Контрольні запитання
Міркуємо Обговорюємо
-
Поясніть сутність цифрового мистецтва.
-
Назвіть різновиди цифрового мистецтва.
-
Що є інструментами у цифровому мистецтві?
2.1.3. Практичні завдання
Початковий
-
Знайти інформацію про українських митців, які реалізують свої творчі ідеї у цифровому мистецтві.
Середній
-
На основі знайдених матеріалів про музеї цифрового мистецтва у світі й в Україні, розповісти про один із таких музеїв, створивши презентацію.
Високий
-
Відшукати в мережі Інтернет роботи з цифрового мистецтва і визначити, до якого виду цифрового мистецтва їх можна віднести. Результати дослідження подати у формі презентації чи вебсторінки.
2.2. Дизайн та код. Генеративне мистецтво
Дизайн-код у багатьох країнах, наприклад, у Великобританії, існує давно: зведення правил, які визначають зовнішній вигляд Лондона, існують з 1666 року.
У Берліні скрупульозність дизайн-коду дійшла, навіть, до вуличної плитки: різне плиткове покриття позначає транспортні та пішохідні зони.
Дизайн-код - це комплексний підхід до візуального впорядкування та формування естетики зовнішнього вигляду міста, що також переслідує завдання доступності та зручності користування містом та його інфраструктурою. |
Застосування дизайн-коду в містах світу сьогодні стає поширеною практикою.
Дизайн-код може застосовуватися до оформлення будь-чого. Наприклад, дизайн-код для вебсайтів - це документ, в якому прописані стандарти та вимоги до розробки вебресурсів з погляду дизайну та зручності взаємодії з користувачем.
Генеративне мистецтво - це процес алгоритмічного генерування нових ідей, форм, кольорів або візерунків. Спочатку ви створюєте правила, що забезпечують межі процесу створення. Тоді виконавець (комп’ютер або рідше людина) дотримується цих правил для створення нових творів. |
На відміну від традиційних художників, які можуть витрачати дні чи навіть місяці на дослідження однієї ідеї, художники генеративного коду використовують комп’ютери, щоб генерувати тисячі ідей за мілісекунди.
Генеративні художники використовують потужність сучасних комп’ютерів, щоб винайти нову естетику, інструктуючи виконання застосунків в рамках певних художніх обмежень і направляючи процес до бажаного результату.
Такий підхід значно зменшує дослідницьку фазу у мистецтві та дизайні й часто приводить до нових дивовижних та витончених ідей.
Переглядаємо Аналізуємо
2.2.2. Контрольні запитання
Міркуємо Обговорюємо
-
У чому полягає сутність дизайн-коду?
-
Що таке «генеративне мистецтво»?
-
Що таке «генеративні алгоритми»?
2.2.3. Практичні завдання
Початковий
-
Підготувати повідомлення про використання дизайн-коду у містах світу й України.
Середній
-
Відшукати в мережі Інтернет роботи митців, які використовують генеративні алгоритми для створення своїх робіт і створити мінівиставку у формі презентації.
Високий
-
Чи дотримуються дизайн-коду у вашому місті? Створити презентацію чи вебсторінку на цю тему.
2.3. Програмування як середовище для творчості. Мова програмування
Креативні усі. Це частина людської природи й поведінки. Принаймні у кожного є доступ до цього ресурсу. З огляду на це, програмування може бути цікавим та надзвичайно творчим заняттям.
Креативне програмування має набір властивостей, перша з яких пов’язана з особливостями творчого процесу: часто художник починає творити, маючи лише ідею, а не детально розроблений план, і тому результат може бути несподіваним для художника.
Тобто, характерною ознакою креативного програмування є відсутність формалізованого технічного завдання.
Іншою відмітною особливістю креативного програмування є розгляд мови програмування як інструмента для створення індивідуальних художніх творів.
Будь-яка технологія чи мова програмування потенційно можуть бути використані для творчих цілей. Спробуємо об’єднати творчість із цікавим процесом програмування за допомогою Processing
.
2.3.1. Що таке Processing?
Processing - мова програмування і середовище розробки для вивчення програмування в контексті цифрового мистецтва - створення зображень, анімації та інтерактивної графіки.
Processing
, як мова програмування, використовує синтаксис на основі мови програмування Java з деякими спрощеннями.
Технічно, Processing - це надбудова над Java з великою колекцією бібліотек для графіки, відео і звуку.
|
Якщо розглядати Processing
як середовище програмування, то Processing
, крім того, що надає середовище для написання і виконання коду, дає можливість використовувати альтернативні інтерфейси для програмування іншими мовами, зокрема JavaScript і Python .
За допомогою Processing
можна швидко реалізувати якусь ідею для графіки, анімації, звуку тощо. Водночас можна перетворити сам процес програмування в експеримент і отримувати цікаві результати таких експериментів.
Processing
дає можливість створювати не лише цікаві проєкти, а й розпочати програмувати тим, хто ніколи до цього не програмував.
2.3.2. З історії створення Processing
Processing
є нащадком проєкту Design By Numbers (DBN) , що був впливовим експериментом у контексті викладання програмування, започаткованим у медіалабораторії MIT (Массачусетський технологічний інститут) у 1990-х роках.
Мову програмування DBN
і програмне забезпечення для вивчення програмування розробив Джон Маеда (американський дизайнер, родом з Японії, фахівець в області комп’ютерних технологій, письменник) разом зі своїми студентами. Це дозволило дизайнерам, художникам та й усім охочим легко розпочати програмувати.
Design By Numbers
вже не є активним проєктом, але він повпливав на багато інших проєктів, спрямованих на те, щоб зробити програмування більш доступним для нетехнічних професій.
Одним із таких проєктів, що досяг міжнародного успіху, став Processing
.
Проєкт був створений Кейсі Різом та Беном Фраєм, студентами Джона Маеди. Для своєї роботи вони взяли за основу DBN
.
Розробка Processing
почалася офіційно навесні 2001 року, коли Кейсі та Бен обговорили питання щодо API та набору функцій, а перші кілька версій Processing
(випуски 0001
, 0003
та 0005
) були використані в серпні 2001 року в Університеті мистецтв Мусашино в Токіо, Японія.
Processing
широко використовується художниками та дизайнерами у всьому світі, а також викладачами, які навчають основам програмування у школах мистецтв та дизайну.
Processing розповсюджується під відкритою ліцензією і є безплатним для завантаження та використання програмним забезпеченням.
|
Переглядаємо Аналізуємо
2.3.4. Контрольні запитання
Міркуємо Обговорюємо
-
Що таке
Processing
? -
Які мови програмування підтримує
Processing
, якщо розглядатиProcessing
як середовище програмування? -
Яку мову програмування використовує онлайн-середовище OpenProcessing ?
-
Чи є програмування творчим процесом? Обґрунтуйте свою відповідь.
2.3.5. Практичні завдання
Початковий
-
Розглянути прототипи застосунків , створених за допомогою
Processing
. Ознайомитись з онлайн-середовищем openprocessing.org . Натиснути наCreate a Sketch
у вікні онлайн-середовищаOpenProcessing
і виконати запропонований код застосунку.
Середній
-
Опрацювати на свій вибір один із покрокових посібників , запропонованих спільнотою
OpenProcessing
.
Високий
-
За зразком створити власний покроковий навчальний посібник як чудовий спосіб познайомити будь-кого із концепціями кодування і вашими ідеями.
2.4. Особливості середовища розробки. Структура програмного проєкту
Processing
- це мова програмування, яка базується на мові Java
.
За допомогою мови програмування записують початковий код, запускають його і зневаджують у середовищі програмування.
Для Processing
таким середовищем програмування є Processing IDE
.


2.4.1. Processing IDE
Середовище програмування Processing IDE можна завантажити з офіційного сайту processing.org для платформ Windows , Linux і macOS .
|
Для встановлення Processing IDE
на комп’ютер необхідно завантажити з офіційного сайту файл-архів відповідної розрядності для вашої операційної системи, розпакувати архів і запустити файл processing.exe
(для Windows
) або install.sh
(для Linux
).

В меню Ctrl+Shift+O) присутні багато прикладів проєктів, створених тою мовою програмування, режим якої зараз обраний. Якщо відкрити будь-який із цих прикладів, початковий код проєкту відкриється у новому вікні середовища Processing IDE
. Проєкт можна переглянути, натиснувши на кнопку запуску.
Окрім того, пункт меню Файл містить стандартні команди відкривання, зберігання, закривання і створення проєкту.
Проєкт Processing
являє собою каталог з необхідними файлами для запуску застосунку, що розробляється.
У каталозі data
каталогу проєкту можуть розміщуватися додаткові файли проєкту, наприклад, файли зображень, відео чи аудіоформатів.
При встановленні альтернативних режимів роботи у Processing (пункт Додати режим... у списку вибору режимів), відповідний пакунок файлів для роботи в цьому режимі розміщується у Windows по шляху С:\Users\user\Documents\Processing\modes\ . Для Linux цей шлях буде /home/teacher/sketchbook/modes . Назви user і teacher - імена користувачів в операційних системах відповідно.
|
Цікавимось
sketch.properties
- ще один із файлів, який створюється автоматично у проєкті й містить інформацію про поточний альтернативний режим Processing IDE
.
Застосунок, створений у Processing , має назву скетч або ескіз. Власне, спосіб мислення, за якого випробовується та оцінюється велика кількість ідей, називається скетчингом. Спочатку створюється загальний ескіз, який у процесі роботи вдосконалюється, отримує додатковий функціонал, інтерактивність і поступово наближається до кінцевого результату.
|
Розділ меню Редагування традиційно використовується для керування редагуванням початкового коду.
Розділ Ескіз об’єднує команди для роботи з проєктом: запуск, зупинка і Режим презентації.
Режим презентації (лише для Java і Python , Ctrl+Shift+R ) запускає застосунок на тлі повноекранної заливки сірого кольору. Цей режим зручно використовувати для демонстрації роботи готового застосунку. Для виходу із цього режиму, необхідно натиснути на кнопку stop у лівому нижньому куті екрану або натиснути на кнопку Esc на клавіатурі.
|
Також, розділ Ескіз містить команди з додавання файлів (наприклад, зображень) у проєкт, відкривання каталогу проєкту і роботі із додатковими бібліотеками.
Розділ Інструменти містить корисні утиліти:
-
Створити шрифт...,
-
Вибрати колір...,
-
та інші, які можна додати за допомогою
.
Дані утиліти відрізняються від бібліотек тим, що бібліотеки використовуються у застосунках безпосередньо - приєднуються у початковий код, а утиліти розв’язують додаткові задачі.
Команда Генератор відео із розділу Інструменти використовується для створення відеоролика із набору файлів зображень.
Розділ Довідка (Help
) містить покликання на навчальні сайти та документацію.
2.4.2. p5.js - інтерпретація Processing для вебу
Як вже було зазначено вище, написання ескізів можна здійснювати з використанням спеціальної бібліотеки p5.js
і мови програмування JavaScript
, а роботу створених застосунків візуалізувати у вікні вебпереглядача.
p5.js - це безплатна бібліотека JavaScript з відкритим початковим кодом для креативного програмування з акцентом на те, щоб зробити кодування доступним для художників, дизайнерів, викладачів, початківців та усіх охочих.
|
Як середовище розробки для використання p5.js
і написання коду мовою JavaScript
, можна обрати:
-
середовище розробки
Processing IDE
у режиміp5.js
; -
OpenProcessing - онлайн-середовище з підтримкою
JavaScript
іp5.js
; -
p5.js Web Editor - онлайн-редактор з підтримкою
JavaScript
іp5.js
та інтерфейсом українською.
Для зручної роботи з колекціями створених ескізів рекомендується зареєструватися на сайтах онлайн-редакторів. |
Для роботи з кодом ви можете використовувати редактор коду, встановлений на вашому комп’ютері, або обрати редактор зі списку, поданому в Додатку A. |
Цікавимось
2.4.3. Ресурси
Корисні джерела
2.4.4. Контрольні запитання
Міркуємо Обговорюємо
-
Які можливості надає середовище розробки
Processing IDE
? -
Яку назву має застосунок, створений за допомогою
Processing
? -
Які режими роботи підтримує
Processing IDE
? На скільки відрізняється структура проєкту в залежності від обраного режиму? -
Для чого використовується бібліотека
p5.js
? -
На вашу думку, яке середовище розробки для написання ескізів мовою
JavaScript
з використаннямp5.js
є оптимальним?
2.4.5. Практичні завдання
Початковий
-
Увімкнути альтернативні режими (
Python
,JavaScript
) вProcessing IDE
. -
У
Processing IDE
в режиміJava
відкрити приклад із меню (Ctrl+Shift+O) і далі . Виконати код із прикладу. -
Створити нове вікно у середовищі
Processing IDE
(Ctrl+N). Обрати режимPython
і довільний приклад та виконати його код. -
Створити нове вікно у середовищі
Processing IDE
(Ctrl+N). Обрати режимJavaScript
і довільний приклад та виконати його код.
Середній
-
У
Processing IDE
в режиміJava
відкрити приклад із меню (Ctrl+Shift+O) і далі . Запустити приклад в режимі Режим презентації. -
Створити нове вікно у середовищі
Processing IDE
(Ctrl+N). Обрати режимJavaScript
і виконати код, поданий нижче.
function setup() {
createCanvas(400, 400);
}
function draw() {
if (mouseIsPressed) {
fill(0);
} else {
fill(255);
}
ellipse(mouseX, mouseY, 80, 80);
}
-
Розглянути прототипи застосунків , створених за допомогою
p5.js
. -
Відкрити онлайн-редактор p5.js Web Editor і запустити код одного із застосунків з попереднього завдання.
Високий
-
У
Processing IDE
в режиміJava
відкрити приклад із меню (Ctrl+Shift+O) і далі . Експортувати цей проєкт як застосунок для вашої операційної системи. Запустити застосунок і переконатися, що він працює окремо від середовищаProcessing IDE
.
3. Графічні побудови та взаємодії
У цьому розділі ви ознайомитеся з мовою програмування JavaScript , основами роботи з бібліотекою p5.js й навчитеся створювати власні графічні застосунки.
|
3.1. Основні елементи мови програмування JavaScript. Використання змінних і виразів
Мова JavaScript була створена в 1995 році Бренданом Айком зі спеціальною метою - додати динамічну поведінку в документи, які відображаються вебпереглядачами.
З тих часів мова JavaScript
значно еволюціонувала. Вебпереглядач, як і раніше, залишається найпоширенішим середовищем виконання коду JavaScript
на боці клієнта, а поява Node.js дала можливість виконувати JavaScript
-скрипти на боці сервера та відправляти користувачеві результат їхнього виконання.
Сьогодні мова JavaScript
перетворилася на мову загального використання з великою спільнотою розробників і нині є однією з найпопулярніших.
Цікавимось
3.1.1. Імена, змінні, константи, коментарі
Мова програмування складається з елементів мови та правил, які регламентують написання застосунків цією мовою.
Мова програмування JavaScript
має такі елементи:
-
алфавіт;
-
зарезервовані слова;
-
змінні та константи;
-
коментарі.
Алфавіт складається з великих (A-Z
) і малих (a-z
) літер латинського алфавіту, цифр (0-9
), спеціальних символів (+
, -
, *
, /
, =
, >
, <
, ;
, ===
та інші).
Зарезервовані слова мають спеціальне призначення і використовуються в конструкціях мови програмування.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Мова JavaScript
передбачає використання змінних і констант, які можна уявити у вигляді контейнерів для зберігання значень різних типів даних - чисел, рядків тощо.
Змінні не є значеннями, вони є контейнерами для значень. |
Змінні та константи мають імена (за нашою аналогією - наліпки з унікальними назвами на контейнерах), які формуються за допомогою алфавіту мови програмування за певними правилами.
Мова JavaScript є регістрозалежною (розрізняються великі та малі літери в іменах). Наприклад, зарезервоване слово for потрібно писати лише як for , але не For чи FOR . Аналогічно, name , Name , NAME - три різні змінні.
|
Зарезервовані слова не можна використовувати як імена для власних змінних чи констант! |
Змінна може використовуватися в одному застосунку багато разів і змінювати своє значення під час його виконання.
На відміну від змінних, спроба звернення до константи на зміну значення призведе до помилки.
Імена змінних і констант в JavaScript - це покликання на їх значення в пам’яті. У разі змінних - покликання на читання і запис значення, у разі констант - лише для читання значення.
|
Цікавимось
Щоб перевірити ім’я вашої змінної чи константи на коректність, скористайтеся сайтом JavaScript variable name validator . |
Коментарі - це текстові пояснення до написаного початкового коду.
Коментарі записуються в один рядок за допомогою //
або у кілька рядків, використовуючи конструкцію /* */
, і не обробляються під час виконання коду.
// однорядковий коментар
/*
коментар
у
кілька
рядків
*/
Інтерпретатор JavaScript ігнорує пропуски, які зустрічаються у тексті застосунку, але завдяки їх використанню можна робити відступи у коді, надаючи йому зручний для читання і розуміння вигляд.
|
3.1.2. Файл ескізу sketch.js
Розглянемо процес написання ескізу на простому прикладі - створення еліпса.
У файл ескізу sketch.js
запишемо такий код:
function setup() {
createCanvas(200, 200); (1)
}
function draw() {
ellipse(100, 100, 80, 80); (2)
}
1 | Функція createCanvas() створює полотно та встановлює його розміри в пікселях по горизонталі й вертикалі. У цьому разі розмір полотна 200x200 пікселів. |
2 | Функція ellipse() малює на екрані еліпс (овал). За стандартним налаштуванням перші два параметри встановлюють розташування центру еліпса, а третій і четвертий - ширину та висоту фігури. Якщо висота не вказана, значення ширини використовується як для ширини, так і для висоти. Еліпс з однаковою шириною та висотою - це коло. |
У JavaScript в кінці кожної інструкції потрібно ставити символ ; (крапка з комою). Хоча це правило у JavaScript є необов’язковим, зокрема, якщо інструкції записані у різних рядках, без розділювача ; інтерпретатор JavaScript в окремих випадках може неправильно трактувати ваш код.
|
Цікавимось
Отже, поданий вище код можна прочитати так:
створи полотно, розміром 200x200
пікселів і намалюй еліпс, центр якого знаходиться на відстані 100
пікселів від лівої межі полотна і 100
пікселів від верхньої межі полотна (центр еліпса), з шириною і висотою у 80
пікселів.
У 2D -режимі (увімкнений за стандартним налаштуванням) початок координат (точка, від якої відраховується кількість пікселів по горизонталі праворуч і по вертикалі вниз) має координати (0, 0) і розміщується у верхньому лівому куті полотна.
|
Цікавимось
Якщо код написаний без помилок, то у вебпереглядачі буде створено полотно, на якому буде намальоване коло, хоча в коді використовується ellipse() . Чому так?
Так відбулося завдяки тому, що у функції ellipse()
було вказано однакові значення висоти (80
) та ширини (80
) відповідно.
Вправа 1
Відредагувати код для функції ellipse()
, щоб отримати еліпс.
Додамо до нашого коду ще кілька команд і коментарі з поясненнями (рядки з коментарями починаються з //
):
function setup() {
// створення полотна
createCanvas(200, 200);
// колір тла полотна
background(0, 0, 0); // Black (1)
}
function draw() {
// колір заливки еліпса
fill(244, 240, 125); // Canary (2)
// вимкнення межі еліпса
noStroke(); (3)
// малювання еліпса
ellipse(100, 100, 80, 80);
}
1 | Функція background() встановлює колір для тла полотна. |
2 | Функція fill() встановлює колір, який використовується для зафарбовування фігур, що створюються після виклику цієї функції (функція noFill() вимикає зафарбовування фігур). |
3 | Функція noStroke() вимикає нанесення обведення (контуру, межі) для фігур (функція stroke() створює протилежний ефект - встановлює колір межі для фігур). |
У зразках коду навпроти виклику функцій для роботи з кольором (fill() , stroke() ) за допомогою коментарів вказані назви кольорів.
|
Щоразу, коли код всередині функції draw()
запускається, створюється новий кадр. Кожен наступний такий кадр змінює те, що є на полотні у попередньому кадрі. Так зміна кадрів створює на екрані ефект руху, анімацію.
Функція background() , як правило, використовується у функції draw() для очищення полотна на початку кожного кадру, але її можна використовувати всередині setup() , щоб встановити тло на першому кадрі анімації або якщо тло потрібно встановити лише один раз.
|
Цікавимось
Вправа 2
Намалювати еліпс з власними значеннями кольорових складових для функцій background()
і fill()
. Якщо треба, змінити режим кольору за допомогою функції colorMode()
.
3.1.3. Використання змінних і виразів
Використаємо у нашому коді змінні. Збережемо ширину і висоту полотна як змінні з назвами canvasWidth
і canvasHeight
відповідно.
Для створення змінних в JavaScript
використовується зарезервоване слово let
, далі вказується ім’я змінної, потім записується оператор присвоєння =
і значення змінної.
Значення для змінної у JavaScript необов’язково присвоювати при її оголошенні. Присвоєння значення може відбуватися пізніше (нижче у коді), але перед тим, як змінну використовуватимуть.
|
Звичайно, canvasWidth
і canvasHeight
можна оголосити як константи, якщо їхні значення не планується змінювати в процесі виконання застосунку. У цьому разі для оголошення констант необхідно використати зарезервоване слово const
з одночасним присвоєнням оголошеним константам їх значень.
Отож, зі змінними код матиме наступний вигляд:
function setup() {
// оголошення локальних змінних і присвоєння їм значень
let canvasWidth = 200;
let canvasHeight = 200;
// створення полотна
createCanvas(canvasWidth, canvasHeight);
// колір тла полотна
background(0, 0, 0); // Black
}
function draw() {
// колір заливки еліпса
fill(244, 240, 125); // Canary
// вимкнення межі еліпса
noStroke();
// малювання еліпса
ellipse(100, 100, 80, 80);
}
Змінні можуть бути локальними (оголошуються і, відповідно, використовуються в межах блоку коду), так і глобальними, коли оголошуються і є доступні для усього застосунку.
Зарезервоване слово let
дозволяє оголосити локальну змінну з областю видимості, обмеженою поточним блоком коду - такі змінні «працюють» всередині блоку і недоступні поза ним.
Здебільшого необов’язково оголошувати окрему змінну для полотна, яке створюється за допомогою функції createCanvas() . Функція createCanvas() автоматично встановлює глобальну змінну canvas , яку можна використовувати в інших частинах коду. Однак, якщо необхідно створити більше одного полотна і керувати їхніми параметрами, варто створити окремі змінні та пов’язати кожну з них зі своїм полотном.
|
У коді вище, змінні canvasWidth
і canvasHeight
є локальними й недоступними поза межами функції setup()
. Спроба використати ці змінні поза межами функції setup()
, наприклад, у функції draw()
, викличе помилку на зразок canvasWidth is not defined
(змінна canvasWidth
не визначена).
Така сама помилка з’явиться у разі, коли ми звертаємось до змінної, якої взагалі не існує.
Змінна, яка існує, але не має значення, є порожнім контейнером. Коли ми звертаємось до такої змінної, ми отримуємо значення undefined .
|
Змінна може бути оголошена лише один раз. Повторне оголошення тієї ж змінної викличе помилку. При створенні константи важливо відразу присвоювати їй значення, інакше це викличе помилку. |
Імена, які дають змінним і константам, повинні легко читатися, мати зрозумілий сенс і говорити про те, які дані в пам’яті зберігаються під цими іменами. Наприклад: userName
, canvasWidth
, footerText
, sketchWrapper
і т. д.
Використання різних змінних для різних значень є хорошим стилем програмування.
Вправа 3
Виконати код для друку значень змінних всередині консолі вебпереглядача за допомогою JavaScript
-функції console.log()
і переглянути результати (Ctrl+Shift+I).
function setup() {
// оголошення локальних змінних і присвоєння їм значень
let canvasWidth = 200;
let canvasHeight = 200;
// створення полотна
createCanvas(canvasWidth, canvasHeight);
// колір тла полотна
background(0, 0, 0); // Black
// виведення в консоль
console.log("Ширина полотна:", canvasWidth, "пікселів");
console.log("Висота полотна:", canvasHeight, "пікселів");
}
function draw() {
// колір заливки еліпса
fill(244, 240, 125); // Canary
// вимкнення межі еліпса
noStroke();
// малювання еліпса
ellipse(100, 100, 80, 80);
}
Запишемо код для створення зображення трьох еліпсів і використаємо для цього змінні.
function setup() {
// оголошення локальних змінних і присвоєння їм значень
let canvasWidth = 360;
let canvasHeight = 120;
// створення полотна
createCanvas(canvasWidth, canvasHeight);
// колір тла полотна
background(235, 235, 235); // Platinum
}
function draw() {
// оголошення локальних змінних і присвоєння їм значень
let y = 60;
let d = 80;
// колір заливки еліпсів
fill(255, 255, 255); // White
// вимкнення меж еліпсів
noStroke();
// малювання еліпсів
ellipse(75, y, d, d);
ellipse(175, y, d, d);
ellipse(275, y, d, d);
}
Вправа 4
Запустити код для створення трьох еліпсів, змінивши значення змінних y
і d
.
Якщо не використовувати змінні y
і d
, то необхідно тричі змінити значення координати y
і шість разів змінити значення d
. У підсумку, завдяки змінним код можна легко змінювати в одному місці, а не шукати значення для зміни вручну у всьому коді застосунку.
Значення змінних можна змінювати також в процесі виконання коду. Продемонструємо, як це реалізується на практиці, використовуючи код нижче.
function setup() {
// оголошення локальних змінних і присвоєння їм значень
let canvasWidth = 300;
let canvasHeight = 200;
// створення полотна
createCanvas(canvasWidth, canvasHeight);
// колір тла полотна
background(235, 235, 235); // Platinum
}
function draw() {
// оголошення локальних змінних і присвоєння їм значень
let x = 20;
let y = 30;
let h = 40;
// малювання прямокутників
rect(x, y, 150, h);
y = y + h;
rect(x + 150, y + h, 130, h);
x = 0;
rect(x, y + h * 2, 100, h);
}
У даному коді використана функція rect() , яка малює прямокутник на полотні.
За стандартним налаштуванням перші два параметри функції rect() встановлюють розташування лівого верхнього кута, третій - ширину, а четвертий - висоту. Спосіб інтерпретації цих параметрів може бути змінений за допомогою функції rectMode() .
|
Символи +
, -
, *
і =
в коді називаються операторами, а значення змінних - операндами. Вказані оператори визначають прості арифметичні операції та операцію присвоєння =
, за допомогою якої змінним присвоюються певні значення.
Оператор |
Арифметична операція |
|
додавання |
|
віднімання |
|
множення |
|
ділення |
|
остача від ділення |
|
піднесення до степеня |
Значення змінних (операндів) в JavaScript
завжди належать до даних певного типу.
Цікавимось
Якщо оператори записують між двома значеннями, то отримуємо вираз.
Вираз, який визначає одну операцію та її операнди, у мовах програмування називають командою або інструкцією.
Текст коду застосунку зазвичай складається з послідовності інструкцій.
Цікавимось
Цікавимось Додатково
Звичайно, порядок виконання операцій у виразах підпорядковується певним правилам, які визначають пріоритет операторів, тобто, які оператори будуть виконані насамперед.
У JavaScript
багато операторів і кожен з них має відповідний номер пріоритету. Той, у кого це число більше, - виконується найперше. Якщо пріоритет однаковий, то порядок виконання - зліва направо. Пріоритет унарних операторів вище, ніж відповідних бінарних, а дужки у виразах є важливішими будь-якого пріоритету.
Пріоритет | Назва | Позначення |
---|---|---|
17 |
унарний плюс |
|
17 |
унарний мінус |
|
16 |
піднесення до степеня |
|
15 |
множення |
|
15 |
ділення |
|
13 |
додавання |
|
13 |
віднімання |
|
3 |
присвоєння |
|
Деякі арифметичні операції настільки часто використовуються у програмуванні, що були придумані способи швидкого запису цих операцій.
Наприклад, у JavaScript
ви можете складати у змінну або віднімати зі змінної за допомогою всього одного оператора:
x += 5; // еквівалентно x = x + 5
y -= 10; // еквівалентно y = y - 10
Вправа 5
Додати у код, який використовується для малювання прямокутників, функції для встановлення різних кольорів зафарбовування прямокутників.
Окрім вбудованих функцій, бібліотека p5.js
використовує системні або вбудовані змінні.
Наприклад, mouseX і mouseY є системними змінними, які зберігають координати розташування вказівника миші на полотні відповідно.
function setup() {
// оголошення локальних змінних і присвоєння їм значень
let canvasWidth = 500;
let canvasHeight = 400;
// створення полотна
createCanvas(canvasWidth, canvasHeight);
// колір тла полотна
background(52, 89, 149); // Bdazzled Blue
}
function draw() {
fill(248, 237, 235); // Seashell
noStroke();
// намалювати коло
circle(mouseX, mouseY, 25);
}
У даному коді використана функція circle() , яка малює коло на полотні: перші два параметри встановлюють розташування центру кола, а третій встановлює діаметр кола.
Вправа 6
За допомогою функції console.log()
відстежити (надрукувати) в консолі вебпереглядача (Ctrl+Shift+I), як змінюються значення системних змінних mouseX
і mouseY
.
Цікавимось
За стандартним налаштуванням функція draw()
викликається максимум 60
разів за секунду, тобто, код всередині цієї функції виконується 60
разів за секунду. Переконаємось у цьому, використавши функцію frameRate() , яка дозволяє визначити кількість кадрів, які відображатимуться щосекунди.
function setup() {
// оголошення локальних змінних і присвоєння їм значень
let canvasWidth = 200;
let canvasHeight = 200;
// створення полотна
createCanvas(canvasWidth, canvasHeight);
// колір тла полотна
background(210);
}
function draw() {
console.log(frameRate());
}
Після запуску ескізу в консолі вебпереглядача можна спостерігати значення частоти кадрів, наближені до 60
(числа з рухомою крапкою). Як бачимо, функція draw()
виконується максимум 60
разів на секунду. Чи більше це значення, тим зміна кадрів є плавнішою. У разі, коли частота кадрів низька, зміна кадрів може виглядати нерівною.
Виведемо частоту кадрів на наше полотно.
У наступному прикладі додатково використаємо функції для роботи з текстом:
-
text() - для створення на полотні текстового напису;
-
textSize() - для встановлення розміру тексту;
-
textAlign() - для вирівнювання тексту на полотні.
function setup() {
// оголошення локальних змінних і присвоєння їм значень
let canvasWidth = 200;
let canvasHeight = 200;
// створення полотна
createCanvas(canvasWidth, canvasHeight);
// вирівнювання тексту на полотні
textAlign(CENTER, CENTER);
}
function draw() {
background(210);
fill(237, 34, 93); // Paradise Pink
// розмір тексту
textSize(24);
// оголошення змінної fps і присвоєння їй значення з рухомою крапкою функції frameRate(), приведеного до цілого значення за допомогою JavaScript-функції parseInt()
let fps = parseInt(frameRate(), 10);
// виведення тексту
text("frameRate: " + fps, width / 2, height / 2);
}
У коді використані функції JavaScript : parseInt() , яка обробляє рядковий аргумент і повертає число з вказаною основою системи числення; якщо цей аргумент не є рядком, тоді він буде перетворений на рядок за допомогою функції toString() .
|
Переглядаємо Аналізуємо
Використаємо frameCount
- змінну, яка веде підрахунок кількості викликів функції draw()
, разом із функцією frameRate()
так, щоб frameCount
змінювалася повільніше у разі нижчого значення frameRate()
.
function setup() {
// оголошення локальних змінних і присвоєння їм значень
let canvasWidth = 200;
let canvasHeight = 200;
// створення полотна
createCanvas(canvasWidth, canvasHeight);
// вирівнювання тексту на полотні
textAlign(CENTER, CENTER);
frameRate(5); // створення повільної анімації
}
function draw() {
background(210);
fill(237, 34, 93); // Paradise Pink
// розмір тексту
textSize(24);
// виведення значення frameCount
text("frameCount: " + frameCount, width / 2, height / 2);
}
Переглядаємо Аналізуємо
Вправа 7
Змінити значення frameRate()
у коді для повільної анімації та розмістити напис в іншому місці полотна.
Розглянемо ще один приклад використання змінних і вбудованої функції random()
.
Цікавимось
function setup() {
// оголошення локальних змінних і присвоєння їм значень
let canvasWidth = 500;
let canvasHeight = 400;
// створення полотна
createCanvas(canvasWidth, canvasHeight);
frameRate(5); // створення повільної анімації
background(250);
}
function draw() {
// оголошення локальних змінних і присвоєння їм значень
let r = random(255); // червона складова
let g = random(255); // зелена складова
let b = random(255); // синя складова
let a = random(255); // прозорість
let d = random(20); // діаметр кола
let x = random(width); // x-координата центра кола
let y = random(height); // y-координата центра кола
// використання локальних змінних
noStroke();
fill(r, g, b, a);
ellipse(x, y, d, d);
}
У наведеному прикладі використовуються змінні для зберігання трьох складових кольору і прозорості, діаметра і координат кола на полотні. У даному застосунку функція random()
дозволяє створювати кола, які визначаються випадковими значеннями цих змінних.
Переглядаємо Аналізуємо
Вправа 8
Змінити код для створення випадкових кольорових кіл, щоб замість кіл будувались еліпси різних розмірів.
Змінні також відіграють важливу роль у моделюванні руху на полотні. Як відомо, код всередині функції draw()
виконується постійно, повторюючись аж до зупинки застосунку. Щоразове повторення виконання коду створює окремі кадри, які змінюються один за одним. Кожен наступний кадр змінює те, що було на попередньому, а зміна кадрів загалом створює на полотні ефект руху.
Розглянемо код застосунку, який створює ілюзію горизонтального руху на полотні, в якому коло переміщується горизонтально зліва направо завдяки зміні значення змінної x
у черговому кадрі:
let x = 0; (1)
let velocity = 0.5; (2)
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
circle(x, height / 2, 50); (3)
x += velocity; (4)
}
1 | Ініціалізація змінної x зі значенням 0 - це початкове значення x -координати центру кола. |
2 | Ініціалізація змінної velocity зі значенням приросту для x -координати у кожному наступному кадрі. Змінюючи значення velocity можна керувати швидкістю руху фігури на полотні. |
3 | Малювання кола з діаметром 50 у точці з x -координатою, що має значення x , і y -координатою, що дорівнює height / 2 (значення цієї координати є однаковим для усіх кадрів, тому коло по вертикалі завжди знаходиться у центрі полотна). |
4 | Збільшення значення змінної x на величину velocity у кожному наступному кадрі. |
Переглядаємо Аналізуємо
Значення x постійно зростає на величину velocity , коли застосунок виконується, тому через певний час коло сховається за правою межею полотна.
|
Вправа 9
Змінити код із попереднього прикладу для створення вертикального руху на полотні.
3.1.4. Ресурси
Корисні джерела
3.1.5. Контрольні запитання
Міркуємо Обговорюємо
-
Які складові мови програмування
JavaScript
? -
Яких правил необхідно дотримуватись при виборі імен для змінної чи константи? Назвіть коректні й некоректні приклади таких імен.
-
Які функції обов’язково присутні у файлі ескізу
sketch.js
? Які особливості роботи цих функцій? -
Як правильно оголосити змінну чи константу в
JavaScript
? -
Чи правильне таке твердження: у файлі ескізу
sketch.js
змінну чи константу можна оголосити у будь-якому місці. Обґрунтуйте свою відповідь. -
Для чого використовується функція
console.log()
? -
Що таке «тип даних»? Які прості типи даних є в
JavaScript
? -
Вказати тип даних
JavaScript
для оголошених змінних і констант:
let count = 0;
const letter = "A";
const startPoint = 5;
let y = 81.19;
let happy = true;
let resultScore = 40;
-
Коротко опишіть призначення поданих функцій бібліотеки
p5.js
за назвою:ellipse()
,background()
,fill()
,noStroke()
,stroke()
,rect()
,circle()
,frameRate()
,text()
,textSize()
,textAlign()
.
-
Яких значень набувають системні змінні
mouseX
іmouseY
?
3.1.6. Практичні завдання
Початковий
-
Доповнити код для малювання кола: вказати розміри полотна, оголосити глобальні змінні, які вже використовуються у коді, і присвоїти змінним значення.
function setup() {
createCanvas(..., ...);
background(255);
}
function draw() {
stroke(0);
fill(r, g, b);
ellipse(x, y, d, d);
}
-
Встановити на свій вибір колір тла полотна, використовуючи значення кольорів
RGB
.
Середній
-
Намалювати коло з центром в точці
(100, 100)
та радіусом60
. Зафарбувати фігуру кольором на свій вибір. -
Намалювати еліпс з центром в точці
(120, 100)
шириною35
і висотою25
. Зафарбувати фігуру кольором і встановити колір межі фігури. Кольори обрати на свій вибір. -
Намалювати прямокутник шириною
80
і висотою30
, лівий верхній кут якого має координати(40, 30)
. Зафарбувати фігуру кольором і встановити колір межі фігури. Кольори обрати на свій вибір. -
Намалювати квадрат зі стороною
75
, лівий верхній кут якого має координати(30, 20)
. Зафарбувати фігуру кольором, встановити колір і товщину межі фігури. Кольори обрати на свій вибір.
Високий
-
Створити застосунок, в якому у різних точках полотна малюється коло щоразу іншого розміру. Очікуваний результат представлений у демонстрації.
-
Використовуючи код з попереднього завдання, отримати результат представлений у демонстрації.
-
Змінити код з попереднього завдання, щоб колір тла змінювався випадковим чином. Очікуваний результат представлений у демонстрації.
Екстремальний
-
Створити застосунок, в якому щосекунди малюється
60
випадкових кольорових кіл. Очікуваний результат представлений у демонстрації.
-
Створити застосунок, в якому відбувається рух кола з лівого верхнього кута у правий нижній кут полотна як представлено у демонстрації.
3.2. Полотно, пікселі, координати, кольори
Ескіз створюється у вікні певного розміру, яке називається полотном.
3.2.1. Система координат полотна
Полотно і його розміри вказуються у функції setup()
за допомогою функції createCanvas()
, яка отримує значення розмірів полотна у пікселях і викликається один раз при запуску застосунку.
function setup() {
createCanvas(400, 300);
}
Задані значення розмірів полотна містяться у системних змінних width і height відповідно. Щоб встановити розміри полотна на увесь екран, використовуються відповідно системні змінні displayWidth і displayHeight . |
У двовимірному режимі (режим стандартного налаштування) кожен піксель полотна має своє позиціювання на екрані, яке визначається двома координатами у системі координат, що схожа на прямокутну систему координат на площині:
-
x
-координата - це відстань, від лівої межі полотна; -
у
-координата - це відстань від верхньої межі.
Координати точки в прямокутній системі координат на площині й пікселя у системі координат полотна визначаються двома значеннями та записуються (x, y)
.

Наприклад, якщо розміри полотна 400x400
пікселів, то координати верхнього лівого пікселя будуть (0, 0)
, а нижнього правого - (399, 399)
, оскільки рахунок пікселів починається з 0
, а закінчується на 399
. Тому, щоб побачити повністю намальовану точку в нижньому правому куті полотна, потрібно використовувати координати (width - 1, height - 1)
.
У 2D -режимі (режим стандартного налаштування) початок координат (0, 0) розміщується у верхньому лівому куті полотна. У режимі 3D (третім аргументом у виклику функції createCanvas() є значення WEBGL ), початок координат розміщується в центрі полотна.
|
Щоб краще зрозуміти, як відбувається графічна побудова в системі координат полотна у 2D
-режимі, використаємо у ролі тла полотна піксельну сітку - графічний файл, розміром 1000x1000
пікселів, розкреслений у клітинку з кроком у 100
пікселів.

Тепер під’єднаємо файл сітки до ескізу, використавши функцію loadImage() .
У разі використання середовища Processing IDE , створіть каталог data у каталозі вашого ескізу і скопіюйте у нього файл із зображенням сітки. Шлях до зображення у функції loadImage() повинен бути відносним до HTML -файлу ескізу.
|
let grid; (1)
function preload() { (2)
grid = loadImage("grid_1000.png"); (3)
}
function setup() {
createCanvas(1000, 1000);
strokeWeight(3);
image(grid, 0, 0); (4)
}
function draw() {
stroke(0, 233, 245); // Electric Blue
line(100, 100, 400, 300); (5)
}
1 | Оголошуємо змінну grid , яка буде вказувати на завантажений графічний файл. |
2 | Щоб переконатися в тому, що зображення повністю завантажено, використаємо функцію preload() . |
3 | Усередині preload() функція loadImage() завантажує графічний файл і призначає його змінній з назвою grid . Тут варто бути уважним і правильно прописати шлях до зображення. |
4 | Функція image() розміщує завантажене зображення grid на полотні в точці з координатами (0, 0) (у початку системи координат полотна). |
5 | Малюємо лінію на полотні. |

Використовуйте сітку, щоб за координатами розуміти, де саме на полотні буде намальований об’єкт. |
Вправа 10
Створити полотно і намалювати точки у різних кутах полотна і на полотні (відобразити пікселі). Розмістити точки поруч, щоб отримати горизонтальні або вертикальні лінії. Використати зразок коду за основу ескізу.
function setup() {
createCanvas(400, 300);
}
function draw() {
background(220);
point(200, 150);
}
Ескіз містить функцію point() , яка малює точку розміром в один піксель за переданими координатами точки. |
3.2.2. Колірні моделі
Важливість кольорових і світлових ефектів в роботі сучасних цифрових художників важко переоцінити.
Для створення таких ефектів використовують різні системи опису кольорів - колірні моделі.
В комп’ютерній графіці колір створюється від світіння пікселів на екрані. |
Для встановлення кольорів і прозорості тла, меж або фігур бібліотека p5.js
використовує системи RGB (RGBA) і HSB.
Щоб змінити параметри кольору в ескізі, використовують функції:
Для опису кольорів у p5.js
використовують рядки, що містять значення кольорів, записані різними способами:
-
background("rgb(120, 255, 15)");
- модельRGB
(колірChartreuse Web
); -
background("rgba(0, 255, 0, 0.25)");
- модельRGBA
(колірGreen
); -
background("#32C2AA");
- шістнадцяткові значення абоHex CSS
(колірKeppel
); -
background("magenta");
-CSS
назви кольорів.
Зазначені та інші назви кольорів, що використовуються у підручнику, отримані за допомогою онлайн-сервісу для створення та збирання колірних палітр coolors.co . Цей онлайн-інструмент дозволяє працювати з великою кількістю колірних просторів, включаючи RGB , CMYK , LAB , HSB і кількома популярними бібліотеками кольорів, таких як Pantone® , Copic® , Prismacolor® тощо.
|
Значення параметрів вказаних функцій для колірної моделі RGB
належать діапазону від 0
до 255
, де 255
- це білий колір, 128
- відтінок сірого кольору, і 0
- чорний колір.
Якщо для наведених вище функцій надано лише одне значення, воно буде інтерпретовано як значення у градаціях сірого кольору.
Якщо додано друге значення - воно буде використано для прозорості.
Коли вказані три значення, вони інтерпретуються як значення RGB
або HSB
. Додавання четвертого значення застосовує прозорість.
Цікавимось
Вправа 11
Надрукувати в консолі вебпереглядача значення змінної c
.
function setup() {
createCanvas(400, 300);
frameRate(1); // створення повільної анімації
}
function draw() {
background(255); // білий колір тла
let c = color(random(255)); // визначення випадкового сірого відтінку
stroke(c); // колір, який використовується для малювання ліній та меж навколо фігур
strokeWeight(20); // товщина обведення, що використовується для ліній, точок та межі навколо фігур
point(200, 150); // малювання точки в центрі полотна
}
У коді оголошується змінна c і їй присвоюється значення функції color() , яка створює кольори для зберігання у змінних типу даних «колір». Це зручно, якщо необхідно створити колір, який надалі буде використовуватися у коді.
|
Результатом виконання наведеного коду буде мерехтлива точка сірого відтінку (у діапазоні від чорного до білого), розміщена в центрі полотна.
Розуміючи, як працює діапазон значень кольорів, можна сказати, що кожна фігура може мати stroke()
(обведення, межу) і fill()
(заливку).
Колір тла полотна встановлюється за допомогою функції background()
, яка записується в розділі функції draw()
і очищає, по суті, екран.
stroke()
визначає колір контуру фігури, а fill()
- колір заповнення цієї фігури. Зрозуміло, що лінії та точки можуть мати лише контур і значення товщини, яке встановлюється за допомогою функції strokeWeight() у пікселях.
Якщо значення кольору не вказано для вищезгаданих функцій, за стандартним налаштуванням використовується чорний (0
) для stroke()
та білий (255
) для fill()
.
Додатково можна використовувати функцію noFill() для вимкнення заливки або функцію noStroke() для вимкнення контуру перед малюванням фігури. Варто пам’ятати, що при записі у коді застосунку обидвох функцій перед малюванням фігур на екрані нічого не з’явиться. |
Колірна модель RGB
у «цифровому» світі набула широкого використання. Кольори цієї моделі подаються трійкою значень: Red
(червоний), Green
(зелений), Blue
(синій).
Змішуючи ці кольори у різних комбінаціях, можна отримати необхідний колір:
червоний + зелений = жовтий
червоний + синій = фіолетовий
зелений + синій = блакитний (синьо-зелений)
червоний + зелений + синій = білий
немає кольорів = чорний
Вправа 12
Заповнити прогалини у функції color()
трьома значеннями складових кольору, які генеруються випадково, щоб точка світилася не лише у відтінках сірого кольору. Скористатися поданим зразком коду.
function setup() {
createCanvas(400, 300);
frameRate(1);
}
function draw() {
background(255);
let c = color(..., ..., ...);
stroke(c);
strokeWeight(20);
point(200, 150);
}
На додаток до червоного, зеленого та синього компонентів кожного кольору існує додатковий необов’язковий четвертий компонент, який називається альфа-каналом.
Альфа-канал відповідає за прозорість і є корисним, коли ви хочете намалювати елементи, які здаються частково прозорими один відносно одного.
Значення альфа-каналу також може набувати значень з діапазону від 0
до 255
. У цьому разі значення 0
означає повністю прозорий (непрозорість 0%
) і 255
- повністю непрозорий (непрозорість 100%
).
Подивимось, як працює прозорість, побудувавши кілька прямокутників за допомогою функції rect()
з використанням заливки fill()
різної прозорості.
function setup() {
createCanvas(200, 200);
}
function draw() {
background(251, 133, 0); // Tangerine (1)
noStroke();
fill(255, 200, 221); // Orchid Pink (2)
rect(0, 0, 100, 200);
fill(20, 33, 61, 255); // Oxford Blue (3)
rect(0, 0, 200, 40);
fill(20, 33, 61, 191); (4)
rect(0, 50, 200, 40);
fill(20, 33, 61, 127); (5)
rect(0, 100, 200, 40);
fill(20, 33, 61, 63); (6)
rect(0, 150, 200, 40);
}
1 | Заливка полотна помаранчевим кольором. Значення прозорості відсутнє, тому полотно 100% непрозоре. |
2 | Заливка рожевого кольору для наступного прямокутника. Значення прозорості відсутнє, тому прямокутник 100% непрозорий. |
3 | Заливка синього кольору для наступного прямокутника. Значення прозорості 255 , тому прямокутник 100% непрозорий. |
4 | Непрозорість 75% для наступного синього прямокутника. |
5 | Непрозорість 50% для наступного синього прямокутника. |
6 | Непрозорість 25% для наступного синього прямокутника. |

Як було вже зазначено, колір RGB
із діапазоном від 0
до 255
- це не єдиний спосіб подання кольорів у p5.js
.
За стандартним налаштуванням параметри background()
, color()
, fill()
і stroke()
визначаються значеннями від 0
до 255
за допомогою колірної моделі RGB
.
Це еквівалентно режиму
colorMode(RGB, 255)
встановленого за допомогою функції colorMode() . Вказавши власний діапазон у функції colorMode()
, можна змінити спосіб інтерпретації даних кольору.
Наприклад, для режиму
colorMode(RGB, 100);
діапазон значень RGB
буде від 0
до 100
, а для режиму
colorMode(RGB, 100, 200, 30, 255);
значення червоного кольору будуть в діапазоні від 0
до 100
, зеленого - від 0
до 200
, синього - від 0
до 30
і альфа-канал від 0
до 255
.
Наявні кольорові об’єкти запам’ятовують режим, у якому вони були створені, тому можна змінювати режими як завгодно, не впливаючи на зовнішній вигляд об’єктів. |
Використання лише кольорів RGB
здебільшого буде достатньо, проте можна використовувати й іншу колірну модель - HSB:
-
Hue
- відтінок - відтінок кольору (зелений, червоний тощо) за стандартним налаштуванням коливається від0
до360
(у градусах); -
Saturation
- насиченість - яскравість кольору, значення від0
до255
за стандартним налаштуванням (у %); -
Brightness
- яскравість - значення від0
до255
за стандартним налаштуванням (у %).
У Processing IDE є вбудований інструмент вибору кольору, який можна відкрити із головного меню за допомогою .
|

У вікні вибору кольору в Processing IDE
можна отримати значення кольору (Dark Slate Blue
) у моделях RGB
і HSB
та у шістнадцятковому вигляді (#42367A
).
Вправа 13
У функції setup()
випадково були видалені значення кольорів. Спробуйте відновити початковий вигляд коду відповідно до інформації із коментарів. Що зображено на полотні?
let c1, c2;
function setup() {
createCanvas(500, 500);
c1 = color("..."); // чорний колір, шістнадцяткове значення
c2 = color(...); // білий колір, RGB
background(c1);
}
function draw() {
fill(c1);
stroke(c2);
rect(5, 5, 190, 190);
fill(46, 196, 182); // Tiffany Blue
triangle(70, 80, 130, 80, 100, 185);
fill("#F72585"); // Flickr Pink
ellipse(100, 80, 60, 60);
}
3.2.3. Ресурси
Корисні джерела
3.2.4. Контрольні запитання
Міркуємо Обговорюємо
-
Що таке «піксель»?
-
Який вигляд має система координат полотна?
-
Що таке «колірна модель»? Які є різновиди колірних моделей? Дайте характеристику одній із колірних моделей.
-
Яке призначення функцій
color()
,background()
,fill()
,stroke()
,colorMode()
бібліотекиp5.js
? -
Які способи запису значення кольору використовуються у вищезгаданих функціях?
-
Який колір визначає кожен із наступних рядків коду? У разі потреби з’ясувати експериментальним шляхом.
fill(0, 120, 0);
fill(100);
fill(0);
stroke(0, 0, 200);
stroke(225);
stroke(255, 255, 0);
stroke(0, 255, 255);
stroke(200, 150, 150);
stroke(255, 0, 255);
3.2.5. Практичні завдання
Початковий
-
Заповнити прогалини в коді значеннями довільного кольору. Для запису значення кольору використати різні способи.
function setup() {
createCanvas(200, 200);
background(...);
}
function draw() {
stroke(...);
fill(...);
ellipse(100, 100, 85);
}
Середній
-
Поданий зразок коду створює ефект веселки. В цьому разі застосовується колірна модель
RGB
. Змінити колірну модель наHSB
. Порівняти результати для обох колірних моделей.
function setup() {
createCanvas(300, 300);
background('#04B1CE'); // Pacific Blue
noFill();
}
function draw() {
strokeWeight(random(3, 10));
stroke(random(255), random(255), random(255)); // RGB
let rainbowSize = random(200, 270);
ellipse(150, 350, rainbowSize, rainbowSize);
}
-
Намалювати прямокутник. В ескізі використати функції
background()
,stroke()
,fill()
і колірну модельRGB
для запису значень кольорів.
Високий
-
Створити квітку як на малюнку. Використати фрагмент коду нижче, доповнивши його відсутніми командами.

function setup() {
createCanvas(300, 300);
}
function draw() {
let flowerX = 150;
let flowerY = 150;
let petalSize = 100;
let petalDistance = petalSize / 2;
background(82, 183, 136);
fill(255, 255, 255);
// верхня ліва пелюстка
circle(flowerX - petalDistance, flowerY - petalDistance, petalSize);
// верхня права пелюстка
...
// нижня ліва пелюстка
...
// нижня права пелюстка
...
// центральна пелюстка
fill(255, 210, 63);
circle(flowerX, flowerY, petalSize);
}
-
Використати повний код застосунку з попереднього завдання для малювання квітки у випадкових точках полотна щоразу іншого розміру.
Застосувати функцію random() для створення випадкових значень для flowerX , flowerY і petalSize . У функції setup() використати функцію frameRate(1) (вказує кількість кадрів, які відображаються щосекунди) для створення повільної анімації.
|
-
Використати повний код для малювання квітки та експериментальним шляхом дізнатися, як можна досягнути результату як у демонстрації. Пояснити отриманий результат.
Екстремальний
-
Створити застосунок, який малює на полотні випадкові за розміром, кольором і місцем розташування кола різної прозорості.
-
Створити імітацію вогнів нічного міста як у демонстрації.
Створити три еліпси різних кольорів. Замість функції background() у draw() використати функцію побудови прямокутника відповідно розмірів полотна та з певним значенням прозорості. В разі потреби у функції setup() вказати частоту кадрів за допомогою функції frameRate() .
|
3.3. Основні форми: точка, лінія, прямокутник, еліпс
3.3.1. Примітиви
Будь-який візуальний застосунок складається з простих фігур: точок, ліній, прямокутників, кіл, еліпсів, трикутників тощо. Ці фігури називаються примітивами.

З функціями p5.js
для малювання простих фігур ми вже зустрічалися у попередніх розділах. Познайомимось із ними ближче.
Розглянемо в демонстраційних цілях полотно розміром 10x10
пікселів (це кілька міліметрів екранного простору) і уявимо його аркушем у клітинку. Зрозуміло, що в реальних задачах полотно має більші розміри.
Для малювання будь-якої простої фігури необхідно спочатку вказати інформацію щодо її розташування на полотні.
Для малювання точки на полотні використовується функція point() у такий спосіб: point(x, y)
, де x
та y
- це координати цієї точки на полотні.
Продемонструємо точку на нашому аркуші у клітинку.

Зображена точка у формі квадрата - це піксель - найдрібніша одиниця цифрового зображення в растровій графіці. Він являє собою неподільний об’єкт зазвичай квадратної форми, що має певний колір. Будь-яке растрове комп’ютерне зображення складається з пікселів, розташованих по рядках і стовпцях. |
Малювання лінії вимагає двох точок

Прямокутники можна малювати за допомогою функції rect() у різних режимах, які встановлюються за допомогою функції rectMode() .
Режим rectMode(CORNER)
, що використовується за стандартним налаштуванням, інтерпретує перші два параметри у rect()
як координати лівого верхнього кута прямокутника, тоді як третім і четвертим параметрами є ширина та висота прямокутника.

Також, прямокутник можна створювати у режимах:
-
rectMode(CENTER)

-
rectMode(CORNERS)

Назви режимів функції rectMode() мають бути записані у верхньому регістрі.
|
Вправа 14
Заповнити прогалини у функціях rect()
, щоб отримати прямокутники із вказаними у коментарях властивостями. Скористатися поданим зразком коду.
function setup() {
createCanvas(650, 200);
}
function draw() {
background(255);
fill(0, 255, 0, 25); // Green
rect(20, 50, ..., ...); // квадрат
rect(180, 50, ..., ..., ...); // квадрат із заокругленими кутами, кожен з яких має однаковий радіус заокруглення
rect(340, 50, ..., ..., ..., ..., ..., ...); // квадрат із заокругленими кутами, які мають різні радіуси заокруглення
rect(500, 75, ..., ...); // звичайний прямокутник, витягнутий по горизонталі
}
Для малювання еліпса (овалу) застосовують функцію ellipse() , а сама концепція малювання є схожою як і для прямокутника, з різницею у назві функції для встановлення режимів малювання ellipseMode() .
За стандартним налаштуванням перші два параметри встановлюють розташування центру еліпса, а третій і четвертий параметри встановлюють ширину та висоту фігури. Якщо висота не вказана, значення ширини використовується як для ширини, так і для висоти. Якщо вказана від’ємна висота або ширина, приймається абсолютне значення.
Еліпс з однаковою шириною та висотою - це коло. |
Отже, для створення еліпса можна використовувати наступні режими:
-
ellipseMode(CENTER)

-
ellipseMode(CORNER)

-
ellipseMode(CORNERS)
На малюнках еліпси не виглядають особливо круглими, оскільки, масштаб полотна - аркуша у клітинку - є збільшеним і ми отримуємо набір квадратів у формі еліпса. Повернення до реальних розмірів пікселів на екрані комп’ютера дозволяє отримати бажану круглість.
Вправа 15
Заповнити прогалини у функціях ellipse()
, щоб отримати еліпси різних форм: у формі кола, витягнутий по горизонталі й витягнутий по вертикалі. Скористатися поданим зразком коду.
function setup() {
createCanvas(480, 200);
}
function draw() {
background(255);
fill(255, 204, 0); // Sunglow
ellipse(80, 100, ..., ...);
ellipse(240, 100, ..., ...);
ellipse(400, 100, ..., ...);
}
Можна виділити ще кілька інструментів для малювання інших примітивів.
Для малювання трикутника, використовують функцію triangle() , яка приймає шість параметрів - координати вершин трикутника, а багатокутник quad() має аж вісім параметрів-координат.
Вправа 16
Поекспериментувати зі значеннями для побудови трикутників і багатокутників. Скористатися поданим зразком коду.
function setup() {
createCanvas(650, 600);
}
function draw() {
background(255);
stroke(0);
// трикутники
fill(0, 0, 255); // Blue
triangle(..., ..., ..., ..., ..., ...);
triangle(..., ..., ..., ..., ..., ...);
// багатокутники
fill(0, 255, 255); // Aqua
quad(..., ..., ..., ..., ..., ..., ..., ...);
quad(..., ..., ..., ..., ..., ..., ..., ...);
}
Для малювання дуги (сектора), використовують функцію arc() , для якої необхідно вказати координати центру, ширину і висоту, початковий і кінцевий кут в радіанах.
Цікавимось
Для перетворення градусів у радіани можна скористатися функцією radians() . |
Вправа 17
Заповнити прогалини у коді значеннями градусних констант, щоб утворити зображення як на малюнку.
function setup() {
createCanvas(200, 200);
background(245);
}
function draw() {
let c = color(25, 144, 123, 191); // Illuminating Emerald
fill(c);
arc(100, 100, 50, 50, 0, ...);
noFill();
arc(100, 100, 80, 80, ..., ...);
arc(100, 100, 110, 110, ..., PI + QUARTER_PI);
arc(100, 100, 140, 140, PI + QUARTER_PI, ...);
}

Геометричні примітиви мають властивості - це товщина лінії та згладжування (англ. anti-aliasing
).
Відповідно, для згладжування (увімкнене за стандартним налаштуванням у 2D
-режимі) використовується функція smooth() , а щоб вимкнути згладжування застосовують функцію noSmooth() .
За стандартним налаштуванням товщина ліній складає 1 піксель, але це значення можна змінити за допомогою функції strokeWeight() , вказавши значення товщини лінії.
Вправа 18
У поданому зразку коду змінити параметри фігур відповідно до інформації у коментарях.
function setup() {
createCanvas(480, 200);
background(245);
}
function draw() {
fill(23, 195, 178); // Tiffany Blue
...; // вимкнути згладжування
strokeWeight(...); // зменшити товщину ліній до 1 пікселя
ellipse(75, 100, 90, 90);
strokeWeight(...); // збільшити товщину ліній до 8 пікселів
ellipse(175, 100, 90, 90);
strokeWeight(...); // збільшити товщину ліній до 14 пікселів
ellipse(279, 100, 90, 90);
strokeWeight(...); // збільшити товщину ліній до 20 пікселів
ellipse(389, 100, 90, 90);
}
Якщо для фігур встановлено деякі параметри, усі фігури, які описані у коді далі за ними, будуть відображатися відповідно цих параметрів. |
3.3.2. Криві
Щоб краще зрозуміти принципи побудови кривих, приєднаємо до нашого ескізу графічну сітку, що використовувалася раніше, і намалюємо лінію.
let grid;
function preload() {
grid = loadImage("grid_1000.png");
}
function setup() {
createCanvas(1000, 1000);
strokeWeight(3);
noFill();
image(grid, 0, 0);
}
function draw() {
stroke(0, 233, 245); // Electric Blue
line(200, 200, 600, 600);
}

Для креслення кривих використовують функцію curve() .
Функцію curve()
можна використати для малювання прямої лінії (накладається зверху):
function draw() {
stroke(0, 233, 245); // Electric Blue
line(200, 200, 600, 600);
stroke(249, 199, 79); // Maize Crayola
curve(0, 0, 200, 200, 600, 600, 500, 500);
}

Як видно, чотири значення координат усередині функції curve()
збігаються зі значеннями у функції line()
. Ці координати вказують початкову та кінцеву точку кривої (у цьому разі кривою є пряма лінія 😉).
Функція curve()
приймає чотири додаткові зовнішні значення (0, 0
і 500, 500
), які визначають дві пари координат контрольних точок. Положення цих контрольних точок визначають напрямок і величину кривини лінії.
Щоб зрозуміти, як використовуються контрольні точки, намалюємо іншу криву.
function draw() {
stroke(0, 233, 245); // Electric Blue
line(200, 200, 600, 600);
stroke(249, 199, 79); // Maize Crayola
curve(100, 525, 200, 200, 600, 600, 800, 400);
// контрольні точки
stroke(255, 128, 64); // Fern Green
fill(255);
circle(100, 525, 10);
circle(800, 400, 10);
noFill();
}

У цьому разі функція curve()
приймає інші дві пари координат контрольних точок - 100, 525
і 800, 400
відповідно. За такої умови, чотири середні значення у функції curve()
залишаються без змін.
Доповнимо наш код, який намалює криві зеленого кольору, які з’єднають першу контрольну точку з початком кривої та кінець кривої з другою контрольною точкою відповідно.
function draw() {
stroke(0, 233, 245); // Electric Blue
line(200, 200, 600, 600);
stroke(249, 199, 79); // Maize Crayola
curve(100, 525, 200, 200, 600, 600, 800, 400);
stroke(6, 214, 160); // Caribbean Green
curve(0, 400, 100, 525, 200, 200, 500, 500);
curve(0, 0, 600, 600, 800, 400, 650, 650);
// контрольні точки
stroke(255, 128, 64); // Mango Tango
fill(255);
circle(100, 525, 10);
circle(800, 400, 10);
noFill();
}

Результатом буде зелена-жовта-зелена крива, що складається з трьох частин, і показує, як контрольні точки визначають кривину жовтої частини кривої.
Контрольні точки впливають на криву таким способом, що кожен кінець жовтої кривої тягнеться до сусідньої контрольної точки. Чим ближче наближати контрольну точку до центру полотна, тим сильніше крива буде «згинатися» і навпаки.
У цьому разі криві зеленого кольору подовжують в обох напрямках жовту криву, яку можна назвати сплайном.
Цікавимось
Використовуючи функцію curveTightness() , можна змінювати властивості кривих.
Функція curveTightness() визначає, як крива наближається до точок власних вершин.
|
Наприклад, значення 0.0
є значенням за стандартним налаштуванням (це значення визначає криві як сплайни Кетмулла-Рома), а значення 1.0
пов’язує всі точки прямими лініями.
Значення з діапазону (-5.0, 5.0)
деформують криві, але залишають їх впізнаваними, а зі збільшенням значень криві продовжують деформуватися.
function draw() {
stroke(0, 233, 245); // Electric Blue
line(200, 200, 600, 600);
curveTightness(1);
stroke(249, 199, 79); // Maize Crayola
curve(100, 525, 200, 200, 600, 600, 800, 400);
stroke(6, 214, 160); // Caribbean Green
curve(0, 400, 100, 525, 200, 200, 500, 500);
curve(0, 0, 600, 600, 800, 400, 650, 650);
}

3.3.3. Криві Безьє
У векторній комп’ютерній графіці для моделювання гладких кривих знайшли широке застосування криві Безьє, які були розроблені французьким автомобільним інженером і винахідником П’єром Безьє.
Цікавимось
Криві Безьє забезпечують гладке моделювання кривих з використанням якірних (через які проходить крива) і контрольних (які задають форму кривої) точок.

Для побудови кривої Безьє у бібліотеці p5.js
визначена функція bezier() , яка має низку параметрів.
Перші два параметри визначають першу якірну точку, останні два параметри вказують другу якірну точку. Якірні точки стають першою та останньою точками на кривій.
Параметри, що містяться усередині, визначають дві контрольні точки, які утворюють форму кривої.
Для побудови складних за формою ліній окремі криві Безьє можуть бути послідовно з’єднані одна з одною у сплайн Безьє. Для того, щоб забезпечити гладкість лінії в місці з’єднання двох кривих, три суміжні якірні точки обох кривих повинні лежати на одній прямій. |
Намалюємо криву Безьє, використовуючи змінні x2
, y2
, x3
, y3
з однаковими значеннями для пар координат контрольних точок.
let grid;
function preload() {
grid = loadImage("grid_1000.png");
}
function setup() {
createCanvas(1000, 1000);
strokeWeight(3);
noFill();
image(grid, 0, 0);
}
function draw() {
let x2, y2, x3, y3;
x2 = 100;
y2 = 100;
x3 = 100;
y3 = 100;
stroke(0, 233, 245); // Electric Blue
bezier(100, 100, x2, y2, x3, y3, 500, 300);
// якірні точки
stroke(255, 128, 64); // Mango Tango
fill(255);
circle(100, 100, 10);
circle(500, 300, 10);
noFill();
}

Перетворимо отриману пряму лінію на криву, встановивши інші значення для змінних x2
, y2
, x3
, y3
, і намалюємо лінії, які сполучають контрольні і якірні точки.
let grid;
function preload() {
grid = loadImage("grid_1000.png");
}
function setup() {
createCanvas(1000, 1000);
strokeWeight(3);
noFill();
image(grid, 0, 0);
}
function draw() {
let x2, y2, x3, y3;
x2 = 250;
y2 = 400;
x3 = 330;
y3 = 50;
stroke(0, 233, 245); // Electric Blue
bezier(100, 100, x2, y2, x3, y3, 500, 300);
stroke(230, 100, 27); // Spanish Orange
line(100, 100, x2, y2);
line(500, 300, x3, y3);
// якірні точки
stroke(255, 128, 64); // Mango Tango
fill(255);
circle(100, 100, 10);
circle(500, 300, 10);
// контрольні точки
circle(250, 400, 10);
circle(330, 50, 10);
noFill();
}

Попрактикуватись у створенні кривих Безьє можна у вільному редакторі векторної графіки Inkscape за допомогою інструмента Малювання кривих Безьє чи пограти у гру The Bézier Game . |
3.3.4. Вершини фігур
Які відомо, вершина фігури - це точка, яка використовується для з’єднання ліній фігури. Наприклад, для трикутника потрібні 3
вершини, для квадрата - 4
, для зірки ⭐ - 10
. Використовуючи криві для з’єднання вершин, можна створювати й власні форми.
Для цих цілей у бібліотеці p5.js
є спеціальні функції: beginShape() , vertex() та endShape() .
Розглянемо код, який малює точно таку ж фігуру, як і функція rect()
, встановлюючи кожну із вершин (vertex
) прямокутника окремо.
let grid;
function preload() {
grid = loadImage("grid_1000.png");
}
function setup() {
createCanvas(1000, 1000);
strokeWeight(3);
noFill();
image(grid, 0, 0);
}
function draw() {
stroke(0, 233, 245); // Electric Blue
beginShape(); (1)
vertex(200, 200); (2)
vertex(500, 200);
vertex(500, 400);
vertex(200, 400);
endShape(CLOSE); (3)
}
1 | Функція beginShape() вказує, що необхідно створити власну фігуру, яка складається з деякої кількості вершин. |
2 | Функція vertex() визначає точки для кожної з вершин прямокутника. |
3 | Функція endShape() вказує на те, що додавання вершин завершено. Значення CLOSE усередині endShape(CLOSE) сигналізує, що остання точка вершини повинна з’єднатися з першою. |

Після виклику функції beginShape()
повинна слідувати серія команд vertex()
. Щоб завершити малювання фігури, необхідно викликати endShape()
. Кожна фігура буде окреслена поточним кольором для контуру і заповнена кольором, якщо заповнення увімкнено.
Власна форма може бути доволі гнучкою, що є перевагою над наперед визначеними фігурами.
У beginShape()
можна додати значення, вказуючи який саме тип фігури необхідно створити.
Наприклад, якщо створюється шість вершин, бібліотека p5.js
не може знати, що потрібно намалювати: два трикутники чи один шестикутник, якщо не вказати beginShape(TRIANGLES)
.
Якщо ж необхідно намалювати точки або лінії, то використовуються команди beginShape(POINTS)
або beginShape(LINES)
відповідно.
function draw() {
stroke(0, 233, 245); // Electric Blue
// точки
strokeWeight(10);
beginShape(POINTS);
vertex(200, 200);
vertex(500, 200);
vertex(500, 400);
vertex(200, 400);
endShape(CLOSE);
// окремі лінії
strokeWeight(3);
beginShape(LINES);
vertex(200, 500);
vertex(300, 600);
vertex(400, 600);
vertex(500, 500);
endShape(CLOSE);
// трикутники
beginShape(TRIANGLES);
vertex(350, 100);
vertex(100, 500);
vertex(600, 500);
vertex(350, 100);
endShape(CLOSE);
}

Значеннями для beginShape() можуть бути: POINTS , LINES , TRIANGLES , TRIANGLE_FAN , TRIANGLE_STRIP , QUADS , QUAD_STRIP і TESS (лише для WEBGL ). LINES призначений для малювання серії окремих ліній, а не безперервної лінії. Для малювання неперервної лінії значення у функції не вказують.
|
Функцію vertex()
можна замінити функцією curveVertex() , щоб з’єднати вершини кривими замість прямих ліній.
let grid;
function preload() {
grid = loadImage("grid_1000.png");
}
function setup() {
createCanvas(1000, 1000);
image(grid, 0, 0);
}
function draw() {
// амеба
noStroke();
fill(244, 162, 97); // Sandy Brown
beginShape();
curveVertex(170, 330);
curveVertex(165, 310);
curveVertex(175, 300);
curveVertex(200, 290);
curveVertex(250, 294);
curveVertex(290, 250);
curveVertex(280, 150);
curveVertex(330, 120);
curveVertex(370, 150);
curveVertex(380, 230);
curveVertex(470, 220);
curveVertex(495, 240);
curveVertex(420, 320);
curveVertex(435, 335);
curveVertex(450, 360);
curveVertex(440, 400);
curveVertex(460, 450);
curveVertex(440, 470);
curveVertex(360, 440);
curveVertex(270, 490);
curveVertex(250, 450);
curveVertex(250, 400);
curveVertex(190, 360);
curveVertex(170, 330);
endShape(CLOSE);
fill(250, 211, 179); // Peach Puff
circle(345, 340, 85);
fill(255, 255, 255); // White
circle(325, 320, 20);
}

Ще одна функція, яка дозволяє малювати криві між вершинами більш гладкими, - це bezierVertex() .
Функція вказує координати вершин для кривих Безьє. Кожен виклик bezierVertex()
визначає положення двох контрольних точок і однієї якірної точки кривої Безьє.
Відразу після функції beginShape() і перед bezierVertex() слід додати виклик vertex() для встановлення початкової вершини.
|
let grid;
function preload() {
grid = loadImage("grid_1000.png");
}
function setup() {
createCanvas(1000, 1000);
image(grid, 0, 0);
}
function draw() {
// крива
noFill();
stroke(0, 233, 245); // Electric Blue
beginShape(); (1)
vertex(200, 100); // початкова вершина
bezierVertex(
100,
200, // контрольна точка для початкової вершини
300,
400, // контрольна точка для кінцевої вершини
200,
500 // кінцева вершина
);
endShape();
// контрольні точки кривої (2)
stroke(244, 162, 97); // Sandy Brown
line(100, 200, 200, 100);
line(300, 400, 200, 500);
fill(244, 162, 97); // Sandy Brown
circle(100, 200, 10);
circle(300, 400, 10);
// сердечко
noStroke();
fill(239, 71, 111); // Paradise Pink
// ліва частина сердечка
beginShape(); (3)
vertex(500, 400);
bezierVertex(320, 300, 450, 150, 500, 250);
endShape(CLOSE);
// контрольні точки сердечка (4)
stroke(244, 162, 97); // Sandy Brown
line(500, 400, 320, 300);
line(500, 400, 680, 300);
line(500, 250, 450, 150);
line(500, 250, 550, 150);
fill(244, 162, 97); // Sandy Brown
circle(320, 300, 10);
circle(680, 300, 10);
circle(450, 150, 10);
circle(550, 150, 10);
}
1 | Крива складається з двох вершин, кожна з яких приєднана до своєї власної контрольної точки. |
2 | Візуалізація контрольних точок кривої. |
3 | Ліва частина сердечка. |
4 | Візуалізація контрольних точок сердечка. |

Для зміни властивостей ліній, їх з’єднань і візуалізації кінців відрізків, використовують функції strokeJoin() і strokeCap() . |
Вправа 19
Намалювати праву частину сердечка.
Цікавимось Додатково
3.3.5. Ресурси
Корисні джерела
3.3.6. Контрольні запитання
Міркуємо Обговорюємо
-
Що таке «примітиви»?
-
Яке призначення функцій
point()
,line()
,rect()
,ellipse()
,triangle()
,arc()
,quad()
,smooth()
,strokeWeight()
бібліотекиp5.js
? -
Яке призначення функцій
curve()
,bezier()
,beginShape()
,endShape()
,vertex()
,curveVertex()
,bezierVertex()
? -
Для чого використовуються «криві Безьє»?
-
Що називається контрольними і якірними точками у контексті «кривої лінії»?
-
Як створюються складні фігури, для яких у бібліотеці
p5.js
не визначені готові функції?
3.3.7. Практичні завдання
Початковий
-
Заповнити прогалини в коді для створення зображення як на малюнку.

function setup() {
// створення полотна 200x200
...
// колір тла полотна (1, 186, 240) // Cyan Process
...
// інтерпретує перші два параметри прямокутника як центральну точку фігури, тоді як третій і четвертий параметри - це її ширина та висота
rectMode(CENTER);
}
function draw() {
// оголошення змінних
let x = width / 2;
let y = height / 2;
// малювання кола
// колір заливки кола (237, 34, 93) // Paradise Pink
...
// вимкнення межі кола
noStroke();
// малювання еліпса з координатами (x, y, 180, 180)
...
// малювання прямокутника
// колір заливки прямокутника (255)
...
// малювання прямокутника з координатами (x, y, 130, 30)
...
}
-
Створити малюнок з примітивів, використовуючи сторінку зошита в клітинку або файл електронної таблиці відповідно до поданого коду.
line(0, 1, 8, 9);
point(1, 4);
point(2, 5);
point(6, 5);
point(7, 6);
rectMode(CORNER);
rect(5, 1, 4, 3);
ellipseMode(RADIUS);
ellipse(2, 8, 10, 6);
Середній
-
Заповнити прогалини в коді для створення анімованого зображення як у демонстрації.
let count = 0; // ініціалізація глобальної змінної
function setup() {
let canvasWidth = 200;
let canvasHeight = 200;
// створення полотна 200x200
...
// колір тла полотна (1, 186, 240) // Cyan Process
...
// інтерпретує перші два параметри прямокутника як центральну точку фігури, тоді як третій і четвертий параметри - це її ширина та висота
rectMode(CENTER);
}
function draw() {
// оголошення локальних змінних
let x = width / 2;
let y = height / 2;
let size = 100 + count; // зміна розміру фігур
// коло
fill(237, 34, 93); // Paradise Pink
noStroke();
ellipse(x, y, size, size);
// прямокутник
fill(255);
rect(x, y, size * 0.75, size * 0.15);
count = count + 1; // збільшення значення на 1
}
Високий
-
Створити застосунок, який щоразу генерує іншу випадкову квітку.
-
Використати код попереднього завдання для генерації безлічі випадкових квітів.
-
Написати код для створення примітивів відповідно до поданої на малюнку схеми. Для даного завдання може бути кілька правильних відповідей.

Екстремальний
-
Створити візуальне представлення використання дискового простору власного смартфона у вигляді кільцевої діаграми. Орієнтовний зразок представлений на малюнку.

3.4. Реалізація базових алгоритмічних конструкцій
Початковий код застосунків, які розглядалися вище, виконувався послідовно, рядок за рядком, згори вниз. Така алгоритмічна конструкція називається лінійною.
3.4.1. Розгалуження
Досить часто у програмуванні окремі фрагменти коду повинні виконуватися лише за певної умови.
Умову можна описати як висловлення, про яке можна однозначно сказати істинне воно (англ. true
- правда) чи хибне (англ. false
- хибність).
Наприклад:
-
«Мова
JavaScript
є інтерпретованою мовою програмування.» (Відповідь «так», тому висловлення істинне і значення умовиtrue
). -
«
JavaScript
іJava
- це схожі назви мови програмуванняJavaScript
.» (Відповідь «ні», тому висловлення хибне і значення умовиfalse
).
Реалізувати виконання коду за певних умов можна за допомогою алгоритмічної конструкції, яка називається розгалуження. У мові програмування JavaScript
для позначення розгалуження використовується зарезервоване слово if
.
Якщо уявити прохід інтерпретатора JavaScript
через код, то записане у коді зарезервоване слово if
буде тим місцем, де код розгалужується на два і більше шляхів, а інтерпретатор має обрати, яким шляхом йому слідувати.
Вказівка розгалуження if
дозволяє виконати блок коду, лише якщо певна умова є істинною:
if (умова) {
// інструкції, які виконуються, якщо умова істинна (true),
// не виконуються, якщо умова хибна (false)
}
Така форма розгалуження називається неповною.
Умова містить логічний вираз, в результаті обчислення якого одержується значення false або true .
|
Значення true
і false
(зверніть увагу, значення записуються з малої літери) є булевими значеннями або значеннями типу даних Boolean
.
Отже, на місці умови записують логічні вирази, які в результаті обчислення повертають значення істини або хибності.
Для побудови логічних виразів використовують оператори порівняння.
Оператор |
Опис |
|
більше |
|
менше |
|
більше або дорівнює |
|
менше або дорівнює |
|
дорівнює (перевірка на значення, перетворення типів) |
|
не дорівнює (перевірка на значення, перетворення типів) |
|
дорівнює (перевірка на ідентичність, без перетворення типів) |
|
не дорівнює (перевірка на ідентичність, без перетворення типів) |
Цікавимось
Використаємо консоль вебпереглядача (Ctrl+Shift+I) і функцію console.log()
, щоб поглянути на роботу операторів порівняння.
Для цього запишемо у коді застосунку у функції setup()
вирази:
console.log(10 > 5); // true
console.log(12 > 60); // false
console.log(40 == 40); // true
console.log(40 == "40"); // true
console.log(25 === "25"); // false
console.log(5 === 5); // true
console.log(15 >= 10); // true
console.log(200 !== 200); // false
console.log(1 !== 0); // true
Подивимось, як реалізується неповна форма розгалуження у наступному прикладі.
function setup() {
createCanvas(200, 200); (1)
background(220); (2)
noStroke(); (3)
}
function draw() {
if (mouseX > width / 2) { (4)
fill(57, 20, 99); // Persian indigo
rect(width / 2, 0, width / 2, height);
}
}
Прочитати даний код можна так:
1 | Створити полотно розміром 200x200 пікселів. |
2 | Встановити тло сірого кольору. |
3 | Вимкнути межі для фігур. |
4 | Якщо вказівник миші знаходиться справа від точки width / 2 екрану, то намалювати прямокутник без контуру із заливкою кольору Persian indigo праворуч від цієї точки. |
Переглядаємо Аналізуємо
Повна форма розгалуження має такий запис:
if (умова) {
// інструкції, які виконуються, якщо умова істинна (true)
} else {
// інструкції, які виконуються, якщо умова хибна (false)
}
Використаємо у коді повну форму розгалуження.
function setup() {
createCanvas(200, 200); (1)
noStroke(); (2)
}
function draw() {
if (mouseX > width / 2) { (3)
fill(57, 20, 99); // Persian indigo
rect(width / 2, 0, width / 2, height);
} else { (4)
background(220);
}
}
Прочитати даний код можна так:
1 | Створити полотно розміром 200x200 пікселів. |
2 | Вимкнути межі для фігур. |
3 | Якщо вказівник миші знаходиться справа від точки width / 2 екрану, то намалювати прямокутник без контуру із заливкою кольору Persian indigo справа від цієї точки. |
4 | Якщо вказівник миші знаходиться зліва від точки width / 2 екрану, то увесь простір полотна зафарбувати сірим кольором. |
Переглядаємо Аналізуємо
Для перевірки кількох умов у вказівці розгалуження можна використовувати у парі зарезервовані слова else if
у такому вигляді:
if (умова1) {
// інструкції, які виконуються, якщо умова1 істинна (true)
} else if (умова2) {
// інструкції, які виконуються, якщо умова2 істинна (true)
} else if (умова3) {
// інструкції, які виконуються, якщо умова3 істинна (true)
} else if (умова4) {
// інструкції, які виконуються, якщо умова4 істинна (true)
} else {
// інструкції, які виконуються, якщо усі умови хибні (false)
}
Як тільки умова є істинною, виконуються відповідні інструкції, а інші умови ігноруються. |
Вказівка розгалуження if обчислює логічні вирази в умові та в залежності від отриманого значення (істина чи хибність) спрямовує виконання застосунку у відповідну гілку. Для значень undefined , 0 і '' (порожній рядок) вказівка розгалуження визначає результат як false , а для значень, наприклад, 41 і 'hello world' - як true .
|
Використаємо кілька умов у нашому коді.
function setup() {
createCanvas(200, 200);
stroke(255);
}
function draw() {
if (mouseX < width / 3) {
background(173, 232, 244); // Blizzard Blue
} else if (mouseX < (2 * width) / 3) {
background(0, 150, 199); // Blue Green
} else {
background(3, 4, 94); // Midnight Blue
}
line(width / 3, 0, width / 3, height);
line((2 * width) / 3, 0, (2 * width) / 3, height);
}
В залежності від розташування вказівника миші, полотно буде зафарбовуватись у різні кольори.
Переглядаємо Аналізуємо
Вказівки розгалуження можуть вкладатися одна в одну, хоча такий варіант не дуже оптимальний, якщо мова йде про багато вкладень.
Щоб зменшити кількість таких ситуацій записують умови, у яких використовують складені логічні вирази.
Розглянемо код, який реалізує горизонтальний рух кульки та відбивання її від вертикальних меж полотна.
let x = 25;
let d = 50;
let velocity = 0.5;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(255);
if (x > width - d / 2 || x < d / 2) {
velocity = velocity * -1;
}
stroke(0);
fill(19, 133, 53); // Forest Green Web
ellipse(x, height / 2, d, d);
x = x + velocity;
}
Переглядаємо Аналізуємо
У коді використовується змінна x
, яка зберігає значення положення кульки та збільшується на величину velocity
.
Змінна velocity
визначає швидкість руху кульки. Окрім того, значення velocity
при досягненні кулькою межі, змінюється на протилежне (якщо було додатне, то змінюється на від’ємне і навпаки), і кулька рухається у протилежний бік. Інакше кажучи, якщо значення х
більше ширини полотна мінус радіус кульки або значення x
менше радіуса кульки, знак швидкості змінюється на протилежний.
Чому у цих двох логічних виразах використовується значення радіуса кульки d / 2
? Це робиться для того, щоб умова зміни напрямку перевірялася для випадку, коли кулька не виходить за вертикальні межі полотна, а лише торкається цих меж.
Умова складається з двох логічних виразів: х більше ширини полотна мінус радіус кульки та x менше радіуса кульки. Тобто, умова є складеною з двох простих умов, з’єднаних логічним оператором АБО (||
).
Складена умова у разі використання || буде істинною, якщо принаймні одна із простих умов буде істинною.
|
У нашому прикладі, якщо кулька намагається вийти за ліву межу полотна, проста умова x < d / 2
стане істинною і складена умова поверне значення true
.
Аналогічно, якщо кулька досягне правої межі полотна і намагатиметься вийти за цю межу, проста умова x > width - d / 2
стане істинною і складена умова поверне значення true
.
Поруч з логічним оператором ||
використовуються й інші логічні оператори: І (&&
) і НЕ (!
).
У разі використання Логічний оператор |
Деякі приклади використання логічних операторів і булевих значень:
true && true // true
true && false // false
true || false // true
false || false // false
!true // false
!false // true
Вправа 20
Змінити код для руху кульки, щоб кулька при відбиванні від меж не ховалась наполовину свого діаметра за межу.
Цікавимось
Переглядаємо Аналізуємо
Використаємо вказівку розгалуження для створення застосунку, який друкуватиме вітальне повідомлення на ім’я певного користувача, текст якого залежатиме від поточного часу доби.
let moment; (1)
const name = "user"; (2)
function setup() {
createCanvas(200, 100);
textSize(18); (3)
textAlign(CENTER, CENTER); (4)
frameRate(1); (5)
}
function draw() {
noStroke();
fill(255);
moment = random(0, 25); (6)
if (moment >= 5 && moment < 11) { (7)
background(224, 177, 203); // Pink Lavender
text(`Good morning, ${name}!`, width / 2, height / 2); (8)
} else if (moment >= 11 && moment < 16) {
background(190, 149, 196); // Lilac
text(`Good day, ${name}!`, width / 2, height / 2);
} else if (moment >= 16 && moment < 24) {
background(94, 84, 142); // Purple Navy
text(`Good evening, ${name}!`, width / 2, height / 2);
} else if (moment == 24 || (moment >= 0 && moment < 5)) {
background(59, 42, 113); // Spanish Violet
text(`Good night, ${name}!`, width / 2, height / 2);
} else {
background(159, 134, 192); // Purple Mountain Majesty
text(`Hi, ${name}!`, width / 2, height / 2);
}
}
1 | Оголошення змінної moment , яка буде зберігати значення часу доби в годинах. |
2 | Оголошення константи name зі значенням імені користувача. |
3 | Встановлення за допомогою функції textSize() поточного розміру шрифту. |
4 | Встановлення поточного вирівнювання для тексту за допомогою функції textAlign() . Функція приймає значення LEFT , CENTER або RIGHT (для горизонтального вирівнювання, відносно значення x -координати функції text() ) і TOP , BOTTOM , CENTER і BASELINE (для вертикального вирівнювання, відносно значення y -координати функції text() ). |
5 | Уповільнення анімації за допомогою функції frameRate() . |
6 | Генерування випадкового значення години доби в діапазоні від 0 до 25 (не включаючи 25 ). Результат роботи функції random() - число з рухомою крапкою, на зразок 0.007746035769928827 , 18.804574102871392 , 24.197599339160092 . |
7 | Вказівка розгалуження if зі складеними умовами у логічних виразах. В умовах перевіряється чи входить згенероване значення години в певний проміжок часу (ранок, день, вечір, ніч). |
8 | Виведення текстового повідомлення відповідно до часу доби з ім’ям користувача за допомогою шаблонного літерала і вирівнювання тесту по центру полотна. |
Цікавимось
Переглядаємо Аналізуємо
Оскільки p5.js
завжди відраховує час, що минув із запуску застосунку, це можна використовувати для моделювання руху, запуску подій із затримкою у часі чи у певному порядку.
Це зручно реалізувати за допомогою вказівки розгалуження if
і функції millis() , яка повертає значення лічильника часу.
Час вимірюється в мілісекундах (одна тисячна секунди), так, після секунди роботи застосунку лічильник часу матиме значення 1000
, після 3
секунд - 3000
, після хвилини - 60000
і т. д.
Наприклад, змусимо коло рухатися не відразу після запуску застосунку, а через 2
секунди після його запуску.
let t = 2000;
let x = 0;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
let ms = millis();
if (ms > t) {
x += 0.5;
}
fill(19, 133, 53); // Forest Green Web
circle(x, height / 2, 50);
}
Переглядаємо Аналізуємо
Цікавимось
3.4.2. Цикли
У процесі написання застосунків дуже часто виникає необхідність писати рядки коду, які повторюються або містять лише невеликі зміни.
Уявіть, що ви маєте створити тисячі фігур на екрані з різними параметрами. Процес написання такого однотипного коду вимагає багато часу і є не дуже оптимальним рішенням.
У разі, коли ми хочемо повторити наш код як є або з певними змінами, використовують алгоритмічну конструкцію цикли.
Цикл дозволяє виконувати блок коду скільки завгодно разів.
Функція draw() - це також цикл, який називається нескінченним, оскільки усі інструкції всередині цієї функції виконуються знову і знову, поки ми не зупинимо виконання застосунку.
|
Розглянемо такий код.
function setup() {
createCanvas(400, 200);
background(255);
}
function draw() {
stroke(158, 0, 89); // Jazzberry Jam
circle(100, 100, 15);
circle(120, 100, 15);
circle(140, 100, 15);
circle(160, 100, 15);
circle(180, 100, 15);
circle(200, 100, 15);
circle(220, 100, 15);
circle(240, 100, 15);
circle(260, 100, 15);
circle(280, 100, 15);
}
Функція stroke() встановлює колір для малювання ліній та меж навколо фігур. |
Результатом виконання коду буде десять кіл, розташованих на полотні горизонтально у рядок.
Код працює правильно, але його можна суттєво удосконалити. Якщо звернути увагу на написання функції circle()
, то усі значення, які отримує функція, крім першого, є незмінними та повторюються.
Оптимізуймо код.
function setup() {
createCanvas(400, 200);
background(255);
}
function draw() {
stroke(158, 0, 89); // Jazzberry Jam
let y, d;
y = 100;
d = 15;
circle(100, y, d);
circle(120, y, d);
circle(140, y, d);
circle(160, y, d);
circle(180, y, d);
circle(200, y, d);
circle(220, y, d);
circle(240, y, d);
circle(260, y, d);
circle(280, y, d);
}
Тепер змінити значення y
і d
для усіх команд дуже зручно, оскільки змінні знаходяться в одному місці. Як видно з коду, перше значення у виклику функції circle()
також зазнає повторень - змінюється на 20
пікселів щоразу для малювання кожного наступного кола. Тому тут можна застосувати цикл.
while
Отож, використаємо у нашому коді один із циклів з назвою while
.
function setup() {
createCanvas(400, 200);
background(255);
}
function draw() {
stroke(158, 0, 89); // Jazzberry Jam
let x, y, d;
y = 100;
d = 25;
x = 100;
i = 0;
while (i < 10) { (1)
circle(x, y, d); (2)
x = x + 30; (3)
i = i + 1; (4)
}
}
Розглянемо на цьому прикладі структуру циклу while
.
1 | Зарезервоване слово while , після якого йде у круглих дужках умова, а далі, у фігурних дужках записують інструкції (тіло циклу), які будуть повторюватися. Цикл буде виконуватися до тих пір, доки значення логічного виразу умови не стане хибним (false ). Тобто, як тільки значення простої умови i < 10 стане false , відбудеться вихід із циклу. |
2 | Функція малювання кола, у якій значення d і y не змінюються, а значення x змінюється на 30 пікселів. |
3 | Збільшення значення x на 30 пікселів. |
4 | Лічильник циклу - змінна, яка оголошена зі значенням 0 перед циклом, а у тілі циклу збільшується на 1 . Коли лічильник набуде значення 10 , відбудеться вихід з циклу. |
Циклічну структуру while
можна загалом подати так:
while (умова) {
// інструкції, які виконуються, доки умова істинна (true)
}
Вправа 21
Намалювати 5
кіл, розташованих вертикально одне під одним.
for
Ще одним різновидом циклічної структури в JavaScript
є цикл for
. Він дозволяє повторювати дії задану кількість разів.
Структура циклу for
має наступну форму:
for (let i = 0; i < 10; i = i + 1) {
// інструкції
}
-
let i = 0
- ініціалізація змінноїi
, яка буде відстежувати скільки разів цикл виконується - це змінна лічильника. -
i < 10
- умова для циклу, яка перевіряється на істинність - логічний вираз в умові обчислюється щоразу (на кожній ітерації - кроці виконання циклу), коли цикл починається. У нашому прикладі виконується перевірка, чи зміннаi
менша від числа10
. -
i = i + 1
- збільшення значення лічильника. -
Всередині фігурних дужок записується код, який має повторюватися. Цей код - тіло циклу.
-
Як тільки значення змінної лічильника робить значення умови хибним, цикл завершується і застосунок продовжує виконувати наступні інструкції, що записані після циклу.
Описати словами хід виконання циклу for
можна так:
-
Оголосити змінну
i
та встановити її початкове значення0
. Зміннаi
використовується лише у циклі. -
Поки
i
менш як10
, повторити код у тілі циклу. -
В кінці кожної ітерації (кроку циклу) додати одиницю до
i
.
Якщо умова циклу залишається завжди істинною, тоді створюється нескінченний цикл, з якого можна вийти лише за допомогою зовнішнього впливу користувача. Наприклад, функція draw() знаходиться в нескінченному циклі, який можна перервати, зупинивши виконання застосунку або закривши вікно вебпереглядача.
|
Для виходу із циклів ще до їх завершення у JavaScript використовують зарезервоване слово break .
|
Попри те, що нескінченні цикли є допустимим варіантом використання, цикли зазвичай використовуються для виконання операцій з відомою кількістю разів чи унаслідок виконання/невиконання умови.
Цікавимось
Як і розгалуження можуть бути вкладеними, також використовують вкладені цикли.
Розглянемо код ескізу, який малює на екрані задану кількість еліпсів за допомогою одного циклу for
.
function setup() {
createCanvas(200, 200);
}
function draw() {
background(57, 20, 99); // Persian indigo
fill(237, 34, 93); // Paradise Pink
noStroke();
let diameter = 50;
for (let i = 0; i < width / diameter; i = i + 1) {
ellipse(diameter / 2 + i * diameter, diameter / 2, diameter, diameter);
}
}
Переглядаємо Аналізуємо
Доповнимо попередній код застосунку вкладеним циклом для малювання кіл на всьому полотні.
function setup() {
createCanvas(200, 200);
}
function draw() {
background(57, 20, 99); // Persian indigo
fill(237, 34, 93); // Paradise Pink
noStroke();
let diameter = 50;
for (let i = 0; i < width / diameter; i = i + 1) { (1)
for (let j = 0; j < height / diameter; j = j + 1) { (2)
ellipse(
diameter / 2 + i * diameter,
diameter / 2 + j * diameter,
diameter,
diameter
);
}
}
}
1 | Зовнішній цикл. |
2 | Вкладений цикл. |
Переглядаємо Аналізуємо
Цикл називають вкладеним, якщо він міститься в тілі іншого циклу (його також називають внутрішнім), а цикл, у якому він міститься, - зовнішнім. Лічильники вкладених циклів змінюються так: спочатку змінюється лічильник внутрішнього циклу, набуваючи усіх своїх значень, а потім зовнішній цикл змінить значення на один крок і знову лічильник внутрішнього циклу набуде усіх своїх значень. Так триває доти, доки лічильник зовнішнього циклу не набуде усіх своїх значень. |
Розглянемо детальніше використання циклів і розгалужень на прикладі плиток Трюше.
Якщо розмістити плитки у вигляді квадратної мозаїки на площині, вони можуть утворювати чудові візерунки. Отож, спробуємо створити різні зразки візерунків на основі плиток.
Спочатку створимо одну плитку, що складається з двох кругових дуг радіусом, що дорівнює половині довжини сторони плитки з центром у протилежних кутах.
Така плитка буде мати дві модифікації.
function setup() {
createCanvas(1000, 1000);
}
function draw() {
background(0, 175, 185); // Maximum Blue Green
noFill();
stroke(255);
strokeWeight(5);
// плитка ліворуч
arc(200, 200, 300, 300, 0, PI / 2);
arc(500, 500, 300, 300, PI, PI * 1.5);
rect(200, 200, 300, 300);
// плитка праворуч
arc(600, 500, 300, 300, (3 * PI) / 2, 0);
arc(900, 200, 300, 300, PI / 2, PI);
rect(600, 200, 300, 300);
}

«Застелимо» полотно великою кількістю плиток з дугами для утворення візерунка.
Для цього спочатку, зменшимо розміри нашої плитки і помістимо її у верхній лівий кут полотна за допомогою такого коду:
arc(0, 0, 100, 100, 0, PI / 2);
arc(100, 100, 100, 100, PI, PI * 1.5);
У цьому разі, кожна плитка має розмір 100x100
пікселів. Для розміру полотна 1000x1000
пікселів, по горизонталі й вертикалі можна розмістити рівно по 10
(1000 / 100 = 10
) плиток відповідно.
Застосуємо цикл for
для заповнення полотна плитками, де x
і y
- значення координат побудови дуг на окремій плитці, а (0, PI / 2)
і (PI, PI * 1.5)
- значення кутів, поданих у радіанах, початку і кінця дуг на окремій плитці.
function setup() {
createCanvas(1000, 1000);
background(0, 175, 185); // Maximum Blue Green
noFill();
stroke(255);
strokeWeight(3);
}
function draw() {
for (let x = 0; x < 1000; x += 100) {
for (let y = 0; y < 1000; y += 100) {
arc(x, y, 100, 100, 0, PI / 2);
arc(x + 100, y + 100, 100, 100, PI, PI * 1.5);
}
}
}
Тут використано два цикли for
- зовнішній цикл (заповнення полотна плитками по горизонталі) і внутрішній цикл (заповнення полотна плитками по вертикалі).

Для наших цілей можна також використати лише один цикл for
. З міркувань вище щодо розмірів полотна й окремої плитки, для заповнення полотна плитками необхідно 100
(10 × 10
) плиток.
Отже, код для одного циклу for
і з аналогічним результатом виконання, як і для випадку двох циклів, матиме наступний вигляд:
function setup() {
createCanvas(1000, 1000);
background(0, 175, 185); // Maximum Blue Green
noFill();
stroke(255);
strokeWeight(3);
}
function draw() {
let x = 0; (1)
let y = 0;
for (let k = 1; k < 101; k++) { (2)
arc(x, y, 100, 100, 0, PI / 2); (3)
arc(x + 100, y + 100, 100, 100, PI, PI * 1.5);
x += 100; (4)
if (k % 10 == 0) { (5)
x = 0;
y += 100;
}
}
}
1 | Оголошення двох змінних x і y з початковими значеннями. |
2 | Прохід циклом for по усіх k -плитках, які помістяться на полотні (всього 100 ). Тут використана умова k < 101 , яка враховує те, що цикл припинить своє виконання, коли значення k буде дорівнювати 101 . |
3 | Побудова візерунка на плитці у формі двох дуг за допомогою функцій arc() . |
4 | Збільшення координати x на 100 при кожній ітерації - рух праворуч щоразу на 100 пікселів. |
5 | Такий код з вказівкою розгалуження if дозволяє переходити на новий рядок нижче (значення змінної x стає рівним 0 , а значення змінної y збільшується на 100 - відбувається переміщення нижче на полотні), коли поточний рядок буде заповнений плитками. Це дозволяє зробити операція порівняння остачі від ділення виразу k % 10 і 0 . Результат виразу k % 10 для значень змінної лічильника k циклу набуває значень, які представлено у таблиці. |
Значення лічильника k циклу |
Операція k % 10 |
Результат k % 10 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Модифікуємо наш візерунок за допомогою вказівки розгалуження if...else
і функцій:
function setup() {
createCanvas(1000, 1000);
background(0, 175, 185); // Maximum Blue Green
noFill();
stroke(255);
strokeWeight(3);
}
function draw() {
let x = 0;
let y = 0;
for (let k = 1; k < 101; k++) {
if (int(random(2))) { (1)
arc(x, y, 100, 100, 0, PI / 2);
arc(x + 100, y + 100, 100, 100, PI, PI * 1.5);
} else { (2)
arc(x + 100, y, 100, 100, PI / 2, PI);
arc(x, y + 100, 100, 100, PI * 1.5, TWO_PI);
}
x += 100;
if (k % 10 == 0) {
x = 0;
y += 100;
}
}
noLoop(); (3)
}
1 | Генерація випадкового числа з рухомою крапкою за допомогою функції random() з діапазону від 0 до 2 (не включаючи 2 ) і перетворення отриманого числа в ціле за допомогою функції int() . Тобто, результатом буде або число 0 , або число 1 . Якщо отримується 1 (інтерпретується як true ), то виконується код у фігурних дужках відразу після if . |
2 | Якщо отримується 0 (інтерпретується як false ) - виконується код у фігурних дужках після слова else . |
3 | Функція noLoop() зупиняє виконання коду у функції draw() . Її використовуємо, щоб зробити «знімок» візерунку. |

Наведемо ще кілька варіацій візерунків, використовуючи попередній код як зразок.
У першому варіанті у функції draw()
використані функції noStroke()
і fill()
.
function setup() {
createCanvas(1000, 1000);
background(0, 175, 185); // Maximum Blue Green
noFill();
stroke(255);
strokeWeight(3);
}
function draw() {
let x = 0;
let y = 0;
noStroke();
fill(255);
for (let k = 1; k < 101; k++) {
if (int(random(2))) {
arc(x, y, 100, 100, 0, PI / 2);
arc(x + 100, y + 100, 100, 100, PI, PI * 1.5);
} else {
arc(x + 100, y, 100, 100, PI / 2, PI);
arc(x, y + 100, 100, 100, PI * 1.5, TWO_PI);
}
x += 100;
if (k % 10 == 0) {
x = 0;
y += 100;
}
}
noLoop();
}

У другому варіанті у функцію draw()
додано більше умовних виразів і використані функції circle()
і line()
.
function setup() {
createCanvas(1000, 1000);
background(0, 175, 185); // Maximum Blue Green
noFill();
stroke(255);
strokeWeight(8);
}
function draw() {
let x = 0;
let y = 0;
for (let k = 1; k < 101; k++) {
let q = int(random(4));
if (q == 0) {
arc(x, y, 100, 100, 0, PI / 2);
arc(x + 100, y + 100, 100, 100, PI, PI * 1.5);
} else if (q == 1) {
arc(x + 100, y, 100, 100, PI / 2, PI);
arc(x, y + 100, 100, 100, PI * 1.5, TWO_PI);
} else if (q == 2) {
line(x + 50, y, x + 50, y + 100);
line(x, y + 50, x + 100, y + 50);
} else if (q == 3) {
line(x + 50, y, x + 50, y + 10);
line(x + 50, y + 90, x + 50, y + 100);
circle(x + 50, y + 50, 80);
line(x, y + 50, x + 10, y + 50);
line(x + 90, y + 50, x + 100, y + 50);
}
x += 100;
if (k % 10 == 0) {
x = 0;
y += 100;
}
}
noLoop();
}

Цікавимось
3.4.3. Ресурси
Корисні джерела
3.4.4. Контрольні запитання
Міркуємо Обговорюємо
-
Що таке «літерали»?
-
Яка алгоритмічна конструкція називається «лінійною»?
-
Що таке «розгалуження» і які має форми запису у мові
JavaScript
? -
Як будуються складені умови?
-
Визначити результат логічних виразів (
true
чиfalse
) у коді без його запуску, якщоx = 1
,y = 4
:
!(x > 4)
(x == 4 && x == 1)
(x == 4 || x == 1)
(x > 0 && y < 5)
(x > 7 && x < 5)
-
Що називають «циклом»? Які види циклів використовуються у мові
JavaScript
?
3.4.5. Практичні завдання
Початковий
-
Заповнити прогалини в коді для малювання мішені із концентричних кіл різного радіуса в центрі полотна.
let d = 0;
function setup() {
createCanvas(800, 600);
background(255);
}
function draw() {
while (...) {
stroke(0);
fill(d, 0);
circle(..., ..., d);
d = ... + 40;
}
}
-
Застосунок малює вертикальні лінії, як представлено у першій демонстрації. Змінити код, поданий нижче, так, щоб в результаті лінії малювалися горизонтально, як у другій демонстрації.
let x = 0;
function setup() {
createCanvas(200, 200);
background(255);
frameRate(5);
}
function draw() {
stroke(66, 54, 122); // Dark Slate Blue
line(x, 0, x, height);
x += 10;
if (x > width) {
x = 0;
}
}
-
Щоразу при запуску застосунку результат не змінюється - на екрані з’являється повідомлення
Error!
. З’ясувати, при яких значеннях змінноїoutput
на екрані буде повідомлення про успіх.
const fail = "Error!";
const hit = "Successfully!";
let output;
function setup() {
createCanvas(200, 100);
textSize(20);
}
function draw() {
fill(255);
if (output) {
background(0, 150, 0); // Slimy Green
text(hit, 40, 60);
} else {
background(255, 0, 0); // Red
text(fail, 73, 60);
}
}
Середній
-
Створити застосунок, який виводить на екран інформацію про навчальні успіхи студентів у формі повідомлень як на малюнку. В залежності від набраної кількості балів (шкала від
0
до100
) виводиться інший текст: якщо оцінка≥ 90
-Congratulations!
,≥ 80
-Good job!
,≥ 70
-Just okay.
,≥ 60
-Not good!
, інакше -Study more!
.

-
Створити застосунок, який створює горизонтальний градієнт в колірній системі
HSB
.

-
Створити застосунок, який фарбує кожен піксель полотна у випадковий колір.
-
Створити застосунок, який малює фігуру в залежності від числового значення змінної. Наприклад:
1
- коло,2
- еліпс,3
- прямокутник,4
- квадрат і т. д., а за стандартним налаштуванням - лише зафарбовує полотно певним кольором.
Для реалізації цієї задачі можна використати вказівку розгалуження switch .
|

Високий
-
Намалювати сітку, яка заповнює полотно, незалежно від розміру полотна. Розміри клітинок сітки -
10х10
пікселів.

-
Створити застосунок - генератор кіл. Орієнтовний результат представлений на малюнку.

-
Створити анімацію, у якій три квадрати різного розміру рухаються горизонтально, відбиваючись від меж полотна. Кожен із квадратів рухається з різною швидкістю.
-
Створити застосунок, який малює випадкове коло щоразу різного розміру. Якщо коло виходить за межі полотна, то малюються два кола: оригінальне коло і його копія, яка не виходить за межі полотна (дотикається до відповідної межі або меж). Орієнтовний результат представлений в демонстрації.
-
Створити застосунок, що на полотні малює квадрати, у які вписує кола. Орієнтовний результат представлений в демонстрації.
Екстремальний
-
Створити візерунок, використовуючи плитки Трюше, наприклад як на малюнку.

-
Створити текстову екранну заставку. Орієнтовний варіант заставки представлений в демонстрації.
Екранна заставка - це застосунок, який через деякий час простою комп’ютера замінює поточне зображення на моніторі іншим. |
-
Змоделювати рух м’яча, який відбивається від меж полотна. Розглянути випадки, коли м’яч в русі уповільнюється і прискорюється.
3.5. Інтерактивність
Інтерактивність (англ. Interaction - взаємодія) - поняття, яке розкриває характер і ступінь взаємодії між об’єктами.
|
Інтерактивність у цифровому мистецтві передбачає взаємодію як між об’єктами застосунку, так і глядача з твором художника. Для реалізації такої взаємодії код застосунку повинен виконуватися безперервно.
Безперервне виконання коду стає можливим, коли ми записуємо його у функцію draw()
.
function setup() {
createCanvas(500, 400);
frameRate(12);
}
function draw() {
background(220);
console.log("🐑", frameCount);
}
В результаті виконання коду в консолі вебпереглядача з’являються наступні рядки:
🐑 1
🐑 2
🐑 3
🐑 4
...
Код, записаний в блоці draw()
, виконується згори донизу, а потім повторюється до тих пір, поки не відбудеться вихід із застосунку через кнопку зупинки або закриття вікна вебпереглядача.
Одноразове виконання коду у функції draw() називається ітерацією. У контексті p5.js кожна така ітерація є кадром.
|
У функції setup()
за допомогою функції frameRate()
ми вказали кількість кадрів, які відображатимуться щосекунди, а функція console.log()
буде друкувати смайлик Emoji
🐑 і номер кадру за допомогою вбудованої змінної frameCount
.
За стандартним налаштуванням код у функції draw() виконується із частотою 60 кадрів за секунду.
|
У підсумку:
-
Код всередині функції
setup()
запускається лише один раз. -
Код всередині функції
draw()
працює безперервно - циклічно.
Інструкції у функції draw() виконуються у циклі та щоразу перемальовують усі об’єкти на полотні. Для глядача це виглядає статично, оскільки кожного разу малюється одне й те ж.
|
З огляду на те, як працює блок draw() , функції, що визначають налаштування, які не змінюються під час виконання застосунку для об’єктів полотна, оптимально записувати у блоці setup() .
|
3.5.1. Проста взаємодія
Розглянемо взаємодію із застосунком на прикладі роботи із вказівником миші й використаємо для цього вбудовані змінні mouseX та mouseY , які набувають значень координат горизонтального і вертикального положень вказівника миші відповідно.
Коли застосунок запускається, значення змінних mouseX
і mouseY
дорівнюють 0
. Якщо розпочати рухати мишею, то значення координат вказівника будуть змінюватися. За допомогою mouseX
та mouseY
їх можна отримати й надалі використовувати для взаємодії з об’єктами на полотні.
Вправа 22
Створити малюнок за допомогою переміщення вказівника миші, використовуючи поданий код.
function setup() {
createCanvas(500, 400);
fill(34, 124, 157, 100); // CG Blue
noStroke();
}
function draw() {
ellipse(mouseX, mouseY, 24, 24);
}
У цьому прикладі щоразове виконання коду в блоці draw()
створює на полотні нове коло. Оскільки зафарбовування кіл кольором CG Blue
є прозорим, темніші області показують місця, над якими вказівник миші знаходився більше часу і де він рухався повільніше.
Щоб відобразити лише найновіше коло, використаємо функцію background()
у функції draw()
перед тим, як намалювати фігуру.
function setup() {
createCanvas(500, 400);
fill(34, 124, 157, 100); // CG Blue
noStroke();
}
function draw() {
background(220);
ellipse(mouseX, mouseY, 24, 24);
}
Функція background() очищає полотно повністю, тому варто переконатися, що вона записана перед іншими функціями в блоці draw() , інакше фігури, намальовані функціями, що стоять перед нею, будуть стерті.
|
Вправа 23
Використати іншу фігуру для малювання вказівником миші.
Ці дві змінні містять попередні значення координат mouseX
та mouseY
розташування вказівника миші, тобто значення, де вказівник миші перебував востаннє.

Завдяки pmouseX
та pmouseY
відкриваються цікаві можливості взаємодії. Наприклад, якщо за допомогою функції line() провести лінію від попереднього розташування вказівника миші до поточного, за рухом вказівника миші малюється безперервна лінія.
Вправа 24
Використовуючи поданий код, написати своє ім’я за допомогою безперервної лінії.
function setup() {
createCanvas(500, 400);
background(255);
stroke(34, 124, 157); // CG Blue
}
function draw() {
line(pmouseX, pmouseY, mouseX, mouseY);
}
Щоб проілюструвати, як змінюється відстань між поточним та попереднім положеннями вказівника миші, обчислимо її, а отримане значення застосуємо для встановлення товщини лінії.
Для цих цілей використаємо функцію strokeWeight() , яка встановлює товщину лінії, і функцію dist() , яка обчислює відстань між двома точками у двовимірному або тривимірному просторах.
Вправа 25
Як змінюється товщина ліній при зміні швидкості переміщення вказівника миші?
function setup() {
createCanvas(500, 400);
stroke(34, 124, 157, 100); // CG Blue
}
function draw() {
let d = dist(mouseX, mouseY, pmouseX, pmouseY);
strokeWeight(d);
line(mouseX, mouseY, pmouseX, pmouseY);
}
Змінна mouseX
може набувати значень в межах від 0
до ширини полотна, однак діапазон значень mouseX
можна змінити за допомогою функції map() .
Перший параметр map()
- змінна, другий і третій - мінімальне і максимальне значення змінної, четверте і п’яте - бажане мінімальне і максимальне значення змінної.
З’ясуємо на прикладі інтерактивної зміни кольору полотна, як працює функція map()
.
function setup() {
createCanvas(200, 200); (1)
}
function draw() {
let g = map(mouseX, 0, width, 0, 255); (2)
background(0, g, 0); (3)
console.log(mouseX, g); (4)
}
1 | Створення полотна розміром 200х200 пікселів. |
2 | Оголошення змінної g і присвоєння їй значення із функції map() . Тут функція map() змінює поточний діапазон значень (від 0 до width ) змінної mouseX на діапазон від 0 (коли mouseX = 0 ) до 255 (коли mouseX = width ). |
3 | Зафарбовування полотна за допомогою функції background() . Змінна g , яка є зеленою складовою кольору, набуває значень x -координати вказівника миші, але вже із нового діапазону від 0 до 255 . |
4 | Виведення в консоль вебпереглядача значень x -координати вказівника миші у початковому (mouseX ) і зміненому (g ) діапазонах. |
Переглядаємо Аналізуємо
Розглянемо ще один інтерактивний приклад із вказівником миші. Цього разу вказівник миші буде рухати еліпс по екрану.
Розглянемо початковий код застосунку.
let mx = 0; // початкова x-координата малювання еліпса
let my = 0; // початкова y-координата малювання еліпса
let coming = 0.05; (1)
let radius = 24; (2)
function setup() {
createCanvas(500, 300);
ellipseMode(RADIUS); (3)
}
function draw() {
background(40, 54, 24); // Kombu Green
stroke(255);
line(0, height / 2, width, height / 2); (4)
line(width / 2, 0, width / 2, height); (5)
if (abs(mouseX - mx) > 0.1) { (6)
mx = mx + (mouseX - mx) * coming;
}
if (abs(mouseY - my) > 0.1) { (7)
my = my + (mouseY - my) * coming;
}
fill(170, 186, 120); // Olivine
ellipse(mx, my, radius, radius); (8)
}
1 | Оголошення змінної coming , значення якої буде впливати на плавність наближення еліпса до положення вказівника миші. |
2 | Оголошення змінної radius і присвоєння їй значення радіуса еліпса. |
3 | Функція ellipseMode() встановлює режим RADIUS для малювання еліпса, а саме, перші два параметри у функції ellipse() є координатами x та y центра еліпса, а третій та четвертий параметри вказують половину ширини та висоти еліпса. |
4 | Створення горизонтальної лінії, яка ділить полотно горизонтально порівну на дві частини за допомогою функції line() . |
5 | Створення вертикальної лінії, яка ділить полотно вертикально порівну на дві частини. |
6 | Вказівка розгалуження перевіряє чи відстань між поточною x -координатою і координатою вказівника миші mouseX є більшою за значення 0.1 . Якщо умова abs(mouseX - mx) > 0.1 є істиною, то відбувається плавний рух еліпса до точки, в якій перебуває вказівник миші, інакше рух припиняється. Функція abs() обчислює абсолютне значення виразу mouseX - mx . |
7 | Аналогічно, як у пункті 6, тільки для координат my і mouseY . |
8 | Малювання еліпса з координатами центру mx і my та радіусом radius . Оскільки третій і четвертий параметри є однаковими у функції ellipse() в результаті отримуємо коло. |
Якщо виконати наведений код, то отримаємо рух еліпса на екрані без обмежень за вказівником миші.
Переглядаємо Аналізуємо
Обмежимо рух еліпса лише у лівій верхній частині полотна. Тепер перемістити еліпс в будь-яке місце екрана не вийде, тому що рух еліпса буде обмежений прямокутною рамкою.
Для встановлення значення координат малювання еліпса лише у вказаній області полотна використаємо функцію constrain() , яка утворює діапазон для значення певної величини.
Код застосунку зараз матиме наступний вигляд (наведено фрагмент зі змінами):
...
function draw() {
...
// ліва верхня частина полотна
mx = constrain(mx, radius, width / 2 - radius);
my = constrain(my, radius, height / 2 - radius);
fill(170, 186, 120); // Olivine
ellipse(mx, my, radius, radius);
}
Переглядаємо Аналізуємо
Аналогічно можна обмежити рух кола і в інших областях полотна. Нижче наведені фрагменти коду, за допомогою яких можна це реалізувати.
// права верхня частина полотна
mx = constrain(mx, width / 2 + radius, width - radius);
my = constrain(my, radius, height / 2 - radius);
// ліва нижня частина полотна
mx = constrain(mx, radius, width / 2 - radius);
my = constrain(my, height / 2 + radius, height - radius);
// права нижня частина полотна
mx = constrain(mx, width / 2 + radius, width - radius);
my = constrain(my, height / 2 + radius, height - radius);
3.5.2. Полярні координати
До цього часу усі побудови фігур були реалізовані у прямокутній системі координат на площині, в якій точки визначалися парою координат (x, y)
.
Ці координати відомі як декартові координати, названі на честь відомого французького математика Рене Декарта , який розвинув ідеї декартового простору .
Використовуючи декартову систему координат на площині, геометричні примітиви можна описувати за допомогою алгебричних рівнянь, які містять координати точок, що належать примітиву.
Зазвичай нам немає потреби записувати ці рівняння в коді, оскільки вони описані всередині вбудованих функцій бібліотеки p5.js
. Наприклад, для обчислення відстані між двома точками, що мають координати (x1, y1)
і (x2, y2)
на площині, ми використовуємо із бібліотеки p5.js
функцію dist() .
Відстань d
між двома точками з координати (x1, y1)
і (x2, y2)
на площині можна обчислити також за допомогою рівняння:
Це рівняння є версією теореми Піфагора у декартовій системі координат. Теорема Піфагора встановлює співвідношення між сторонами прямокутного трикутника .

Формулювання цієї теореми багатьом відоме і звучить як квадрат гіпотенузи дорівнює сумі квадратів катетів та записується за допомогою рівняння:
Відношення сторін прямокутного трикутника можна описати й за допомогою тригонометричних функцій , які є важливим компонентом у програмуванні графіки.
Визначені для прямокутного трикутника тригонометричні функції є основним інструментом тригонометрії - розділу математики, який вивчає співвідношення між сторонами й кутами трикутників.
Тригонометрію використовують в будівництві та архітектурі, теорії музики, навігації, оптиці, астрономії, медицині (наприклад, в комп’ютерній томографії), аналізі фінансових ринків і звичайно у мистецтві та в комп’ютерній графіці. Багато досліджень природних явищ навколишнього світу базуються на тригонометричних властивостях.
Використовуючи тригонометричні функції, співвідношення між сторонами прямокутного трикутника на малюнку вище можна записати так (θ
, theta
- грецька літера тета):
\$cos(θ) = b / c, b = c * cos(θ), c = b / cos(θ)\$
Поруч з декартовою системою координат існує полярна система координат .
Полярна система координат задається променем, який називають полярною віссю. Будь-яка точка на площині у полярній системі координат визначається двома полярними координатами: радіальною r
та кутовою θ
.
Радіальна координата r
відповідає відстані від точки на площині до початку координат (полюса). Кутова координата θ
, що також зветься азимутом, дорівнює куту між полярною віссю та напрямком на точку.
Визначена таким чином радіальна координата може набувати значення від нуля до нескінченості, а кутова координата змінюється в межах від 0°
до 360°
, рухаючись по колу проти годинникового напрямку від позначки 0°
.
Для зручності діапазон значень кутової координати можна розширити за межі повного кута (360°
), а також дозволити їй набувати від’ємних значень, що відповідає повороту за годинниковим напрямком.

Однією з важливих особливостей полярної системи координат є те, що одна й та сама точка може бути представлена нескінченною кількістю способів. Це відбувається тому, що для визначення азимута точки потрібно повернути полярну вісь таким чином, щоб він вказував на точку. Якщо здійснити довільну кількість додаткових повних обертів, напрям на точку не зміниться.
Загалом точка з полярними координатами (r, θ)
може бути представлена у вигляді
або
де n
- довільне ціле число.
Хоча відношення між точками найпростіше зобразити у вигляді відстаней та кутів, використання полярних координат у вбудованих функціях бібліотеки p5.js
не є можливим. У цьому разі полярні координати перетворюють у декартові.
Якщо відомий радіус r
та кут θ
, декартові координати x
та y
точки можна обчислити за допомогою формул:
\$cos(theta) = x / r, x = r * cos(theta)\$
Як видно, ці перетворення можна виконувати завдяки тригонометричним функціям sin() і cos() , які входять до складу бібліотеки p5.js
.
Кожна із наведених функцій отримує один аргумент - значення кута у радіанах за стандартним налаштуванням, а результатами обчислення функцій sin()
і cos()
є значення з рухомою крапкою в діапазоні від –1
до 1
.
За допомогою функції angleMode() та констант DEGREES і RADIANS можна змінювати режим обчислення значення кутів. |
Розглянемо приклад використання полярних координат і перетворення їх у декартові координати.
Створимо застосунок, у якому точка буде рухатися по колу. Для зручності аналізу руху точки намалюємо на полотні коло і горизонтальну й вертикальну осі.
let x, y; // декартові координати
// початкові значення полярних координат
let r = 150;
let theta = 0;
function setup() {
createCanvas(500, 400);
ellipseMode(RADIUS);
}
function draw() {
// тло
background("#2D3142"); // Space Cadet
// осі на полотні
strokeWeight(3);
stroke("#4F5D75"); // Black Coral
line(width / 2, height, width / 2, 0);
line(0, height / 2, width, height / 2);
// коло
noFill();
circle(width / 2, height / 2, r);
// перехід від полярних у декартові координати
x = r * cos(theta);
y = r * sin(theta);
// малювання точки на колі
noStroke();
fill("#FFFFFF"); // White
ellipse(x + width / 2, height / 2 - y, 5, 5);
// збільшення значення кута повороту
theta += 0.01;
}
Переглядаємо Аналізуємо
Функція ellipse()
малює точку у вказаних координатах на лінії кола, а кут повороту theta
щоразу визначає нове місце для малювання. Так ми спостерігаємо імітацію руху проти годинникового напрямку.
Такий рух називається циклічним, оскільки він повторюється (точка проходить ті самі положення) через певні проміжки часу. Час, необхідний для завершення одного такого циклу (оберту у цьому разі) називається періодом.
Прикладами циклічних рухів є: рух стрілки механічного годинника, рух поршня в циліндрі двигуна автомобіля, рух супутників навколо планети тощо.
Якщо у коді використати інструкцію для зменшення значення кута повороту theta -= 0.01;
, рух відбуватиметься за годинниковим напрямком.
Вправа 26
Збільшити/зменшити швидкість руху точки.
У підсумку, розглянемо код застосунку, який ілюструє застосування полярних і декартових координат.
let r = 150; // радіус кола
let theta = 1; // початкове значення кута theta в радіанах
let x, y, g, pointX, pointY;
function setup() {
createCanvas(500, 400);
ellipseMode(RADIUS);
}
function draw() {
// тло
background("#2D3142"); // Space Cadet
// осі на полотні
strokeWeight(3);
stroke("#4F5D75"); // Black Coral
line(width / 2, height, width / 2, 0);
line(0, height / 2, width, height / 2);
// коло
noFill();
circle(width / 2, height / 2, r);
// співвідношення у прямокутному трикутнику:
// x - прилеглий катет, y - протилежний катет, r - гіпотенуза
x = r * cos(theta);
y = r * sin(theta);
// координати точки на колі
pointX = width / 2 + x;
pointY = height / 2 - y;
// гіпотенуза
stroke("#FFFFFF"); // White
line(width / 2, height / 2, width / 2 + x, height / 2 - y);
// протилежний катет
stroke("#EF8354"); // Mandarin
line(width / 2 + x, height / 2 - y, width / 2 + x, height / 2);
// прилеглий катет
stroke("#FFD23F"); // Sunglow
line(width / 2, height / 2, width / 2 + x, height / 2);
// малювання дуги кута theta
noFill();
stroke("#D8315B"); // Cerise
if (pointX > width / 2 && pointY < height / 2) {
arc(width / 2, height / 2, 20, 20, TWO_PI - theta, TWO_PI);
} else if (pointX < width / 2 && pointY < height / 2) {
arc(width / 2, height / 2, 20, 20, PI, TWO_PI - theta);
} else if (pointX < width / 2 && pointY > height / 2) {
arc(width / 2, height / 2, 20, 20, TWO_PI - theta, PI);
} else if (pointX > width / 2 && pointY > height / 2) {
arc(width / 2, height / 2, 20, 20, 0, TWO_PI - theta);
}
// напис значення кута theta
strokeWeight(1);
textSize(12);
text("theta = " + theta + " рад", 20, 20);
// побудова точки на колі
noStroke();
fill("#FFFFFF"); // White
circle(pointX, pointY, 5);
// обчислення значення кута theta у градусах
// g = (theta * 180) / PI;
// друк в консолі значення кута в градусах і координат точки
// console.log(g);
// console.log(pointX, pointY);
}
У результаті виконання застосунку для вказаного кута theta
буде будуватися дуга світло-вишневого кольору (Cerise
), прямокутний трикутник з кутом theta
зі сторонами: протилежним катетом помаранчевого кольору (Mandarin
), прилеглим катетом жовтого кольору (Sunglow
) і гіпотенузою білого кольору (White
).

Отож, змусьмо точку на колі рухатися вздовж лінії кола.
Щоб це реалізувати, значення кута theta
має змінюватися при виконанні застосунку. Запишемо на початку функції draw()
інструкцію, яка буде щоразу збільшувати значення theta
:
function draw() {
theta += 0.02;
...
}
Переглядаємо Аналізуємо
У разі, коли буде змінюватися значення радіуса колової траєкторії, точка буде рухатися по спіралі. Змінимо код для реалізації цієї задачі.
...
let k = 1; (1)
function setup() {
createCanvas(500, 400);
ellipseMode(RADIUS);
}
function draw() {
k += 0.4; (2)
theta += 0.02;
...
// співвідношення у прямокутному трикутнику:
// x - прилеглий катет, y - протилежний катет, r - гіпотенуза
x = k * cos(theta); (3)
y = k * sin(theta);
...
}
1 | Оголошуємо нову змінну k , яка слугуватиме лічильником, який збільшує значення радіуса обертання точки. |
2 | Значення змінної k збільшується на величину 0.4 у циклі функції draw() . |
3 | Для визначення координат точки підставляється щоразу нове значення k . |
Переглядаємо Аналізуємо
Змінимо код застосунку так, щоб відтворити лінію траєкторії точки, відкинувши усі інші елементи.
let r = 150; // радіус кола
let theta = 1; // початкове значення кута theta в радіанах
let x, y, pointX, pointY;
let k = 1;
function setup() {
createCanvas(500, 400);
ellipseMode(RADIUS);
// тло
background("#2D3142"); // Space Cadet
// осі на полотні
strokeWeight(3);
stroke("#4F5D75"); // Black Coral
line(width / 2, height, width / 2, 0);
line(0, height / 2, width, height / 2);
}
function draw() {
// радіус обертання
k += 0.4;
// завдяки збільшенню значення кута точка рухається по криволінійній траєкторії
theta += 0.02;
// коло
noFill();
circle(width / 2, height / 2, r);
// координати точки на колі
x = cos(theta) * k;
y = sin(theta) * k;
pointX = width / 2 + x;
pointY = height / 2 - y;
// побудова точки на колі
noStroke();
fill("#FFBD00"); // Amber
circle(pointX, pointY, 5);
}
Частину коду ми перемістили у функцію setup()
, щоб на полотні залишався слід траєкторії руху і змінили значення кольору на Amber
для лінії траєкторії.
Переглядаємо Аналізуємо
Знову змінимо наш код, щоб залишити на полотні лише точку, яка рухається.
let r = 150; // радіус кола
let theta = 1; // початкове значення кута theta в радіанах
let x, y, pointX, pointY;
let k = 1;
function setup() {
createCanvas(500, 400);
ellipseMode(RADIUS);
}
function draw() {
// радіус обертання
k += 0.4;
// завдяки збільшенню значення кута точка рухається по криволінійній траєкторії
theta += 0.02;
// тло
background("#2D3142"); // Space Cadet
// осі на полотні
strokeWeight(3);
stroke("#4F5D75"); // Black Coral
line(width / 2, height, width / 2, 0);
line(0, height / 2, width, height / 2);
// коло
noFill();
circle(width / 2, height / 2, r);
// координати точки на колі
x = cos(theta) * k;
y = sin(theta) * k;
pointX = width / 2 + x;
pointY = height / 2 - y;
// побудова точки на колі
noStroke();
fill("#FFFFFF"); // White
circle(pointX, pointY, 5);
}
Переглядаємо Аналізуємо
Вправа 27
Реалізувати спіральний рух точки до центру кола і зупинки точки в центрі.
Для руху точки по еліптичній траєкторії необхідно використовувати два значення, які будуть визначати ширину і висоту еліптичної орбіти, по суті, висоту і ширину еліпса, вздовж лінії якого відбуватиметься рух.
Оголосимо ще дві змінні h
і v
, які будуть містити значення висоти й ширини еліптичної орбіти.
let r = 150; // радіус кола
let theta = 1; // початкове значення кута theta в радіанах
let x, y, pointX, pointY;
let h = 1; // початкове значення ширини еліптичної орбіти
let v = 1; // початкове значення висоти еліптичної орбіти
function setup() {
createCanvas(500, 400);
ellipseMode(RADIUS);
}
function draw() {
// зміна характеристик еліптичної орбіти
h += 0.4;
v += 0.2;
// завдяки збільшенню значення кута точка рухається по криволінійній траєкторії
theta += 0.02;
// тло
background("#2D3142"); // Space Cadet
// осі на полотні
strokeWeight(3);
stroke("#4F5D75"); // Black Coral
line(width / 2, height, width / 2, 0);
line(0, height / 2, width, height / 2);
// коло
noFill();
circle(width / 2, height / 2, r);
// координати точки на колі
x = cos(theta) * h;
y = sin(theta) * v;
pointX = width / 2 + x;
pointY = height / 2 - y;
// побудова точки на колі
noStroke();
fill("#FFFFFF"); // White
circle(pointX, pointY, 5);
}
Переглядаємо Аналізуємо
Вправа 28
Реалізувати рух точки по еліптичній орбіті вертикально.
Функція синуса sin()
, яку ми використовували для перетворення полярних координат у декартові, зазвичай застосовується в моделюванні періодичних явищ, таких як світлові та звукові хвилі, коливання середньої температури протягом року, коливання в молекулах, якими зумовлене поглинання інфрачервоних променів, коливання вантажу на пружині, різноманітні коливання в електротехніці тощо.
Коливальні процеси характерні для величезної кількості явищ в навколишньому світі, для яких спостерігається певна повторюваність у часі.
Візуальне представлення (графік) функції синуса sin()
називається синусоїдою. Зобразимо цей графік як сукупність еліпсів.
Переглядаємо Аналізуємо
У застосунку ми спостерігаємо гармонічне коливання точки помаранчевого кольору Melon
, при поширенні хвилі у просторі. Усі точки хвилі здійснюють такі коливання.
Гармонічними коливаннями називаються періодичні коливання певної величини залежно від часу, які відбуваються згідно із законами синуса або косинуса. |
Відстань між двома послідовними гребенями (чи западинами) хвилі називають довжиною хвилі, а найбільше значення, яке приймає величина y
під час коливань, називається амплітудою і дорівнює r
- радіусу намальованого на полотні кола.
Ще однією характеристикою коливань є частота (ν
), яка пов’язана із періодом коливань (T
) співвідношенням:
Тепер проаналізуємо код нашого застосунку, де насамперед оголошуємо глобальні змінні.
let r = 150; // радіус кола
let theta = 0; // початкове значення кута theta в радіанах
let x = 0, y = 0; // початкові значення координат точок графіка
let n = 100; // кількість точок на графіку
let k = 1; // кількість хвиль
let w = 0; // циклічна частота
let pointX, pointY; // координати точки, що коливається вертикально
let angle;
function setup() {
createCanvas(500, 400);
ellipseMode(RADIUS);
}
function draw() {
// тло
background("#2D3142"); // Space Cadet
// осі на полотні
strokeWeight(3);
stroke("#4F5D75"); // Black Coral
line(width / 2, height, width / 2, 0);
line(0, height / 2, width, height / 2);
// коло
noFill();
circle(width / 2, height / 2, r);
// синусоїда
noStroke();
theta += 0.01;
for (let i = 0; i < n; i++) { (1)
w = 0.062 * k; (2)
angle = w * i + theta; (3)
x = i * 5; // 5, 25, 50 (4)
y = r * sin(angle);
if (x === width / 2) { (5)
fill("#FFB4A2"); // Melon
} else {
fill("#FFFFFF"); // White
}
circle(x, y + height / 2, 5);
}
}
1 | У циклі for проходимо по кожній точці майбутнього графіка, кількість яких визначена у змінній n . |
2 | Підбираємо певне числове значення, наприклад, 0.062 для циклічної частоти w , що визначає кількість повторень коливань або обертань, які здійснюються за фіксований період часу, так, щоб у змінній k вказувати ціле число хвиль на графіку, які поміщаються на полотні у ширину. |
3 | Формуємо значення кута angle для обчислення у функції sin() із кута theta , який щоразу збільшується на величину 0.01 , значення лічильника циклу i , значення якого збільшується на одиницю з плином часу і величини w . |
4 | Обчислюємо декартові координати (x, y) точок графіка. Координата x визначається добутком значення лічильника циклу i на числовий коефіцієнт (5 , 25 , 50 обрані не випадково, а для малювання кольорової точки, що коливається вертикально). |
5 | Якщо координата x точки збігається з центром полотна, зафарбовуємо точку кольором Melon і додаємо її на графік, інакше - просто малюємо точку білого кольору. |
У підсумку, коли хвиля поширюється у просторі горизонтальні координати розташування точок не змінюються, а кожна точка здійснює вертикальні гармонічні коливання.
Типовий графік тригонометричної функції синус можна побудувати, скориставшись вбудованою функцією sin() бібліотеки p5.js
.
let theta = 0.0; (1)
function setup() {
createCanvas(470, 200);
}
function draw() {
background(31, 32, 65); // Space Cadet
theta += 0.014; (2)
fill(0);
let angle = theta; (3)
for (let x = 0; x <= width; x += 8) { (4)
let y = map(sin(angle), -1, 1, 0, height); (5)
stroke(17, 157, 164); // Viridian Green (6)
strokeWeight(5); (7)
point(x, y); (8)
angle += 0.1; (9)
}
}
1 | Встановлення початкового значення кута theta . |
2 | Інкремент theta . Величина інкременту визначає швидкість появи наступних відрізків графіка синусоїди. Отже, можна спостерігати ефект переміщення графіка. Чим більша величина збільшення, тим більша швидкість і навпаки. Якщо значення theta дорівнює 0 - графік є статичним. |
3 | Оголошення змінної angle з початковим значенням theta , значення якої обчислюється у функції sin() . |
4 | Цикл for використовується для малювання всіх точок уздовж синусоїди. |
5 | Обчислення значення y -координати, виходячи з функції синуса. Тут використовується функція map() , яка перетворює діапазон значень від -1 до 1 для sin(angle) в новий діапазон від 0 до height . Отож, амплітуда графіка буде дорівнювати висоті полотна. |
6 | Встановлення кольору точки. |
7 | Встановлення товщини точки. |
8 | Створення точки з координатами (x, y) . |
9 | Інкремент значення кута в циклі. |
Переглядаємо Аналізуємо
3.5.3. Ресурси
Корисні джерела
3.5.4. Контрольні запитання
Міркуємо Обговорюємо
-
Що таке «інтерактивність»?
-
Як визначити координати вказівника миші на полотні?
-
Як визначаються координати в полярній системі координат?
-
Для чого використовують функції
map()
іconstrain()
?
3.5.5. Практичні завдання
Початковий
-
Змінити код, щоб за допомогою вказівника миші можна було б малювати на полотні.
function setup() {
createCanvas(200, 200);
noStroke();
}
function draw() {
background(25, 100, 126); // // Blue Sapphire
ellipse(mouseX, mouseY, 15, 15);
}
-
Заповнити прогалини в коді, щоб частини розділеного полотна реагували на вказівник миші.
function setup() {
createCanvas(200, 200);
noStroke();
fill(217, 197, 178); // Desert Sand
}
function draw() {
background(20, 17, 15); // Smoky Black
if (...) {
rect(0, ..., width / 2, ...); // ліворуч
} else {
rect(..., ..., ..., height); // праворуч
}
}
Середній
-
Створити застосунок, в якому усі три частини розділеного полотна реагують на вказівник миші - змінюється колір тла.
-
Створити застосунок, в якому полотно вертикально розділяється порівну на дві частини лінією. Якщо вказівник миші перебуває ліворуч від лінії розділу - колір полотна поступово стає чорним, якщо праворуч - поступово набуває максимального значення однієї зі складових
RGB
. Кольорову складову для цього ефекту обрати на свій вибір.
-
Створити застосунок, в якому, залежно від розташування вказівника миші, відображається відповідний квадрат.
-
Створити застосунок, в якому коливаються дві точки вздовж горизонтальної й вертикальної осей.
Високий
-
Створити застосунок, який малює еліпс, колір і розмір якого змінюється інтерактивно - за рухом вказівника миші як представлено у демонстрації.
-
Створити застосунок, у якому рух еліпса за вказівником миші обмежений прямокутною рамкою як представлено в демонстрації.
Екстремальний
-
Створити симуляцію коливання вантажу на пружині, яке затухає. При наведенні вказівника миші на вантаж коливання знову починаються.
-
Створити застосунок, який на полотні зображує графіки двох тригонометричних функцій: sin() і cos() . За допомогою вказівника миші графіками можна інтерактивно керувати - змінювати їхнє положення: амплітуда функції синуса регулюється змінною
mouseY
, а амплітуда функції косинуса -mouseX
. Для розуміння процесів, що відбуваються, переглянути демонстрацію.
3.6. Обробка подій
Дії користувача, як от натискання кнопок миші чи клавіш на клавіатурі, визначаються як події.
Якщо необхідно, наприклад, змінити колір тла при натисканні кнопки миші, то пишуть блок коду для обробки цієї події. Цей блок коду називається обробником події. Він повідомляє застосунку, який код виконувати, коли подія настає.
Спробуймо розібратися як обробляються такі події, використовуючи можливості бібліотеки p5.js
.
3.6.1. Миша
Багато прикладів інтерактивності можна реалізувати разом з вказівкою розгалуження if
і системними змінними бібліотеки p5.js
, які відстежують поведінку миші.
Цікавимось
Розглянемо код простого застосунку, який змінює колір полотна, якщо натиснути будь-яку кнопку миші.
function setup() {
createCanvas(200, 200);
}
function draw() {
if (mouseIsPressed) {
background(181, 131, 141); // Burnished Brown
} else {
background(109, 104, 117); // Old Lavender
}
}
Переглядаємо Аналізуємо
Доки кнопка миші натиснута, змінна mouseIsPressed
набуває значення true
і виконується гілка if
- функція background()
для тла полотна встановлює колір Burnished Brown
. Як тільки кнопка миші буде відпущена - значення mouseIsPressed
стане false
і виконується гілка else
- встановлюється інший колір тла - Old Lavender
.
Використовуючи будь-яку функцію для створення примітивів, можна перетворити вказівник миші на інструмент для малювання.
function setup() {
createCanvas(200, 200);
background("#2A2A2A"); // Jet
}
function draw() {
fill("#00F4D3"); // Sea Green Crayola
noStroke();
if (mouseIsPressed) {
circle(mouseX, mouseY, 10);
}
}
Переглядаємо Аналізуємо
Бібліотека p5.js
надає багато функцій, які вона автоматично викликає, коли трапляється подія. Якщо написати певний код всередині цих функцій, він буде запускатися, коли ці події настають.
Ось деякі з таких функцій:
-
mousePressed() - викликається один раз, коли користувач натискає кнопку миші.
-
mouseReleased() - викликається один раз, коли користувач відпускає кнопку миші.
-
mouseClicked() - викликається один раз після натискання і відпускання кнопки миші.
-
mouseMoved() - викликається щоразу, коли вказівник миші рухається і кнопка миші не натискається.
-
mouseDragged() - викликається щоразу, коли вказівник миші рухається під час натиснутої кнопки миші.
-
mouseWheel() - викликається під час прокрутки за допомогою колеса миші.
Вправа 29
Виконати код і перевірити твердження: Після запуску тло має сірий колір. Коли користувач натискає кнопку миші, тло стає червоним, а коли відпускає кнопку - жовтим. Тло стає синього кольору, коли користувач перетягує вказівник миші із натиснутою кнопкою.
let r = 52;
let g = 52;
let b = 52;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(r, g, b);
}
function mousePressed() {
r = 245;
g = 0;
b = 0;
}
function mouseReleased() {
r = 245;
g = 245;
b = 0;
}
function mouseDragged() {
r = 0;
g = 245;
b = 0;
}
Функції подій, як от mousePressed() та інші, записуються окремо поза межами функцій setup() і draw() , але за допомогою аналогічного синтаксису.
|
Об’єднаємо в одному застосунку використання змінної mouseIsPressed
і функції mouseClicked()
.
function setup() {
createCanvas(200, 200);
background(45);
}
function draw() {
if (mouseIsPressed) {
fill(255, 210, 63); // Sunglow
circle(mouseX, mouseY, 30);
}
}
function mouseClicked() {
fill(14, 173, 105); // GO Green
circle(mouseX, mouseY, 15);
}
Усередині функції draw()
(яка викликається 60
разів за секунду) виконується перевірка значення змінної mouseIsPressed
і, якщо кнопка миші натиснута, під вказівником миші малюється велике жовте коло.
Усередині функції mouseClicked()
(яка викликається щоразу, коли користувач натискає кнопку миші), під вказівником миші малюється маленьке зелене коло. Інакше кажучи, користувач може затиснути кнопку миші, щоб намалювати велике жовте коло, або натиснути мишею, щоб намалювати маленьке зелене коло.
Якщо потрібно дізнатися, яка кнопка миші натиснута, перевіряють змінну mouseButton
.
Змінна mouseButton
приймає три значення: LEFT
, CENTER
і RIGHT
. Щоб визначити, яка з кнопок миші натиснута, використовують оператор порівняння ===
.
Вправа 30
Виконати код для перегляду в консолі вебпереглядача інформації про натиснуту кнопку миші.
function setup() {
createCanvas(100, 100);
}
function draw() {
background(237, 34, 93); // Paradise Pink
fill(0);
if (mouseIsPressed) {
if (mouseButton === LEFT) {
ellipse(50, 50, 50, 50);
}
if (mouseButton === RIGHT) {
rect(25, 25, 50, 50);
}
if (mouseButton === CENTER) {
triangle(23, 75, 50, 20, 78, 75);
}
}
console.log(mouseButton);
}
Застосуємо знання про змінні, події й вказівку розгалуження, створивши прототип графічного редактора для малювання вказівником миші.
let s; (1)
let r = 0; (2)
let g = 0; (3)
let b = 0; (4)
function setup() {
createCanvas(500, 400); (5)
background(255); (6)
}
function draw() {
if (mouseX > width / 2) { (7)
r = r + 1;
g = g - 1;
} else {
r = r - 1;
g = g + 1;
}
if (mouseY > height / 2) { (8)
b = b + 1;
g = g - 1;
} else {
b = b - 1;
g = g + 1;
}
if (mouseIsPressed) { (9)
if (mouseButton === CENTER) { (10)
background(255);
} else if (mouseButton === LEFT) { (11)
fill(r, g, b, 135);
s = random(10, 100);
ellipse(mouseX, mouseY, s, s);
} else if (mouseButton === RIGHT) { (12)
stroke(0);
line(mouseX, mouseY, pmouseX, pmouseY);
}
}
r = constrain(r, 0, 255); (13)
g = constrain(g, 0, 255); (14)
b = constrain(b, 0, 255); (15)
}
Переглядаємо Аналізуємо
1 | Оголошення глобальної змінної s , що зберігатиме розмір кола. |
2 | Оголошення глобальної змінної, що зберігатиме червону складову кольору. Початкове значення 0 . |
3 | Оголошення глобальної змінної, що зберігатиме зелену складову кольору. Початкове значення 0 . |
4 | Оголошення глобальної змінної, що зберігатиме синю складову кольору. Початкове значення 0 . |
5 | Створення полотна розміром 500x400 . |
6 | Зафарбовування полотна білим кольором. |
7 | Якщо вказівник миші знаходиться праворуч від точки width / 2 екрану, то значення кольору r збільшується на 1 , а кольору g - зменшується на 1 , якщо ліворуч - навпаки. |
8 | Якщо вказівник миші знаходиться вище від точки height / 2 екрану, то значення кольору b зменшується на 1 , а кольору g - збільшується на 1 , якщо нижче - навпаки. |
9 | Якщо будь-яка кнопка миші натиснута (mouseIsPressed === true ), то виконати вкладене розгалуження. |
10 | Якщо натиснута середня кнопка миші (змінна mouseButton має значення CENTER ), то зафарбувати полотно білим кольором. |
11 | Якщо натиснута ліва кнопка миші (змінна mouseButton має значення LEFT ), то встановити колір зафарбовування (r, g, b, 135) , у змінну s зберегти випадкове значення із діапазону [10, 100) для розміру еліпса по ширині й висоті (значення однакові, тому у результаті буде малюватися коло), намалювати еліпс у місці розташування вказівника миші. |
12 | Якщо натиснута права кнопка миші (змінна mouseButton має значення RIGHT ), то намалювати лінію чорного кольору у місці розташування вказівника миші. |
13 | Встановлення обмеження значення кольору r у діапазоні від 0 до 255 включно. |
14 | Встановлення обмеження значення кольору g у діапазоні від 0 до 255 включно. |
15 | Встановлення обмеження значення кольору b у діапазоні від 0 до 255 включно. |
Функція constrain() визначає для наданого їй значення діапазон зміни його від мінімального значення до максимального значення. |
Вправа 31
Створити малюнок за допомогою прототипа графічного редактора. Зробити скриншот малюнка і зберегти.
Розглянемо ще один приклад інтерактивності, в якому при наведенні вказівника миші на певний об’єкт полотна об’єкт зреагує і змінить свої властивості. При відведенні вказівника від області об’єкта - об’єкт повертається у свій початковий стан.
Для прикладу, таким об’єктом буде коло, а властивість, яка буде змінюватись при наведенні вказівника миші - колір межі кола.
Отже, маємо отримати такий результат: як тільки вказівник миші потрапляє в область кола, межа кола підсвічується іншим кольором, а коли вказівник миші виходить за межі кола це підсвічування зникає. Тобто, необхідно перевірити, чи точка, в якій перебуває вказівник миші, міститься усередині кола.
Пригадаємо, що якщо точка належить колу, то її координати задовольняють рівнянню кола
де (a, b)
- координати центра кола, (x, y)
- координати будь-якої точки кола.
У цьому разі необхідно перевірити не лише належність точки кола, але й чи точка міститься усередині кола, тому рівняння кола перетворюється на нерівність:
Щоб з’ясувати, чи належить області кола точка із заданими координатами (точка, у якій перебуває вказівник миші), потрібно підставити координати точки в нерівність і перевірити істинність отриманої нерівності.
Поглянемо на код, який реалізує цю задачу.
let cx;
let cy;
let cSize = 75;
let h;
function setup() {
createCanvas(200, 200);
cx = width / 2;
cy = height / 2;
strokeWeight(4);
}
function draw() {
background(48, 52, 63); // Gunmetal
h = (mouseX - cx) * (mouseX - cx) + (mouseY - cy) * (mouseY - cy); (1)
if (h <= ((cSize / 2) * cSize) / 2) { (2)
stroke(255, 217, 218); // Pale Pink
fill(153);
} else {
stroke(153);
fill(153);
}
circle(cx, cy, cSize);
}
1 | Обчислення лівої частини нерівності, де cx і cs - координати центра кола, mouseX і mouseY - координати вказівника миші. |
2 | Обчислення правої частини нерівності й порівняння обох частин нерівності. В залежності від значення порівняння (true чи false ), виконуються відповідні гілки вказівки розгалуження. |
Переглядаємо Аналізуємо
Додамо у наш застосунок ще більше інтерактивності, а саме, перетягування кола на полотні за допомогою вказівника миші згідно з алгоритмом: натиснути мишею на коло, затиснути кнопку миші та перетягнути коло в інше місце полотна, відпустити кнопку миші.
Перетягування (Drag & Drop ) - форма виконання певних дій у графічних інтерфейсах. У дослівному перекладі з англійської означає «тягни та кинь».
|
Для цього завдання застосуємо функції подій mousePressed()
, mouseDragged()
і mouseReleased()
.
let cx;
let cy;
let cSize = 75;
let overCircle = false; (1)
let locked = false; (2)
let xOffset = 0.0;
let yOffset = 0.0;
let h;
function setup() {
createCanvas(200, 200);
cx = width / 2.0;
cy = height / 2.0;
strokeWeight(4);
}
function draw() {
background(48, 52, 63); // Gunmetal
h = (mouseX - cx) * (mouseX - cx) + (mouseY - cy) * (mouseY - cy);
if (h <= ((cSize / 2) * cSize) / 2) {
overCircle = true;
if (!locked) {
stroke(255, 217, 218); // Pale Pink
fill(153);
}
} else {
stroke(153);
fill(153);
overCircle = false;
}
circle(cx, cy, cSize);
}
function mousePressed() {
if (overCircle) {
locked = true;
fill(255, 255, 255); // White
} else {
locked = false;
}
xOffset = mouseX - cx; (3)
yOffset = mouseY - cy;
}
function mouseDragged() {
if (locked) {
cx = mouseX - xOffset; (4)
cy = mouseY - yOffset;
}
}
function mouseReleased() {
locked = false; (5)
}
1 | У змінній overCircle буде зберігатися значення true чи false - відповідь на запитання чи перебуває вказівник миші над колом. |
2 | Ще одна булева змінна locked - якщо вказівник миші перебуває в області кола і була натиснута кнопка миші набуває значення true і при переміщенні кола на полотні обчислюються нові координати cx і cy кола. |
3 | Обчислення значень зміщень xOffset і yOffset при натиснутій кнопці миші. Ці значення використовуються для обчислення нових координат кола. |
4 | Обчислення нових значень координат кола при переміщенні кола на полотні. |
5 | Змінна locked змінює значення на false при відпусканні кнопки миші. |
Переглядаємо Аналізуємо
3.6.2. Клавіатура
Подібно до того, як змінна mouseIsPressed
щоразу набуває значення true
, коли користувач натискає кнопку миші, для подій клавіатури використовується змінна keyIsPressed , яка щоразу набуває значення true
, коли користувач натискає будь-яку клавішу на клавіатурі.
Використаємо змінну keyIsPressed
у вказівці розгалуження if
.
function setup() {
createCanvas(200, 200);
}
function draw() {
if (keyIsPressed) {
background(252, 221, 188); // Bisque
} else {
background(105, 88, 95); // Deep Taupe
}
}
Змінні mouseIsPressed і keyIsPressed не приймають інших значень, окрім значень true і false . Це значить, що записувати порівнювання як mouseIsPressed === true чи keyIsPressed === true немає потреби.
|
Відстежувати можна не лише натиснення клавіш на клавіатурі, але і яка із клавіш була натиснута останньою.
Для цього можна використати змінну key , яка містить символ останньої натиснутої клавіші.
function setup() {
createCanvas(200, 200);
textAlign(CENTER, CENTER);
}
function draw() {
background(76, 76, 76); // Davys Grey
fill(219, 219, 219); // Gainsboro
textSize(28);
text(key, width / 2, height / 2);
}
Переглядаємо Аналізуємо
Для відстежування подій натиснення клавіш на клавіатурі можна використовувати функцію keyIsDown() , яка перевіряє, чи зараз певна клавіша натиснута.
Ця функція є корисною у разі, коли необхідно впливати на поведінку об’єкта кількома клавішами, наприклад, переміщувати об’єкт на полотні у різних напрямках.
Функція keyIsDown()
отримує число, яке відповідає значенню keyCode для клавіші. Наприклад, для спеціальних клавіш змінна keyCode
може набувати таких значень:
-
BACKSPACE
, -
DELETE
, -
ENTER
, -
RETURN
, -
TAB
, -
ESCAPE
, -
SHIFT
, -
CONTROL
, -
OPTION
, -
ALT
, -
UP_ARROW
, -
DOWN_ARROW
, -
LEFT_ARROW
, -
RIGHT_ARROW
.
Перевірити числові коди для клавіш клавіатури можна на сайті JavaScript Key Code . |
Розглянемо застосунок, у кому можна керувати об’єктом на полотні за допомогою клавіатури.
let x = 100;
let y = 100;
function setup() {
createCanvas(200, 200);
fill(252, 221, 188); // Bisque
}
function draw() {
background(100);
if (keyIsDown(LEFT_ARROW)) {
x -= 5;
}
if (keyIsDown(RIGHT_ARROW)) {
x += 5;
}
if (keyIsDown(UP_ARROW)) {
y -= 5;
}
if (keyIsDown(DOWN_ARROW)) {
y += 5;
}
ellipse(x, y, 50, 50);
text(key, 130, 20);
}
Переглядаємо Аналізуємо
У вказівці розгалуження перевіряється, яка із клавіш-стрілок є натиснутою і координата, що відповідає напрямку руху, змінюється з кроком 5
. Що відбудеться, якщо запустити застосунок без функції background();
?
Використаємо у функції keyIsDown()
числові значення keyCode
клавіш клавіатури.
let d = 50;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(75, 88, 66); // Rifle Green
// 107 і 187 числові значення keyCode клавіші "+"
if (keyIsDown(107) || keyIsDown(187)) {
d += 1;
}
// 109 і 189 числові значення keyCode клавіші "-"
if (keyIsDown(109) || keyIsDown(189)) {
d -= 1;
}
fill(143, 179, 57); // Apple Green
ellipse(50, 50, d, d);
}
Переглядаємо Аналізуємо
Подібно до того, як бібліотека p5.js
автоматично викликає функції mousePressed()
, mouseReleased()
і mouseClicked()
, залучені на подіях миші, автоматично викликаються й функції keyPressed() , keyReleased() , keyTyped() , залучені на подіях клавіатури.
Створимо застосунок, який дозволить відображати на полотні введені з клавіатури символи. Оголосимо змінну message
, яка буде утворювати рядок з усіх введених символів.
let message = "";
function setup() {
createCanvas(200, 200);
textSize(36);
textAlign(CENTER, CENTER);
}
function draw() {
background(50);
fill(255, 214, 10); // Gold Web Golden
text(message, width / 2, height / 2);
}
function keyTyped() {
message += key;
}
У коді використовується функція keyTyped()
, яка викликається один раз при щоразовому натисканні клавіші, за цієї умови дії клавіш Backspace
, Delete
, Ctrl
, Shift
та Alt
ігноруються.
Щоб простежити keyCode для однієї з клавіш, як-от Backspace , Delete , Ctrl , Shift та Alt , використовують функцію keyPressed() .
|
Переглядаємо Аналізуємо
Напишемо код для відображення на екрані такої інформації:
-
останнього введеного символу із клавіатури;
-
усіх введених англійських великих і малих літер, символів пропуску, інші символи - ігноруються.
let s = ""; // останній символ, введений з клавіатури
let row = ""; // рядок, в якому зберігатимуться введені символи
function setup() {
createCanvas(400, 150);
textAlign(LEFT, TOP);
}
function draw() {
background(0, 29, 61); // Oxford Blue
fill(255);
textSize(14);
text("Останній введений символ: " + s, 20, 30); (1)
text(`Рядок складається з: ${row.length} символів`, 20, 60); (2)
textSize(36);
text(row, 20, 90); (3)
}
function keyTyped() {
// змінна key завжди містить значення клавіші,
// яка була натиснута останньою
if ((key >= "A" && key <= "z") || key == " ") { (4)
s = key; (5)
row = row + key; (6)
console.log(key); // виведення символів в консоль
}
}
1 | Виведення на екран останнього введеного символу s за допомогою функції text() . Тут використовується конкатенація (об’єднання) двох рядків за допомогою операції + . |
2 | Виведення на екран кількості введених символів. Тут використовується метод length з JavaScript , який визначає довжину рядка. Для виведення значення довжини рядка (кількість символів у рядку) використовуються синтаксис шаблонних рядків з JavaScript . |
3 | Виведення на екран рядка із введеними символами. |
4 | Перевірка введених з клавіатури символів: враховуються лише англійські великі й малі символи та пропуск. |
5 | Змінна s набуває значення key введеного символу. |
6 | У рядок row додаються послідовно усі введені символи з клавіатури, які пройшли перевірку у пункті 4. |
У підсумку, об’єднаємо змінні та функції подій в одному застосунку, який можна використовувати для отримання інформації, введеної користувачем.
let f = ""; // назва функції події
function setup() {
createCanvas(320, 190);
}
function draw() {
background(45, 49, 66); // Space Cadet
textSize(16);
fill(255);
text("mouseIsPressed: " + mouseIsPressed, 20, 30);
text("mouseButton: " + mouseButton, 20, 50);
text("mouseX: " + mouseX, 20, 70);
text("mouseY: " + mouseY, 20, 90);
text("keyIsPressed: " + keyIsPressed, 20, 110);
text("key: " + key, 20, 130);
text("keyCode: " + keyCode, 20, 150);
text(`event function: ${f}`, 20, 170);
console.log(f);
}
function keyPressed() {
f = "keyPressed: " + key;
}
function keyReleased() {
f = "keyReleased: " + key;
}
function keyTyped() {
f = "keyTyped: " + key;
}
function mousePressed() {
f = "mousePressed";
}
function mouseReleased() {
f = "mouseReleased";
}
function mouseClicked() {
f = "mouseClicked";
}
function mouseMoved() {
f = "mouseMoved";
}
function mouseDragged() {
f = "mouseDragged";
}
function mouseWheel() {
f = "mouseWheel";
}
Переглядаємо Аналізуємо
Вправа 32
Використовуючи застосунок, дізнатися коди клавіш: BackSpace
, Delete
, клавіш-стрілок, Enter
, пропуск, Alt
, Ctrl
.
3.6.3. Ресурси
Корисні джерела
3.6.4. Контрольні запитання
Міркуємо Обговорюємо
-
Що належить до подій миші та клавіатури?
-
Які функції подій визначені для миші?
-
Які функції подій визначені для клавіатури?
3.6.5. Практичні завдання
Початковий
-
Заповнити прогалини в коді, щоб по натисканню кнопки миші лінії ставали помаранчевими, а після натискання будь-якої клавіші на клавіатурі - білими.
function setup() {
createCanvas(200, 200);
strokeWeight(5);
}
function draw() {
background(45, 49, 66); // Space Cadet
if (...) {
stroke(239, 131, 84); // Mandarin
}
if (...) {
stroke(255, 255, 255); // White
}
line(0, 0, width, height / 2);
line(width, height / 2, 0, height);
line(0, height, width / 2, 0);
line(width / 2, 0, width, height);
}
-
Заповнити прогалини в коді, який додає квадрат, коли натискається будь-яка кнопка миші, і очищає тло полотна при кожному натисканні будь-якої клавіші на клавіатурі.
function setup() {
createCanvas(200, 200);
background(255);
}
function draw() {
if (...) {
noStroke();
fill(0, 109, 119); // Ming
rectMode(CENTER);
rect(mouseX, mouseY, 10, 10);
}
if (...) {
...;
}
}
Середній
-
Створити застосунок, в якому можна малювати як олівцем.
-
Створити застосунок, у якому за допомогою клавіш-стрілок буде переміщуватися на полотні коло, а клавіші
R
,L
,B
,C
,M
іY
будуть змінювати колір зафарбовування кола.
-
Створити застосунок, який генерує різноколірні кола різного розміру за натискання кнопки миші.
-
Створити застосунок для малювання, в якому при натисканні кнопки миші змінюється випадковим чином розмір і колір «пензля». Орієнтовний результат роботи застосунку представлено в демонстрації.
-
Створити застосунок для малювання, в якому вказівник миші є «пензликом, що пульсує», а при натисканні будь-якої кнопки миші полотно очищається.
Високий
-
Створити застосунок, який імітує кидання ігрових кубиків за натисканням миші як представлено в демонстрації.
-
Створити імітацію роботи вмикача/вимикача освітлення в кімнаті, який частково зіпсувався в процесі експлуатації. Тобто, коли натискаємо на прямокутник (вмикач/вимикач) будь-якою кнопкою миші, лише тоді змінюється колір тла полотна (вмикається світло). Коли ж відпускаємо - колір тла стає чорним (світло вимикається).
-
«Полагодити» вмикач/вимикач освітлення з попереднього завдання. Тобто, коли натискаємо на прямокутник (вмикач/вимикач) будь-якою кнопкою миші - змінюється колір тла полотна (вмикається світло). Коли ж відпускаємо кнопку миші - колір тла залишається «увімкненим». Якщо ще раз натискаємо на прямокутник - колір тла стає чорним (світло вимикається).
Екстремальний
-
Створити прототип простого текстового редактора. У редакторі можна вводити текст і видаляти його справа наліво клавішею
BACKSPACE
по одному символу.
-
Реалізувати виконання дії
Drag & Drop
у графічних інтерфейсах - перетягування фігури на полотні за допомогою вказівника миші.
3.7. Правила написання читабельного коду. Коментарі у тексті програми
Кодування - це вид письма, який має певні правила.
Для порівняння, у звичайному письмі з найбільш невидимих правил - це написання зліва направо та пропуск між словами. Більш відкритими правилами є орфографічні умови, написання великих літер у власних назвах і використання розділових знаків у кінці речень.
Якщо ми порушуємо одне або кілька з правил під час написання коду, p5.js
в консолі вебпереглядача намагається сказати нам, в якому рядку зроблена помилка, виділивши його, і вгадати, в чому саме помилка.
Рядок коду з помилкою часто знаходиться на один рядок над виділеним рядком. |
Крім того, застосунок зупиняє своє виконання на першій помилці. Якщо у коді багато помилок, необхідно буде продовжувати запускати застосунок та виправляти помилки послідовно одна за одною.
Розглянемо основні правила написання коду.
3.7.1. Змінні
Як відомо, імена для змінних потрібно вибирати дотримуючись правил, які визначені у JavaScript
. При цьому треба бути уважним, оскільки обране вами ім’я, яке може відповідати правилам, може бути вже використано для однієї з функцій бібліотеки p5.js
.
У JavaScript
при оголошенні змінної можна не вказувати її початкове значення. Проте, якщо слідувати такій практиці, інколи застосунок може працювати не так, як би нам хотілося.
Наприклад, розглянемо такий код:
let r; (1)
function setup() {
createCanvas(200, 200);
}
function draw() {
background("#685369"); // Eggplant
r += 1; (2)
fill("#efa48b"); // Dark Salmon
circle(width / 2, height / 2, r);
}
Після запуску застосунку жодних повідомлень про помилки ми не отримаємо, хоча того, що вимагалося від застосунку - малювання кола в центрі полотна щоразу більшого радіуса r
- не відбулося.
Проблема, як і її розв’язання, ховається у позначених рядках.
1 | При оголошенні r змінній не було присвоєно початкове значення, тому значення r має тип undefined . |
2 | Змінна r , що бере участь в обчисленнях, а саме в інструкції інкременту (збільшення на 1 ), не має значення, тому результат додавання undefined + 1 дає результат NaN - значення не число (Not-A-Number ). |
Змінній r
потрібно присвоїти початкове значення, наприклад так: let r = 0;
. Тепер змінна матиме тип number
і математична операція інкременту буде виконуватися успішно.
3.7.2. Функції та параметри
Застосунки складаються з багатьох невеликих фрагментів коду, зібраних разом у цілу структуру. У коді всі ці частини мають різні імена і виконують різні дії.
Функції та параметри є важливими частинами будь-якого застосунку.
Функції - це основні будівельні блоки p5.js . Параметри - це значення, що визначають, як буде працювати функція.
|
Розглянемо функцію background()
, яка встановлює колір тла полотна. У функції є три параметри, що визначають колір. Ці числа задають кількість червоного, зеленого і синього компонентів потрібного кольору.
Наприклад, такий код задає синє тло:
background(51, 102, 153); // Lapis Lazuli
Особливості цього рядка коду - це дужки після імені функції, в яких записані числа, розділені комами й крапка з комою в кінці рядка.
Крапка з комою використовується як крапка у звичайних реченнях. Вона «повідомляє» інтерпретатору JavaScript
, що вираз закінчився і можна переходити до наступного. Все це повинно бути присутнім в коді для правильної роботи.
Вправа 33
Знайти та проаналізувати помилки у коді.
background 51, 102, 153;
background(51 102, 153);
background(51, 102, 153)
3.7.3. Коментарі
Коментарі - це примітки, які ви пишете собі (або іншим людям) всередині коду і які ігноруються під час виконання застосунку. |
Як вже було продемонстровано в коді багато разів, якщо коментар починається з двох похилих рисок (//
) і триває до кінця рядка - це однорядковий коментар.
// однорядковий коментар
Можна зробити коментар у кілька рядків, починаючи коментар /*
і закінчуючи */
.
/*
коментар
у
кілька
рядків
*/
Коли коментар набрано правильно, вся закоментована область стає сірою, щоб можна було бачити, де вона починається і закінчується.
Коментарі варто використовувати у навчальних цілях, у випадках з неочевидними рішеннями чи за умови важливості певних деталей в коді. Тому не потрібно зловживати коментарями - у кращому разі написаний код сам повинен пояснювати без коментарів, що він робить. |
3.7.4. Регістр літер
Середовище розробки відрізняє великі літери від малих і тому слово Background
є відмінним від background
.
Наприклад, якщо необхідно намалювати прямокутник за допомогою функції rect()
, а в коді написати Rect()
, то це викличе помилку.
3.7.5. Стиль коду
Оскільки p5.js
використовує мову програмування JavaScript
, то усі правила по стилю написання коду походять з JavaScript
.
Для JavaScript
неважливо, скільки місця використовується для форматування коду. Можна написати так
rect(50, 20, 30, 40);
або
rect(50,20,30,40);
або
rect (50, 20,
30, 40) ;
Однак, в наших інтересах зробити код зручним для читання. Це стає особливо важливо, якщо необхідно відшукати помилку в коді.
Наприклад, для розгалуження можна використовувати такий стиль запису:
if (...) {
...;
}
Якщо рядок коду є надто довгим, його розбивають на кілька рядків. Довжина рядка коду зазвичай повинна бути до 80
символів.
Для вказівки розгалуження if
, у разі надто довгих умов, використовують наступний синтаксис:
if (
... &&
... &&
...
) {
...;
}
При написанні коду відступи є також важливим елементом. Горизонтальні відступи створюються 2
чи 4
пропусками, вертикальні - порожнім рядком. Необхідно уникати ситуацій, коли багато рядків коду йдуть підряд без вертикального відступу.
Здебільшого крапку з комою можна не ставити між інструкціями, коли вони записані у різних рядках. Так теж буде працювати:
createCanvas(200, 200)
background(0)
В цьому разі JavaScript
інтерпретує перенесення рядка як «неявну» крапку з комою. У більшості випадків новий рядок має на увазі крапку з комою. Але «в більшості випадків» не означає «завжди»!
Щоб уникнути підводних каменів рекомендують використовувати крапки з комою, оскільки помилки, які можуть виникати, коли крапка з комою не використовується, досить складно виявляти та виправляти.
3.7.6. Консоль вебпереглядача
Консоль вебпереглядача - окрема область у вебпереглядачі, яка відкривається за допомогою Ctrl+Shift+I.
Консоль необхідна для того, щоб спостерігати за тим, що відбувається всередині застосунків, поки вони виконуються. Також, консоль можна використовувати для друку значень змінних.
Щоб надрукувати повідомлення у консолі вебпереглядача, використовують функцію console.log()
.
Наприклад, цей код друкує в консолі повідомлення про поточний час:
let m;
let h;
let s;
function setup() {
createCanvas(400, 400);
}
function draw() {
if (second() >= 0 && second() < 10) {
s = 0;
} else {
s = "";
}
if (minute() >= 0 && minute() < 10) {
m = 0;
} else {
m = "";
}
if (hour() >= 0 && hour() < 10) {
h = 0;
} else {
h = "";
}
console.log(
"На годиннику " + h + hour() + ":" + m + minute() + ":" + s + second()
);
}
Вправа 34
Записати команду console.log()
, використовуючи шаблонний літерал.
3.7.7. Загальні рекомендації
Дотримуйтесь таких загальних рекомендацій при написанні коду:
-
Пишіть кілька рядків коду за раз і запускайте код часто, щоб переконатися, що помилки не накопичуються без вашого відома.
-
Об’єднуйте свій код у простіші фрагменти та виконуйте їх по черзі.
-
Якщо у вас є помилка, спробуйте ізолювати область коду, де, на вашу думку, міститься проблема. Якщо ви застрягли на розв’язанні певної проблеми, зробіть перерву або попросіть друга про допомогу.
3.7.8. Ресурси
Корисні джерела
3.7.9. Контрольні запитання
Міркуємо Обговорюємо
-
У яких випадках доцільно використовувати коментарі?
-
Що таке «читабельний код»?
-
Для чого використовується консоль вебпереглядача?
3.7.10. Практичні завдання
Початковий
-
Виправити помилки у рядках коду.
createcanvas(200, 200;
background();
stroke 255;
fill(150)
rectMode(center);
rect(100, 100, 50);
Середній
-
Записати коментарі до фрагментів коду, де використовується розгалуження. Перевірити, чи зрозумілий коментований код іншим.
let x = 0;
let y = 0;
let prevX, prevY;
function setup() {
createCanvas(600, 300);
background(0);
x = random(width);
y = random(height);
prevX = x + random(-20, 20);
prevY = y + random(-20, 20);
frameRate(10);
}
function draw() {
stroke(random(255), random(255), random(0));
strokeWeight(1);
circle(x, y, random(50));
prevX = x;
prevY = y;
x += random(-20, 20);
y += random(-20, 20);
if (x < 0) {
x = width;
} else if (x > width) {
x = 0;
}
if (y < 0) {
y = height;
} else if (y > height) {
y = 0;
}
}
-
Записати відсутній код, використовуючи інформацію з коментарів. Результат роботи застосунку представлений в демонстрації.
function setup() {
createCanvas(320, 230);
background(255);
noStroke();
}
function draw() {
// ймовірності для 3-ох різних компонентів кольору
let red = ...; // 60% для червоного кольору
...; // 10% для зеленого кольору
...; // 30% для синього кольору
// випадкове число з рухомою крапкою з діапазону від 0 до 1, яке позначає складову кольору
let n = ...;
// якщо n менше .6
if (...) {
fill(255, 53, 2, 150); // Coquelicot
// якщо n має значення в діапазоні від .6 і до .7
} else if (...) {
fill(156, 255, 28, 150); // French Lime
// n в інших випадках (діапазон від .7 до 1.0)
} else {
fill(10, 52, 178, 150); // UA Blue
}
// намалювати коло
...
}
Високий
-
Створити застосунок, який малює штриховий код у чорно-білому варіанті як представлено в демонстрації. В коді записати коментарі.
-
Створити застосунок, який використовує інтерактивний взірець як у демонстрації.
Екстремальний
-
Створити застосунок, який реалізує один із варіантів алгоритму Random walk («Випадкова прогулянка»), призначеного для вивчення простору (вихід з лабіринту, пошук шляху від однієї точки до іншої тощо). Він працює так: уявіть собі сітку у формі шахової дошки. Кожна клітинка сітки має 4-ох сусідів: зверху, знизу, ліворуч та праворуч. А тепер уявіть, що ви перебуваєте в цій сітці. Щоб вибрати, куди йти далі, ви навмання вибираєте сусідню клітинку. Далі обираєте наступну сусідню клітинку і повторюєте це знову й знову, щоб створити шлях. Це все виконується випадково, тому іноді можна повернутися назад або шляхи будуть перетинатися. Якщо алгоритму надати трохи часу для малювання шляху, можна отримати цікаві візерунки.
Малювати по точках, зміщуючись на одиницю у випадковому напрямку, не виходячи за межі полотна. Кольорові компоненти змінювати на невелике значення і «тримати» в межах від 0 до 255 .
|
4. Функції
У цьому розділі ви детально зможете розібратися з тим, як створити власну функцію і використати її у своєму застосунку. |
4.1. Метод функціональної декомпозиції задачі. Модульність
Функція - фрагмент коду, до котрого можна неодноразово звертатись під час виконання застосунку. Функція може приймати певні значення та повертати результати. |
Функції є основними будівельними блоками застосунків у багатьох мовах програмування, зокрема й у JavaScript
.
Ми вже знаємо, як викликати попередньо визначені функції із бібліотеки p5.js
, на зразок, ellipse()
, background()
та інші. Вбудовані функції виконують конкретні задачі і їх розміщують всередині ще двох функцій: setup()
і draw()
.
Готова функція, яку можна було б використати для створення складного об’єкта, наприклад, автомобіля, і намалювати його багато разів, у p5 js
відсутня. У цьому разі пишуть власну функцію.
Для написання власної функції можна використовувати вбудовані функції, на зразок, line()
, ellipse()
та інші. Після того, як код для побудови автомобіля буде написаний у вигляді функції, назву функції, наприклад createAuto()
, записують в коді застосунку стільки разів, скільки потрібно разів намалювати автомобіль.
Функції допомагають керувати складністю початкового коду, групуючи інструкції, що можуть повторно виконуватися, під одним виконуваним ім’ям.
Функції часто називають «процедурами», «методами» або «підпрограмами». У деяких мовах програмування існує різниця між процедурою (виконує завдання) та функцією (обчислює і повертає значення). У мові JavaScript використовується поняття функції.
|
Щоб викликати власну функцію, її ім’я записують всередині функції draw()
.
function draw() {
background(0); // виклик вбудованої функції
createAuto(); // виклик власної функції
}
Цікавимось
Наприклад, використаємо власну функцію createAuto()
для створення автомобіля. Наша власна функція у застосунку нічим не відрізнятиметься за структурою від інших вбудованих функцій p5.js
, тому викликається за своїм іменем.
function draw() {
background(0); // виклик вбудованої функції
createAuto(); // виклик власної функції
}
Але запустивши застосунок, з’явиться помилка з повідомленням createAuto is not defined
, оскільки застосунку невідоме ім’я createAuto
, інакше кажучи, функція з таким ім’ям не визначена (не оголошена) в коді.
Визначення функції передбачає опис усіх інструкцій, котрі має виконати ця функція.
Визначення (створення, оголошення) нової функції починається в JavaScript
із використання зарезервованого слова function
, пропуску та імені функції з парою круглих дужок, після яких йде пара фігурних дужок, всередині яких записують тіло функції - інструкції, які виконуються при виклику функції.
function ім'яФункції() {
// тіло функції
}
Отже, перед тим як викликати власну функцію, її насамперед необхідно оголосити в коді.
Після оголошення функції її ім’я стає ім’ям змінної, значенням якої є сама функція. |
Наприклад, коли записується команда line(0, 0, 100, 100);
, по суті, викликається вбудована функція line()
, яка вже оголошена і має свій код.
Визначимо функцію, яка буде малювати коло червоного кольору у центрі полотна.
function drawRedCircle() {
...
...
}
Ім’я drawRedCircle()
для функції довільне, але записане відповідно до правил запису імен, а тіло функції буде складатися з двох інструкцій. Зверніть увагу, що це лише оголошення функції - при запуску застосунку тіло функції не буде виконуватися.
Щоб функція була виконана, її необхідно викликати з тої частини коду, яка виконується. Це досягається записом імені функції у draw()
.
Вправа 35
Заповнити пропуски та виконати наведений код.
function setup() {
createCanvas(200, 200);
}
function draw() {
background(255);
drawRedCircle();
}
function drawRedCircle() {
...
...
}
Загалом функції у JavaScript
, які оголошені за допомогою зарезервованого слова function
в певному блоці коду, можна викликати у будь-яких місцях цього блоку.
Це значить, що при запуску застосунку усі оголошені функції у файлі ескізу sketch.js
спочатку ініціалізуються усюди в цьому файлі, а вже потім інтерпретатор JavaScript
розпочинає виконання коду.
У кінцевому підсумку виклик функції можна робити як перед оголошенням функції, так і після неї.
Цікавимось Додатково
4.1.1. Ресурси
Корисні джерела
4.1.2. Контрольні запитання
Міркуємо Обговорюємо
-
Що таке «функція»?
-
Яка суть методу функціональної декомпозиції задачі?
-
Які кроки необхідно виконати, щоб написати та використати власну функцію у коді застосунку?
-
Поміркувати, які імена для функцій варто обрати, якщо тіло функції виконує наступні дії: малює зафарбований зеленим кольором прямокутник; малює два кола різного розміру; малює горизонтальну лінію, яка розділяє полотно на дві однакові частини?
-
Навести приклади задач, для розв’язання яких варто використати функціональний підхід.
4.1.3. Практичні завдання
Початковий
-
Створити функцію для побудови зображення будинку, використовуючи зразок коду.
stroke(0);
fill(95, 89, 128); // Purple Navy
rect(50, 70, 100, 100);
fill(47, 48, 97); // Space Cadet
triangle(50, 70, 150, 70, 100, 20);
fill(255);
rect(65, 95, 25, 25);
rect(110, 95, 25, 25);
fill(201, 203, 163); // Sage
rect(100, 136, 25, 33);

Середній
-
Створити функцію для побудови зображення сови, використовуючи зразок коду.
stroke(71, 104, 44); // Dark Olive Green
strokeWeight(40);
line(100, 100, 100, 120);
noStroke();
fill(255);
arc(100, 100, 40, 40, 0, PI);
ellipse(90, 100, 20, 20);
ellipse(110, 100, 20, 20);
fill(0);
ellipse(110, 100, 5, 5);
ellipse(90, 100, 5, 5);
quad(100, 105, 102, 107, 100, 110, 97, 107);

Високий
-
Намалювати персонажа, використовуючи функції для створення окремих його частин. Організувати код відповідно до наведеної структури.
function setup() {
...
}
function draw() {
...
drawCreature(); // виклик функції користувача
...
}
function drawCreature() {
// намалювати тіло у формі кола
...
// намалювати дві ноги у формі прямокутників
...
// намалювати два ока
...
// намалювати райдужки очей
...
// намалювати зіниці очей
...
// намалювати рот
...
}
function body() {
...
}
function legs() {
...
}
function eyes() {
...
}
function irises() {
...
}
function pupils() {
...
}
function mouth() {
...
}

Екстремальний
-
Створити імітацію говоріння персонажа, коли відбувається введення символів з клавіатури. Введений текст відображається на екрані. Реалізувати можливість видалення тексту посимвольно - у цей момент персонаж «мовчить». Орієнтовний результат роботи застосунку представлений в демонстрації.
У застосунку можна використати метод substring() із JavaScript для реалізації видалення символів.
|
4.2. Функції. Бібліотеки та модулі
Розглянемо більш детально техніку розбиття застосунку на модульні частини на прикладі коду.
// оголошення глобальних змінних
let x = 0;
let velocity = 1;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(255);
// зміна координати x (швидкість руху кульки)
x = x + velocity;
// якщо кулька досягла лівої межі полотна,
// зворотний напрямок руху
if (x < 16) {
velocity = 1;
}
// якщо кулька досягла правої межі полотна,
// зворотний напрям руху
if (x > width - 16) {
velocity = velocity * -1;
}
// намалювати кульку в точці з координатою x
stroke(0);
fill(136, 80, 83); // Tuscan Red
ellipse(x, 100, 32, 32);
}
Очевидно, що код можна об’єднати у блоки, а кожен з блоків помістити в окрему функцію.
Визначимо такі функції:
-
movement()
- рух кульки; -
bouncing()
- відбивання кульки; -
painting()
- малювання кульки на екрані.
Оголошення функцій відбувається зазвичай нижче функції draw() , а виклики функцій записуються всередині функції draw() .
|
Отже, застосувавши принцип модульності, код матиме наступний вигляд:
// оголошення глобальних змінних
let x = 0;
let velocity = 1;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
movement();
bouncing();
painting();
}
function movement() {
// зміна координати x (швидкість руху кульки)
x = x + velocity;
}
function bouncing() {
// якщо кулька досягла лівої межі полотна,
// зворотний напрямок руху
if (x < 16) {
velocity = 1;
}
// якщо кулька досягла правої межі полотна,
// зворотний напрям руху
if (x > width - 16) {
velocity = velocity * -1;
}
}
function painting() {
// намалювати кульку в точці з координатою x
stroke(0);
fill(136, 80, 83); // Tuscan Red
ellipse(x, 100, 32, 32);
}
Зараз код у функції draw()
став організованим і читабельним, оскільки складається лише з викликів функцій.
Тепер, щоб змінити відображення кульки, потрібно лише внести зміни у функцію painting()
, без необхідності шукати довгі фрагменти коду або турбуватися про решту коду.
Однією із переваг використання функцій є зручність у налагодженні застосунку, тобто функції можна коментувати, щоб визначити, чи викликають вони помилку, чи ні. |
4.2.1. Файли та модулі
Якщо далі продовжувати розширювати застосунок, принцип модульності можна реалізувати, помістивши код у кілька JavaScript
-файлів.
Наприклад, створимо поруч з файлом sketch.js
файл movement.js
і приєднаємо його у файл index.html
...
<script src="./movement.js"></script>
<script src="./sketch.js"></script>
...
Далі з файлу sketch.js
перемістимо у файл movement.js
код функції movement()
function movement() {
// зміна координати x (швидкість руху кульки)
x = x + velocity;
}
Цікавимось Додатково
4.2.2. Бібліотеки
Інформацію про будь-яку вбудовану функцію бібліотеки p5.js
можна отримати зі сторінки її довідки по функціях .
Довідка по функціях p5.js
містить список усіх доступних функцій бібліотеки p5.js
. Функції бібліотеки можна використовувати в кожному застосунку без додаткового оголошення цих функцій.
Однак, функції, які входять у спеціальні зовнішні бібліотеки, вимагають їх приєднання до основного коду застосунку.
Бібліотека - файл, у якому зберігається «допоміжний» коду у вигляді функцій, змінних чи об’єктів. Зазвичай такі файли бібліотек приєднуються (імпортуються) на початку основного файлу застосунку. |
Функціонал бібліотеки p5.js
розширюється за допомогою зовнішніх бібліотек, які умовно можна об’єднати у дві категорії:
-
основні бібліотеки, які є частиною
p5.js
(наприклад,p5.sound.js
); -
зовнішні бібліотеки, що розробляються і підтримуються членами спільноти
p5.js
.
Щоб приєднати бібліотеку у свій ескіз, необхідно зв’язати її з файлом index.html
у тезі <head>
за допомогою тега <script>
у рядку нижче приєднаного файлу p5.js
, але вище файлу ескізу sketch.js
.
У цьому разі вміст HTML
-файлу може виглядати так:
<!DOCTYPE html>
<html lang="">
<head>
...
<script src="./p5.js"></script>
<script src="./path/to/library"></script> <!-- приєднання зовнішньої бібліотеки -->
<script src="./sketch.js"></script>
</head>
<body>
<main>
</main>
</body>
</html>
В середовищі Processing IDE є можливість встановити деякі зовнішні бібліотеки та імпортувати їх у свій проєкт. Для цього необхідно виконати команду .
|
p5.shape.js
Як приклад роботи із зовнішньою бібліотекою, розглянемо бібліотеку p5.shape.js , яка розширює список простих фігур , які можна будувати за допомогою p5.js
.
Щоб використати бібліотеку p5.shape.js
, необхідно:
-
Завантажити архів бібліотеки
p5.shape.js
з файлом бібліотеки та іншими файлами зі сторінки GitHub -сховища (зелена кнопка з написомCode
). -
Файл бібліотеки
p5.shape.js
із розпакованого архіву скопіювати у каталогlibraries
(за потреби створити) проєкту. -
У файлі
index.html
проєкту правильно прописати шлях до файлу бібліотеки.
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>p5.js example</title>
<style>
body {
padding: 0;
margin: 0;
}
</style>
<script src="./libraries/p5.min.js"></script>
<script src="./libraries/p5.shape.js"></script> <!-- приєднання бібліотеки p5.shape.js -->
<script src="./sketch.js"></script>
</head>
<body>
<main>
</main>
</body>
</html>
Для роботи з бібліотекою p5.shape.js можна скористатися Processing IDE , розмістивши файл бібліотеки у каталозі libraries проєкту та правильно прописавши шлях до p5.shape.js у файлі index.html .
|
Використаємо можливості p5.shape.js
для малювання нових фігур, записавши код у файл ескізу sketch.js
.
function setup() {
createCanvas(650, 600);
}
function draw() {
textSize(15);
background(245);
fill(55, 150, 250); // Dodger Blue
center(5);
text("center(size)", width / 2 - 20, height / 2 + 15);
pent(250, 220, 65, 1);
text("pent(x, y, size, lineT)", 200, 270);
hexagon(100, 200, 50, 1);
text("hexagon(x, y, size, lineT)", 30, 275);
tri(100, 60, 50, 1);
text("tri(x, y, size, lineT)", 50, 125);
hept(100, 400, 50, 1);
text("hept(x, y, size, lineT)", 50, 450);
octo(270, 430, 60, 1);
text("octo(x, y, size, lineT)", 200, 480);
nona(450, 450, 50, 1);
text("nona(x, y, size, lineT)", 400, 500);
fill(255, 10, 200); // Hot magenta
heart(550, 95, 50, 1);
text("heart(x, y, size, lineT)", 500, 170);
fill(55, 150, 250);
deca(550, 300, 55, 1);
text("deca(x, y, size, lineT)", 490, 350);
trap(400, 150, 100, 1);
text("trap(x, y, size, lineT)", 340, 220);
rightTri(270, 70, 60, 1);
text("rightTri(x, y, size, lineT)", 210, 120);
}
Вправа 36
Намалювати на полотні за допомогою бібліотеки p5.shape.js
два серця синього і жовтого кольорів відповідно.
Відомості про те, як використовувати зовнішні бібліотеки для розширення функціонала p5.js , можна прочитати на сторінці офіційної документації .
|
TurtleGL.js
JavaScript Turtle Graphics Library
(TurtleGL.js
) - зовнішня бібліотека, яка використовує можливості p5.js
і JavaScript
для створення Черепашачої графіки (Turtle Graphics
) у вебпереглядачі. Є навчальним середовищем для програмування, яке використовує об’єктоорієнтований підхід.
Для знайомства з бібліотекою TurtleGL.js перейдіть у Додаток C.
|
4.2.3. Ресурси
Корисні джерела
4.2.4. Контрольні запитання
Міркуємо Обговорюємо
-
Навіщо основний код застосунку об’єднують: а) у модульні частини одного файлу? б) у кількох файлах?
-
Опишіть основні кроки алгоритму приєднання зовнішньої бібліотеки до проєкту.
4.2.5. Практичні завдання
Початковий
-
В основному коді є виклик функції
painting()
:
let x = 0;
let velocity = 1;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
movement();
bouncing();
painting();
}
function movement() {
x = x + velocity;
}
function bouncing() {
if (x < 16) {
velocity = 1;
}
if (x > width - 16) {
velocity = velocity * -1;
}
}
// оголошення функції
Оголосити у вказаному місці функцію painting()
з наступним кодом:
background(255);
rectMode(CENTER);
noFill();
stroke(19, 3, 3); // Xiketic
rect(x, x, 32, 32);
fill(255);
rect(x - 4, x - 4, 4, 4);
rect(x + 4, x - 4, 4, 4);
line(x - 4, x + 4, x + 4, x + 4);
Середній
-
Застосунок малює набір кіл навколо вказівника миші та квадрати у випадкових точках полотна в разі натиснення клавіш клавіатури. Застосувати модульний підхід і об’єднати код у кілька окремих функцій.
let x = 0;
let y = 0;
function setup() {
createCanvas(600, 400);
background(41, 41, 46); // Raisin Black
frameRate(10);
}
function draw() {
x = random(mouseX - 70, mouseX + 100);
y = random(mouseY - 50, mouseY + 70);
fill(49, 36, 102, 10); // St Patricks Blue
stroke(0, 206, 209, 60); // Dark Turquoise
strokeWeight(1);
ellipse(x, y, 100, 100);
}
function keyPressed() {
x = random(width);
y = random(height);
stroke(204, 102, 0, 30); // Alloy Orange
strokeWeight(2);
rect(x, y, 100, 50);
stroke(255, 255, 255, 80);
rect(x + 10, y + 20, 100, 100);
stroke(0, 0, 0, 80);
rect(x + 25, y + 25, 40, 40);
}
Високий
-
Використати бібліотеку
TurtleGL.js
для створення зображення на свій задум.

Екстремальний
-
Використати у своєму проєкті бібліотеку з колекції на вибір.
4.3. Передавання значень у функцію та з неї. Формальні та фактичні параметри
Код застосунку виконується рядок за рядком. Коли у коді трапляється виклик певної функції, хід виконання змінюється. Відбувається перехід на фрагмент коду, в якому визначена ця функція і виконується тіло функції, а потім хід виконання повертається до рядка коду, що йде після виклику функції.
Водночас функції мають набагато більше можливостей, ніж просто об’єднувати код у частини та викликатися за своїми іменами.
4.3.1. Параметри функції
Після оголошення функцію можна викликати, використовуючи її ім’я, та передавати у функцію певні значення. Значення надані функції називаються аргументами функції або фактичними параметрами.
Аргументи - це значення, які передаються у функцію. Аргументи можна ототожнити з вхідними даними для функції, які функція використовує у своєму тілі. |
Наприклад, у функціях background(100);
і strokeWeight(4);
аргументами є цілі числа 100
і 4
відповідно.
При оголошенні функції всередині дужок можуть записуватися імена змінних, які з часом отримають певні значення при виклику цієї функції й налаштують функцію під конкретні умови використання.
Ці імена називаються параметрами або формальними параметрами.
Параметри функції можна розглядати як змінні, які отримують значення аргументів при виклику функції. |
Проілюструємо розуміння аргументів і параметрів функції.
Для наших цілей використаємо зображення (510х480 пікселів) картини Лихвар і його дружина (1514 рік) художника Квентіна Массейса.
Використавши функцію loadImage() , під’єднаємо зображення до нашого ескізу.
У разі використання середовища Processing IDE , створіть каталог data для зберігання додаткових файлів проєкту у каталозі вашого ескізу. У каталог data скопіюйте файл зображення. Шлях до зображення у функції loadImage() повинен бути відносним до HTML -файлу ескізу.
|
Завантаження зображення з URL-адреси або іншого віддаленого розташування може бути заблоковано через вбудовану безпеку вашого вебпереглядача. |
Отже, додамо код в ескіз, щоб завантажити зображення на полотно:
let art;
function preload() {
art = loadImage("./data/Massysm_Quentin_—_The_Moneylender_and_his_Wife_—_1514.jpg");
}
function setup() {
createCanvas(510, 480);
image(art, 0, 0, width, height);
}
Візьмемо на себе роль художніх критиків і поміркуємо, про що могли говорити персонажі на картині? Не дотримуючись часових закономірностей, припустимо, що один із діалогів міг би бути таким:
Лихвар: Пам’ятаєш, ми проходили біля ювелірної крамниці пана Елігія, яка зображена на картині Петруса Крістуса?
Дружина лихваря: Авжеж.
Лихвар: Там я пригледів для тебе перлову сережку, як у дівчини з картини Яна Вермера.
Дружина лихваря: (подумки) Цікаво, яку книгу читає людина, що сидить біля вікна?
Спробуємо перенести цей діалог на саму картину за допомогою функції, яка малює словесні бульбашки на зображенні.
Визначимо функцію speechBalloon()
для малювання словесної бульбашки з текстом Пам’ятаєш, ми проходили поруч з ювелірною крамницею пана Елігія, яка зображена на картині Петруса Крістуса?
у точці з координатами (25, 120)
:
let art;
function preload() {
art = loadImage(
"./data/Massysm_Quentin_—_The_Moneylender_and_his_Wife_—_1514.jpg"
);
}
function setup() {
createCanvas(510, 480);
image(art, 0, 0, width, height);
}
function draw() {
speechBalloon();
}
function speechBalloon() {
let x = 25;
let y = 120;
let t = "Пам'ятаєш, ми проходили поруч з ювелірною крамницею пана Елігія, яка зображена на картині Петруса Крістуса?";
// трикутник словесної бульбашки
noStroke();
fill(255);
beginShape();
vertex(x + 100, y - 10);
vertex(x + 60, y - 80);
vertex(x + 30, y - 80);
endShape(CLOSE);
// розмір тексту і відступи
textSize(12);
let txtWidth = textWidth(t);
let txtHeight = txtWidth / (txtWidth / 3);
let py = y - 80;
if (txtWidth >= width / 2) {
txtWidth = txtWidth / 2 * 0.85;
txtHeight = txtHeight * 21.5;
} else if (txtWidth >= width / 3) {
txtWidth = txtWidth / 3 * 1.95;
txtHeight = txtHeight * 14.5;
} else if (txtWidth >= width / 4) {
txtWidth = txtWidth / 4 * 2.95;
txtHeight = txtHeight * 14.5;
} else {
txtWidth = txtWidth / 5 * 9.85;
txtHeight = txtHeight * 14.5;
}
// прямокутник словесної бульбашки
rect(x, py - txtHeight / 2, txtWidth, txtHeight, 12);
// текст у словесній бульбашці
fill(0);
textAlign(LEFT, CENTER);
text(t, x + 6, py, txtWidth - 6);
}
а потім викличемо її всередині функції draw()
function draw() {
speechBalloon();
}
Результатом буде розміщення на зображенні словесної бульбашки, утвореної із фігур трикутника і прямокутника з округленими кутами та вказаним текстом всередині.
У коді застосунку використано блок розгалужень для коректного відображення словесних бульбашок переважного їх текстового наповнення. В разі потреби блок розгалужень можна об’єднати в окрему функцію. |

На цьому етапі була оголошена функція без параметрів, а при її виклику за ім’ям не вказувалось відповідно жодних аргументів.
Щоб надрукувати напис в іншому місці зображення, необхідно змінити значення координат і текст напису у тілі функції. За таких умов, на зображення можна вивести одночасно лише один певний текстовий напис.
Змінимо це, додавши параметри в оголошення функції, які дозволяють передавати значення у функцію за допомогою різних аргументів у виклику функції.
Відредагуємо оголошення функції speechBalloon()
так, щоб функція могла приймати чотири аргументи: x
-координату, y
-координату, текст напису і напрямок direction
бульбашки (вгору - top
або вниз - bottom
). Також необхідно закоментувати (або видалити) рядки із присвоєнням значень змінним x
, y
і t
, щоб уникнути перезапису значень, які будуть передані за допомогою виклику функції.
function draw() {
speechBalloon(240, 385, "Авжеж.", "bottom"); (1)
}
function speechBalloon(x, y, t, direction) { (2)
// x = 25;
// y = 120;
// t = "Пам'ятаєш, ми проходили біля ювелірної крамниці пана Елігія, яка зображена на картині Петруса Крістуса?";
// трикутник словесної бульбашки
noStroke();
fill(255);
if (direction === "top") { (3)
beginShape();
vertex(x + 100, y - 10);
vertex(x + 60, y - 80);
vertex(x + 30, y - 80);
endShape(CLOSE);
} else {
beginShape();
vertex(x + 100, y - 140);
vertex(x + 60, y - 80);
vertex(x + 30, y - 80);
endShape(CLOSE);
}
// розмір тексту і відступи
...
1 | Змінні x , y , t , direction в оголошенні функції speechBalloon() є параметрами або формальними параметрами функції. |
2 | Значення 240 , 385 , "Авжеж." і "bottom" передані у функцію speechBalloon() при її виклику є аргументами або фактичними параметрами функції. |
3 | В залежності від значення параметра direction , фігури трикутників у словесній бульбашці змінюють напрямок. |
Формальний параметр - це просто змінна, оголошена всередині дужок в оголошенні функції. Ця змінна є локальною змінною і буде використана лише в цій функції.
Фактичний параметр - це конкретне значення, яке підставляється на місце формального параметра.
Тобто, виклик функції speechBalloon(240, 385, "Авжеж.", "bottom")
означає передачу у функцію конкретних значень 240
, 385
, "Авжеж."
і "bottom"
, які всередині функції будуть використовуватися з іменами x
, y
, t
і direction
відповідно.
З аргументами функцій ми вже зустрічалися неодноразово, коли використовували вбудовані функції p5.js
. Наприклад, щоб намалювати лінію, застосовують вбудовану функцію line()
із чотирма аргументами: line(10, 25, 100, 75);
.
Функція line() написана розробниками, тому оголошувати її не потрібно. Звернувшись до документації функції line() , можна знайти оголошення функції з чотирма параметрами.
|
Отже, передані у виклик функції аргументи будуть присвоюватися відповідним параметрам. Оскільки є чотири параметри, необхідно надати чотири аргументи під час виклику функції speechBalloon()
.
Перший аргумент 240
призначається параметру x
, другий аргумент 385
призначається параметру y
, третій аргумент "Авжеж."
призначається параметру t
, четвертий параметр "bottom"
призначається параметру direction
і так далі, у тому самому порядку як параметри з’являються в рядку оголошення функції.
Такі аргументи називаються позиційними аргументами, оскільки порядок аргументів визначає, які значення призначаються кожному із параметрів.
Запустивши ескіз, можна переконатися, що результат візуально зміниться.

Як бачимо, параметри функцій відкривають шлях до багаторазового використання функцій. Зробимо кілька викликів функції speechBalloon()
:
function draw() {
speechBalloon(25, 120, "Пам'ятаєш, ми проходили поруч з ювелірною крамницею пана Елігія, яка зображена на картині Петруса Крістуса?", "top");
speechBalloon(240, 385, "Авжеж.", "bottom");
speechBalloon(30, 385, "Там я пригледів для тебе перлову сережку, як у дівчини з картини Яна Вермера.", "bottom");
}

Важливо дотримуватись таких правил передачі значень у функцію:
-
Передавати таку ж кількість аргументів, скільки визначено параметрів в оголошенні функції, зберігаючи їхній порядок.
-
Значення, яке ви передаєте функції, може бути конкретним значенням, змінною або результатом обчислення виразу.
-
Параметри є локальними змінними для функції та доступні лише в межах цієї функції.
Іноді, функцію викликають з меншою кількістю аргументів, ніж кількість оголошених у ній параметрів.
Наприклад, функція rect() може отримувати окрім трьох обов’язкових аргументів, ще й необов’язкові: четвертий (висота прямокутника), п’ятий, шостий, сьомий та восьмий, які визначають радіус кутів для лівого верхнього, верхнього правого, нижнього правого та нижнього лівого кутів відповідно (якщо вказати лише п’ятий аргумент - значення радіуса встановлюється для усіх кутів).
Тобто, якщо функцію для побудови прямокутника викликати з чотирма аргументами, тоді отримаємо прямокутник з кутами 90
градусів. Але якщо вказати п’ятий аргумент, відмінний від нуля, то отримаємо прямокутник з округленими кутами.
У коді функції speechBalloon() використовується функція rect() з п’ятьма аргументами, останній з яких відповідає за радіус округлення кутів прямокутника.
|
При оголошенні функції, для параметрів, аргументи для яких є необов’язковими, можна вказати значення за стандартним налаштуванням або стандартні значення.
У цьому разі стандартні значення застосовуватимуться лише тоді, коли при виклику функції для цього параметра аргумент буде пропущеним. Параметри зі стандартними значеннями записуються в оголошенні функції у вигляді виразу ім’я параметра = значення, який обчислюється при виклику функції, а не коли функція оголошується.
Цікавимось Додатково
При оголошенні функцій, параметри, що мають стандартні значення, розміщують у кінці списку параметрів функції. |
Додамо в оголошення функції speechBalloon()
стандартне значення "Книга, яку читаєш, цікава?"
для параметра t
, враховуючи порядок параметрів:
function speechBalloon(x, y, direction, t = "Книга, яку читаєш, цікава?") {
...
}
Тепер можна викликати функцію speechBalloon()
за допомогою трьох позиційних аргументів, а для четвертого аргументу буде використовуватися стандартне значення параметра:
function draw() {
speechBalloon(5, 345, "bottom"); (1)
speechBalloon(240, 385, "bottom", "Авжеж."); (2)
...
}
function speechBalloon(x, y, direction, t = "Книга, яку читаєш, цікава?") {
...
}
1 | Застосовується стандартне значення для параметра t , оскільки у виклику функції для параметра t не вказано значення позиційного аргументу. |
2 | Значення за стандартним налаштуванням для параметра t не використовується, оскільки у виклику функції для параметра t вказано значення позиційного аргументу. |

Щоб завершити діалог наших персонажів на картині, використаємо хмаринку думок. Візуально хмаринка думок - це ланцюжок невеликих за розміром кіл, намальованих замість трикутника словесної бульбашки.
Враховуючи, що словесні бульбашки є більш поширеними, ніж хмаринки думок, додамо до оголошення функції speechBalloon()
додатковий параметр type
зі стандартним значенням "speech"
, який вказуватиме на словесну бульбашку.
У підсумку, функція speechBalloon()
матиме наступний вигляд:
function speechBalloon(x, y, direction, t = "Книга, яку читаєш, цікава?", type = "speech") { (1)
noStroke();
fill(255);
switch (type) { (2)
// трикутник словесної бульбашки
case "speech": (3)
if (direction === "top") {
beginShape();
vertex(x + 100, y - 10);
vertex(x + 60, y - 80);
vertex(x + 30, y - 80);
endShape(CLOSE);
} else {
beginShape();
vertex(x + 100, y - 140);
vertex(x + 60, y - 80);
vertex(x + 30, y - 80);
endShape(CLOSE);
}
break;
// кола думок
case "thought": (4)
if (direction === "top") {
circle(x + 70, y - 20, 7);
circle(x + 60, y - 35, 15);
} else {
circle(x + 50, y - 130, 7);
circle(x + 40, y - 115, 15);
}
break;
}
// розмір тексту і відступи
textSize(12);
let txtWidth = textWidth(t);
let txtHeight = txtWidth / (txtWidth / 3);
let py = y - 80;
if (txtWidth >= width / 2) {
txtWidth = (txtWidth / 2) * 0.85;
txtHeight = txtHeight * 21.5;
} else if (txtWidth >= width / 3) {
txtWidth = (txtWidth / 3) * 1.95;
txtHeight = txtHeight * 14.5;
} else if (txtWidth >= width / 4) {
txtWidth = (txtWidth / 4) * 2.95;
txtHeight = txtHeight * 14.5;
} else {
txtWidth = (txtWidth / 5) * 9.85;
txtHeight = txtHeight * 14.5;
}
// прямокутник словесної бульбашки
rect(x, py - txtHeight / 2, txtWidth, txtHeight, 12);
// текст у словесній бульбашці
fill(0);
textAlign(LEFT, CENTER);
text(t, x + 6, py, txtWidth - 6);
}
1 | В оголошенні функції speechBalloon() є два параметри зі стандартними значеннями (t = "Книга, яку читаєш, цікава?" , type = "speech" ). Зверніть увагу, що вони йдуть після параметрів, що не мають значень за стандартним налаштуванням. Як вже було зазначено, якщо оголошується будь-яка функція зі значеннями за стандартним налаштуванням, варто розмістити ці параметри у кінці списку. |
2 | Щоб не використовувати вказівки if , для розгалуження використовується інструкція switch , яка отримує значення параметра type і порівнює його із case -значеннями "speech" і "thought" , переліченими всередині неї. Після перевірки виконуються відповідні інструкції. |
3 | У разі, якщо type дорівнює "speech" , тобто персонаж на картині «говорить», будується трикутник словесної бульбашки. |
4 | У разі, якщо type дорівнює "thought" , тобто персонаж на картині «думає», будуються кола словесної бульбашки. |
Виконаємо виклики функції для ілюстрації усього діалогу.
let art;
function preload() {
...
}
function setup() {
...
}
function draw() {
speechBalloon(
25,
120,
"top",
"Пам'ятаєш, ми проходили поруч з ювелірною крамницею пана Елігія, яка зображена на картині Петруса Крістуса?"
);
speechBalloon(240, 385, "bottom", "Авжеж.");
speechBalloon(
5,
345,
"bottom",
"Там я пригледів для тебе перлову сережку, як у дівчини з картини Яна Вермера."
);
speechBalloon(
215,
190,
"top",
"Цікаво, яку книгу читає людина, що сидить біля вікна?",
"thought"
);
}
function speechBalloon(x, y, direction, t = "Книга, яку читаєш, цікава?", type = "speech") {
...
}

4.3.2. Функція, яка повертає значення
Здебільшого функції, що використовувалися до цього часу, при виклику просто виконували код, що містився у їхньому тілі.
Існує ще один вид функцій, які при виклику не просто виконують якісь конкретні дії, але й повертають («повідомляють») результат своєї роботи в основний код застосунку.
Пригадаємо, як працює вбудована функція random()
, яка генерує випадкові числа з певного діапазону. Кожного разу, коли викликається функція random()
, вона «повертає» випадкове значення в межах вказаного діапазону.
Значення, які повертають деякі вбудовані функції, на зразок random()
, можна зберегти у деякій змінній і використовувати у коді застосунку.
Якщо необхідно написати власну функцію, яка повертає значення в основний код застосунку, використовують зарезервоване слово return
, яке одночасно є точкою виходу з функції.
Отже, напишемо власну функцію з іменем changeText()
, яка буде доповнювати текст словесної бульбашки.
function speechBalloon(x, y, direction, t = "Книга, яку читаєш, цікава?", type = "speech") {
...
}
function changeText(t, newText) {
t += newText;
return t;
}
Функція викликатиметься з двома позиційними аргументами: перший аргумент t
- основний текст словесної бульбашки, другий аргумент newText
- новий текст, який буде доповнювати основний текст словесної бульбашки.
Зарезервоване слово return повертає значення із функції changeText() . Якщо додати будь-який код в тіло функції changeText() після рядка return , цей код буде проігнорований.
|
Отже, розширимо основний текст словесної бульбашки лихваря текстом "…42, 43, 44, 45… 50. Ніби все гаразд."
, а текст хмаринки думок дружини лихваря текстом "У дзеркалі, бачите?"
. Для цього оголосимо дві змінні newText1
і newText2
, які будуть зберігати текстові доповнення відповідно.
Використаємо функцію changeText()
, яка повертає значення, як аргументи у виклику функції speechBalloon()
.
let art;
function preload() {
...
}
function setup() {
...
}
function draw() {
let newText1 = "...42, 43, 44, 45... 50. Ніби все гаразд.";
let newText2 = "У дзеркалі, бачите?";
speechBalloon(
5,
345,
"bottom",
changeText("Треба порахувати монети. ", newText1)
);
speechBalloon(
235,
160,
"top",
changeText("Цікаво, яку книгу читає людина, що сидить біля вікна? ", newText2),
"thought"
);
}
function speechBalloon(x, y, direction, t = "Книга, яку читаєш, цікава?", type = "speech") {
...
}
function changeText(t, newText) {
t += newText;
return t;
}

Вправа 37
Виконати код, який друкує на екрані поточні дату й час. Дослідити, як змінються результати на екрані при зміні значення другого аргумента функції nf()
.
function setup() {
createCanvas(200, 100);
}
function draw() {
let currentYear = year(); (1)
let currentMonth = month();
let currentDay = day();
let currentHour = hour();
let currentMinute = minute();
let currentSecond = second();
let d = currentDate(currentYear, currentMonth, currentDay); (2)
let t = currentTime(currentHour, currentMinute, currentSecond);
display(d, t); (3)
}
// оголошення функції для обчислення дати
function currentDate(y, m, d) {
let currentDate = y + "-" + nf(m, 2) + "-" + nf(d, 2);
return currentDate;
}
// оголошення функції для обчислення часу
function currentTime(h, m, s) {
let currentTime = nf(h, 2) + ":" + nf(m, 2) + ":" + nf(s, 2);
return currentTime;
}
// оголошення функції для виведення дати й часу на екран
function display(date, time) {
background(245, 224, 183); // Wheat
fill(133, 126, 123); // Rocket Metallic
noStroke();
textSize(24);
text(date, 40, 40);
text(time, 40, 70);
}
1 | Цей код використовує функції для роботи з датою (year() , month() , day() ) і часом (hour() , minute() , second() ), щоб надрукувати на екрані поточні дату та час. Ці функції повертають значення, які присвоюються змінним currentYear , currentYear , currentMonth , currentDay , currentHour , currentMinute , currentSecond . |
2 | Значення цих змінних передаються як аргументи у функції currentDate() і currentTime() , які зі свого боку повертають результат роботи на основі переданих значень за допомогою return і присвоюють його змінним d і t . Всередині функцій використовується конкатенацію рядків для створення текстових повідомлень і функція nf() - форматування при виведенні числових даних. |
3 | Значення d і t передаються як аргументи іншій функції - display() , яка виводить результат дати й часу на полотно. |
4.3.3. Ресурси
Корисні джерела
4.3.4. Контрольні запитання
Міркуємо Обговорюємо
-
Які різновиди функцій можна виділити, взявши за основу принцип їхньої роботи?
-
Які параметри функції називають: а) формальними; б) фактичними?
-
Яка мета використання стандартних значень для параметрів в оголошенні функції?
-
Що таке «виклик функції»?
-
Як розуміти вислів функція повертає результат?
4.3.5. Практичні завдання
Початковий
-
Використовуючи код, створити функцію, яка має єдиний формальний параметр - значення координати для малювання точок двох графіків. Викликати функцію з різними аргументами.
let x;
function setup() {
createCanvas(250, 200);
}
function draw() {
background(46, 196, 182); // Tiffany Blue
stroke(255, 191, 105); // Mellow Apricot
line(0, 50, width, 50);
line(0, 150, width, 150);
line(0, 250, width, 250);
stroke(255);
x = 0;
while (x < width) {
point(x, 50 + random(-15, 15));
point(x, 150 + 20 * sin(x / 20));
x = x + 1;
}
}
Середній
-
Створити функцію
drawHouse()
, яка малює будинок, використовуючи поданий нижче код. Формальними параметрами функції мають бути координати розташування будинку. Викликати функцію кілька разів із різними значеннями аргументів для малювання однакових будинків у різних місцях полотна.
stroke(0);
// каркас
fill(95, 89, 128); // Purple Navy
rect(x, y, 100, 100);
// дах
fill(47, 48, 97); // Space Cadet
triangle(x, y, x + 100, y, x + 50, y - 50);
// вікна
fill(255);
rect(x + 15, y + 25, 25, 25);
rect(x + 60, y + 25, 25, 25);
// двері
fill(201, 203, 163); // Sage
rect(x + 50, y + 63, 25, 36);

-
Використовуючи код, написати функцію, яка приймає чотири аргументи (
x
,y
,s
- масштаб,c
- колір), для побудови зображення сови. Викликати функцію кілька разів для побудови групи сов різного розміру та кольору.
Використати вбудовану функцію scale() , яка змінює масштаб фігур. Новий аргумент виклику функції множиться на значення попереднього. |
stroke(c);
scale(s);
strokeWeight(40);
line(x, y, x, y + 20);
noStroke();
fill(255);
arc(x, y, 40, 40, 0, PI);
ellipse(x - 10, y, 20, 20);
ellipse(x + 10, y, 20, 20);
fill(c);
ellipse(x + 10, y, 5, 5);
ellipse(x - 10, y, 5, 5);
quad(x, y + 4, x + 3, y + 7, x, y + 10, x - 3, y + 7);

Високий
-
Створити дизайн космічного корабля і намалювати кілька зорельотів з невеликими варіаціями на основі аргументів, які передаються у функцію:
x
-координата,y
-координата, розмір і колір. Один із можливих варіантів дизайну зорельотів представлений на малюнку.

-
Створити застосунок, який малює словесні бульбашки й хмаринки думок на обраному зображенні.
-
Створити застосунок, який генерує новорічну ялинку з випадковою кількістю іграшок. Орієнтовний результат представлений в демонстрації.
Для створення окремої іграшки на дереві використайте готову функцію, що визначає координати x та y цієї іграшки всередині трикутної крони дерева.
|
function toys(x1, y1, x2, y2, x3, y3) {
const r1 = random();
const r2 = random();
const x = (1 - sqrt(r1)) * x1 + sqrt(r1) * (1 - r2) * x2 + sqrt(r1) * r2 * x3;
const y = (1 - sqrt(r1)) * y1 + sqrt(r1) * (1 - r2) * y2 + sqrt(r1) * r2 * y3;
noStroke();
fill(random(255), random(255), random(255));
circle(x, y, random(5, 10));
}
Екстремальний
-
Створити застосунок, який генерує візерунок із квітів. Орієнтовний результат представлений в демонстрації.
-
Написати функцію, яка будує безліч будинків як на малюнку.

4.4. Рекурсія. Рекурсивні побудови
Багато речей в реальному світі можна описати ідеалізованими геометричними формами: смартфон має прямокутну форму, баскетбольний м’яч - круглу і т. д.
Однак, багато об’єктів у природі описати такими простими засобами неможливо. Наприклад, сніжинки, дерева, узбережжя та гори тощо. Ці структури називають самоподібними.
Самоподібні структури - об’єкти, малі частини яких в довільному збільшенні/зменшенні є подібними до самих об’єктів. |
Як можна відтворити самоподібні структури? Один із процесів створення самоподібних структур відомий як рекурсія.
Цікавимось
У програмуванні рекурсія - виклик функції безпосередньо з неї самої з іншими значеннями вхідних аргументів.
Функції, які викликають самих себе, називають рекурсивними й використовуються для розв’язування різних типів задач.
Кількість вкладених викликів функції називається глибиною рекурсії. Через обмеженість обчислювальних ресурсів рекурсія в комп’ютерних застосунках не буває нескінченної - необхідно явно стежити за тим, щоб глибина рекурсивних викликів не перевищувала заздалегідь відомого числа.
Якщо про це не подбати (або ж зробити це неправильно), операційна система (або інтерпретатор) аварійно завершить роботу застосунку як тільки доступні ресурси будуть вичерпані. Тому рекурсивні функції повинні мати умову виходу, як і у разі використання циклів for
і while
.
Типовим прикладом задачі, яку можна розв’язати рекурсивно, є обчислення факторіала.
Факторіал будь-якого числа n
, який зазвичай записується як n!
, визначається як:
Інакше кажучи, факторіал є добутком усіх цілих чисел від 1
до n
.
Розгляньмо 4!
та 3!
.
Отже
Загалом для будь-якого додатного цілого числа n
формула для обчислення факторіала буде:
Факторіал n визначається як n разів помножити на факторіал (n - 1) . Така концепція, коли функція у своєму тілі викликає саму себе, і є рекурсією, а сама функція називається рекурсивною.
|
Вправа 38
Викликати функцію обчислення факторіала за допомогою рекурсії та надрукувати результат в консолі вебпереглядача.
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
// викликати функції для обчислення 3!, 4!
}
function factorial(n) {
if (n == 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
Задачу обчислення факторіала числа можна розв’язати й без рекурсії, наприклад, використовуючи цикл for
. У цьому разі функція factorial()
не буде рекурсивною та описуватиметься так:
function factorial(n) {
let f = 1;
for (let i = 0; i < n; i++) {
f = f * (i + 1);
}
return f;
}
Рекурсію можна використовувати не лише для математичних розрахунків, але й для побудови комп’ютерної графіки.
Водночас важливо встановити обмеження на кількість викликів функції самої себе. Наприклад, можна використовувати змінну для підрахунку глибини рекурсії та зупинитись, коли потрібної глибини вже дісталися.
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
drawCircle(100, 100, 150);
}
function drawCircle(x, y, r) {
fill(254, r, 183); // Peach Puff
ellipse(x, y, r, r);
if (r > 5) {
r *= 0.78;
drawCircle(x, y, r);
}
}
Функція drawCircle()
малює коло на основі набору параметрів, отриманих як аргументи 100
, 100
, 150
, а потім викликає себе з тими самими параметрами, трішки коригуючи їх, лише якщо значення радіуса r
більше ніж п’ять (умова виходу з рекурсії).
Щоразові виклики рекурсивної функції надають r
таких значень:
150
117
91.26
71.1828
55.522584
43.307615520000006
33.779940105600005
26.348353282368006
20.551715560247047
16.030338136992697
12.503663746854304
9.752857722546358
7.607229023586159
5.933638638397205
4.62823813794982 // вихід з рекурсії
150
117
...
Результат - низка кіл, де кожне з кіл намальоване всередині попереднього кола.
Малювання кіл відбувається доти, доки виконується умова r > 5
. При черговому значенні r = 4.62823813794982
умова r > 5
не виконується і функція drawCircle()
перестає викликати саму себе. Після цього у функції draw()
знову робиться перший виклик функції drawCircle()
і все повторюється.
Використання рекурсії дозволяє створювати красиві та складні зображення.
Реалізуймо складніший сценарій з функцією drawCircle()
. Для кожного відображеного кола намалюємо коло вдвічі менше його ліворуч та праворуч від цього кола, а також вгорі й внизу.
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
stroke(253, 252, 220); // Light Yellow
drawCircle(width / 2, height / 2, 100);
}
function drawCircle(x, y, r) {
fill(254, r, 183); // // Peach Puff
ellipse(x, y, r, r);
if (r > 7) {
drawCircle(x + r / 2, y, r / 2);
drawCircle(x - r / 2, y, r / 2);
drawCircle(x, y + r / 2, r / 2);
drawCircle(x, y - r / 2, r / 2);
}
}

Вправа 39
Виконати код для малювання кіл, змінюючи значення радіуса у виклику drawCircle()
і в умові виходу з рекурсії.
4.4.1. Ресурси
Корисні джерела
4.4.2. Контрольні запитання
Міркуємо Обговорюємо
-
Навести приклади самоподібних структур.
-
Що таке «рекурсія»?
-
Навести приклади використання рекурсії у мистецтві.
4.4.3. Практичні завдання
Початковий
-
Записати виклик рекурсивної функції
drawCircle()
, яка створює малюнок як на зразку.
function setup() {
createCanvas(200, 200);
}
function draw() {
background(255);
stroke(0, 119, 182); // Star Command Blue
noFill();
// виклик функції
...
}
function drawCircle(x, y, r) {
ellipse(x, y, r, r);
if (r > 2) {
drawCircle(x + r / 2, y, r / 2);
drawCircle(x - r / 2, y, r / 2);
}
}

Середній
-
Заповнити прогалини в коді, який генерує рекурсивний шаблон як на малюнку.

function setup() {
createCanvas(400, 200);
}
function draw() {
background(255);
stroke(229, 68, 109); // Paradise Pink
// виклик функції
...
noLoop();
}
function branch(x, y, h) {
line(x, y, x - h, y - h);
line(x, y, x + h, y - h);
if (...) {
// функція викликає себе двічі
...(x - h, y - h, h / 2);
...(x + h, y - h, h / 2);
}
}
Високий
-
Написати функцію
squares()
, використовуючи зразок коду. Функція має три параметри (координатиx
іy
розташування лівого верхнього кута квадрата,n
- встановлює розмір сторони квадрата) і малює «рекурсивні квадрати». Орієнтовний результат представлений на малюнку.

// зразок коду для рекурсивної функції
if (n > 1) {
// визначення кольорів
let x2 = x + n;
let y2 = y + n;
rect(x, y, n, n);
n = int(n / 2);
// тут функція викликає саму себе
}
Екстремальний
-
Створити рекурсивну структуру, яка складається з кіл. Орієнтовний результат представлений на малюнку.

4.5. Фрактали як самоподібні структури
Одним із видів комп’ютерної графіки, поруч з векторною і растровою, є фрактальна графіка.
Фрактальна графіка - технологія створення зображень на основі фракталів і базується на фрактальній геометрії. |
Фрактал (лат. fractus
- подрібнений, що складається з фрагментів) - термін, який позначає геометричну фігуру, складену з декількох частин, кожна з яких подібна до всієї фігури цілком.
Тобто, фрактал - це нескінченно самоподібна геометрична фігура, кожен фрагмент якої повторюється при зменшенні масштабу. Невелика частина фракталу містить інформацію про увесь фрактал.
Переглядаємо Аналізуємо
В навколишньому світі нас оточують об’єкти, які створені як людиною, так і природою.
Щоб описати об’єкти та форми, які створила людина, використовуються знання про геометричні фігури - трикутники, прямокутники, квадрати, кола та інші фігури.
Але більшість речей, які є у природі, неможливо описати ідеалізованими геометричними фігурами Евклідової геометрії .
Наприклад, гора при віддаленому розгляді здається конусом, але при наближенні вона виявляється покрита скелями й каменями. У цьому разі її поверхня вже далека від ідеальної поверхні конуса.
Якщо блискавку описувати ламаною лінією, то при зменшенні масштабу ми побачимо, що кожен з відрізків також необхідно описувати своєю ламаною лінією, відрізки якої, при ще більш дрібному масштабі виявляються ламаними.
А як описати дерево, контури острова, з чим порівняти будову морської хвилі, форму сніжинки або листка папороті, чи описати розгалужену структуру бронхів або кровоносної системи? Ці об’єкти та явища є об’єктами дослідження фрактальної геометрії.
Чому геометрію часто називають холодною і сухою? Одна з причин полягає в її нездатності описати форму хмари, гори, дерева або берега моря. Хмари - це не сфери, гори - не конуси, лінії берега - це не кола, і кора не є гладкою, і блискавка не поширюється по прямій. Природа демонструє нам не просто вищий ступінь, а зовсім інший рівень складності.
Фрактальна геометрія природи
Об’єкти, які тепер називаються фракталами, досліджувались задовго до того, як їм було дано таку назву.
Термін фрактал запропонував французько-американський математик Бенуа Мандельброт у 1975 році, щоб описати самоподібні структури, знайдені в природі. Відомою працею по фракталах є його книга «Фрактальна геометрія природи» («The Fractal Geometry of Nature», 1982).
У роботі Мандельброта використані наукові результати інших вчених, які працювали в цій області й заклали математичну базу для появи теорії фракталів: Анрі Пуанкаре, Фелікс Кляйн, П’єр Фату, Ґастон Жюліа, Георг Кантор, Нільс Фабіан Гельґе фон Кох, Поль Леві, Фелікс Хаусдорф.
Фрактали бувають різних видів:
-
геометричні;
-
алгебричні;
-
стохастичні;
-
концептуальні (соціокультурні, непросторові).
4.5.1. Види фракталів
Геометричні
Геометричні фрактали є найбільш наочними й простими в будові. Безліч таких фракталів можна намалювати на звичайному аркуші паперу в клітинку.
Прикладами геометричних фракталів є:
Розглянемо принцип побудови геометричних фракталів на прикладі кривої Коха.
Процес її побудови виглядає так: беремо відрізок, поділяємо його на три рівні частини та замінюємо середній інтервал рівностороннім трикутником без цього інтервалу.

У результаті утворюється ламана, що складається з чотирьох ланок з довжиною 1/3
довжини початкового відрізка.

На наступному кроці повторюємо операцію для кожного з чотирьох отриманих ланок

і так далі



У підсумку, повторення операції побудови (ітерацій) утворюють криву, яка є кривою Коха.
Три копії кривої Коха, побудовані вістрями назовні на сторонах правильного трикутника, утворюють замкнену криву, так звану сніжинку Коха.

Сніжинка Коха є основою фрактальних антен, які використовуються в мобільних пристроях. Завдяки такій формі антени мають компактний розмір і широкий діапазон дії. |
Розглянемо ще один принцип побудови фракталів відомий як трикутник Серпінського.
Візьмемо рівносторонній трикутник, відзначимо середини його сторін.

З’єднаємо серединні точки прямими лініями. Утворюється 4 трикутники. Центральний трикутник виймаємо.

Тепер повторимо цю операцію з кожним із новоутворених трикутників.




І так до нескінченності. Як бачимо, кількість трикутників збільшується, і сума їх периметрів (сума сторін трикутників) прямує до нескінченності.
Виймаючи з трикутника все наповнення після кожної ітерації, ми постійно зменшуємо його площу і в результаті зводимо її до нуля.
Проілюструємо принципи побудови й деяких інших геометричних фракталів.
Переглядаємо Аналізуємо
Алгебричні
Алгебричні фрактали - це найбільша група фракталів, яка базується на основі різних алгебричних формул.
Поява обчислювальних пристроїв дозволила прискорено проводити ітерації (багаторазово повторюваний процес обчислення) і візуалізувати формули.
Яскравим прикладом алгебричних фракталів є фрактал Мандельброта.
Можна виділити й інші приклади фракталів цього виду.
Нині алгебричні фрактали зображують в кольорі. Виходять дуже красиві незвичайні орнаменти, які використовують, наприклад, в дизайні одягу.
Стохастичні
Геометричні фрактали, на зразок, кривої Коха чи трикутника Серпінського, є детермінованими, тобто вони не мають випадковості й завжди дають однаковий результат кожного разу, коли їх відтворюють. Вони є надто точні, щоб бути природними.
Стохастичні фрактали (недетерміновані, випадкові) будуються шляхом хаотичної зміни деяких параметрів. За такої умови виходять об’єкти, дуже схожі на природні.
Прикладом використання такого фракталу в комп’ютерній графіці є ефект плазми - комп’ютерний візуальний ефект, анімований у реальному часі, який є ілюзію рідкого, органічного руху.
Переглядаємо Аналізуємо
Фрактали даного виду широко застосовуються в комп’ютерній графіці для штучного створення гір, хмар, поверхонь морів, берегових ліній, планет, блискавок, полум’я тощо.
У темі Трансформації та моделювання руху розділу Мультимедіа розглядається створення стохастичного фракталу - дерева. |
Концептуальні
Принцип багаторівневої самоподібності закладений в культурних творах. У художніх текстах (віршах для дітей, народних піснях, у музичних творах і казках) часто зустрічається «оповідання в оповіданні», на зразок, «Притча про філософа, якому сниться, що він метелик, якому сниться, що він філософ, якому сниться…», «Дім, який збудував Джек» тощо.
Фрактальність спостерігається в організації людських поселень (країна - місто - квартал); у розподілі суспільства на групи (народ - соціокультурна група - сім’я - людина).
Сюди ж можна віднести фрактальність взаємовідносин, які починаються з самої людини. Змінюється людина, її сприйняття, внутрішній стан - змінюються взаємовідносини в сім’ї, колективі, в результаті перетворюється все суспільство. Фрактальність простежується і в ієрархічних системах управління.
Концептуальні фрактали об’єднують непросторові структури, що виходять за рамки геометричної фрактальності.
4.5.2. Фрактали у природі
Найвідомішими фрактальними об’єктами у природі є дерева: від кожної гілки відходять менші, схожі на неї, від них - ще менші. За окремою гілкою можна відстежувати властивості всього дерева.

Найцікавіше, що прожилки на листі дерев теж утворюють фрактальний малюнок, дуже схожий на плоске мініатюрне дерево. Немає листя з однаковим малюнком, так само як немає людей з однаковим відбитком пальця. Малюнок на кожному листку унікальний.
Рослини теж несуть в собі форми самоподібності.

Фрактальними властивостями володіють і тварини.

Удар блискавки - фрактальна гілка, лід, замерзлий на склі, має самоподібний малюнок. Фрактальними властивостями володіють багато географічних об’єктів - морські узбережжя, річки та гірські хребти, кордони держав, видимі межі хмар.
Наприклад, узбережжя на кілометровому відрізку виглядає таким же «порізаним», як і на стокілометровому. Тобто, криві, подібні до кривої Коха, у природі становлять швидше правило, ніж виняток.

Цікавимось Додатково
4.5.3. Шум Перліна
Коли необхідно створити певну непередбачуваність у поведінці об’єктів, використовують застосунки - генератори випадкових чисел.
Загалом принцип роботи генераторів випадкових чисел зазвичай базується на деякій рекурентній формулі, яка обчислює наступні елементи числової послідовності через значення попередніх елементів. Тому, задаючи однакові початкові елементи послідовності, можна щоразу отримувати однакові послідовності.
У результаті, згенеровані числа називають псевдовипадковими, бо вони отримуються за чітким детермінованим алгоритмом.
По суті, функція random() з бібліотеки p5.js
є генератором псевдовипадкових чисел.
Шум Перліна - ще один генератор псевдовипадкових чисел, що виробляє більш природно впорядковану послідовність чисел, порівняно зі стандартною функцією random()
.
Шум Перліна (Perlin Noise ) був створений Кеном Перліном (Ken Perlin ) у 1983 році та був названий на честь свого творця.
|
З моменту створення, із деякими покращеннями реалізації, шум Перліна використовується в комп’ютерній графіці для створення процедурних текстур (зображення текстури - малюнка поверхні - створюється за допомогою алгоритму), природного руху, форм, рельєфу, візуальних ефектів із природними якостями, як-от полум’я, туман тощо.
Зокрема, розробка Perlin Noise
дозволила художникам комп’ютерної графіки краще представити складність природних явищ у візуальних ефектах для кіноіндустрії. Також шум Перліна використовується в розробці комп’ютерних ігор для генерації ігрових світів.
Бібліотека p5.js
містить реалізацію цього алгоритму у вигляді функції noise() .
Аргументами у виклику функції noise()
є значення координат. Залежно від кількості переданих значень координат у функцію noise()
, можна обчислювати 1D
-, 2D
- і 3D
-шум.
Функція noise()
повертає фіксоване (протягом виконання застосунку) псевдовипадкове значення шуму Перліна завжди у діапазоні від 0.0
до 1.0
. На це значення впливає різниця між послідовно переданими координатами у noise()
, наприклад, під час використання noise()
у циклі. Як правило, чим менша різниця між координатами, тим плавнішою буде кінцева шумова послідовність, яка генерується за лаштунками.
Отож, розглянемо код застосунку, в якому реалізуємо анімацію хаотичного руху кола.
function setup() {
createCanvas(200, 200);
}
function draw() {
background(245);
fill(131, 103, 199); // Amethyst
let x = random(width);
let y = random(height);
circle(x, y, 20);
}
Переглядаємо Аналізуємо
Як бачимо, виклик функції random()
визначає щоразу випадкові значення координат (x, y)
центру побудови кола, внаслідок цього виникає ілюзія, що рухаються багато кіл.
Використаємо значення шуму Перліна, яке повертає функція noise()
, для створення плавної анімації.
let xoff1 = 0; (1)
let xoff2 = 100000; (2)
let i = 0.01; (3)
function setup() {
createCanvas(200, 200);
}
function draw() {
background(245);
fill(131, 103, 199); // Amethyst
let x = map(noise(xoff1), 0, 1, 0, width); (4)
let y = map(noise(xoff2), 0, 1, 0, height); (5)
circle(x, y, 20);
xoff1 += i; (6)
xoff2 += i; (7)
}
1 | Ініціалізуємо змінну з ім’ям xoff1 , значення якої буде передаватися як аргумент у функцію noise() . |
2 | Ініціалізуємо змінну з ім’ям xoff2 , значення якої буде передаватися як аргумент у функцію noise() . |
3 | Ініціалізуємо змінну i , яка буде збільшувати значення xoff1 та xoff2 на 0.01 відповідно. |
4 | Обчислюємо x -координату для побудови кола, використовуючи функцію map() , яка перетворює значення шуму Перліна з діапазону від 0 до 1 у діапазон від 0 до width . |
5 | Обчислюємо y -координату для побудови кола, використовуючи функцію map() , яка перетворює значення шуму Перліна з діапазону від 0 до 1 у діапазон від 0 до height . |
6 | Збільшуємо значення xoff1 на величину i , щоб змінити псевдовипадкове значення шуму Перліна на наступній ітерації виконання застосунку. |
7 | Збільшуємо значення xoff2 на величину i , щоб змінити псевдовипадкове значення шуму Перліна на наступній ітерації виконання застосунку. |
Значення 0
та 100000
, якими ініціалізуються змінні xoff1
і xoff2
, є значеннями координат в нескінченному одновимірному просторі, в якому визначається шум Перліна.
Якби ці значення не змінювалися у пунктах 6 і 7, то псевдовипадкові значення шуму Перліна залишалися б однаковими протягом виконання застосунку, а при повторному запуску застосунку набували інших значень в діапазоні від 0
до 1
і знову протягом виконання застосунку були однаковими.
У підсумку, у процесі виконання застосунку зміна значень xoff1
і xoff2
формує відстані між послідовними координатами, які передаються у noise()
під час використання noise()
у циклі. Це і визначає характер кінцевої псевдовипадкової послідовності - як правило, чим ця відстань менша, тим вона буде плавнішою.
Переглядаємо Аналізуємо
За стандартним налаштуванням noise() повертає різні результати під час чергового запуску застосунку. Використовуючи функцію noiseSeed() для встановлення початкового значення (насіння) для noise() , функція noise() буде повертати однакові псевдовипадкові числа під час кожного запуску застосунку.
|
Візуалізуємо відмінності у результатах викликів функцій random()
і noise()
.
Спочатку розглянемо код застосунку для функції random()
.
function setup() {
createCanvas(200, 200);
noFill();
noLoop(); (1)
}
function draw() {
background(245);
stroke(131, 103, 199); // Amethyst
beginShape();
for (let x = 0; x < width; x++) {
let y = random(height);
vertex(x, y); (2)
}
endShape();
}
1 | Використовуємо у блоці setup() функцію noLoop() (це має бути останній рядок усередині блоку setup() ), яка зупиняє безперервне виконання коду у draw() . Одна ітерація у блоці draw() точно відбудеться. |
2 | Малюємо фігуру, використовуючи beginShape() і endShape() . У циклі for малюємо вершини фігури із координатами x та y за допомогою функції vertex(x, y) . Значення y -координати обчислюється випадково за допомогою функції random() . |

А тепер подивимось на код застосунку для функції noise()
.
let xoff = 0;
let i = 0.02; (1)
function setup() {
createCanvas(200, 200);
noFill();
noLoop(); (2)
}
function draw() {
background(245);
stroke(131, 103, 199); // Amethyst
beginShape();
for (let x = 0; x < width; x++) {
let y = noise(xoff) * height; (3)
vertex(x, y);
xoff += i;
}
endShape();
}
1 | Значення змінної i впливає на плавність графіка, оскільки визначає різницю між переданими значеннями координат у функцію noise() . Чим більше значення i , тим кінцевий графік буде більше схожий на графік для random() . |
2 | Знову зупиняємо безперервне виконання коду у draw() . Одна ітерація у блоці draw() точно відбудеться. |
3 | Тепер y -координату для малювання вершин фігури обчислюємо як добуток значення висоти height полотна і значення шуму Перліна, яке повертає функція noise() на кожній ітерації циклу for . |
У результаті виконання застосунку отримаємо статичний плавний графік функції шуму Перліна.

Вправа 40
Закоментувати рядок noLoop();
. Перемістити рядок let xoff = 0;
і розмістити після beginShape();
. Виконати застосунок. Чому графік залишається статичним?
Змінимо код застосунку візуалізації шуму Перліна так, щоб початкове значення координати xoff
у кожній ітерації блоку draw()
набувало нових значень.
let i = 0.02;
let start = 0; (1)
function setup() {
createCanvas(200, 200);
noFill();
}
function draw() {
background(245);
stroke(131, 103, 199); // Amethyst
beginShape();
let xoff = start; (2)
for (let x = 0; x < width; x++) {
let y = noise(xoff) * height;
vertex(x, y);
xoff += i; (3)
}
endShape();
start += i; (4)
}
1 | Ініціалізуємо змінну з ім’ям start , яке буде покликанням на поточне значення координати, що передаватиметься у цикл for . |
2 | У кожній ітерації в блоці draw() ініціалізуємо змінну з ім’ям xoff і значенням start , яке буде початковим значенням координати для функції noise() . |
3 | Збільшуємо значення xoff на величину i , щоб у циклі функція noice() за допомогою нового значення xoff повернула нове значення шуму Перліна. |
4 | По завершенні виконання циклу for у черговій ітерації в блоці draw() , збільшуємо на величину i значення start початкової координати для noice() . |
У результаті виконання застосунку, ми здійснюємо рух через простір шуму Перліна з плином часу й отримуємо багато плавних функцій (октав) з різними частотами та амплітудами, що сумуються для створення графіка функції шуму Перліна.
Переглядаємо Аналізуємо
Якщо у коді застосунку замість noise()
використати функцію sin()
, отримаємо знайому криву - графік функції sin()
.
let i = 0.02;
let start = 0;
function setup() {
createCanvas(200, 200);
noFill();
}
function draw() {
background(245);
stroke(131, 103, 199); // Amethyst
beginShape();
let xoff = start;
for (let x = 0; x < width; x++) {
let y = map(sin(xoff), -1, 1, 0, height);
vertex(x, y);
xoff += i;
}
endShape();
start += i;
}
У коді знову було використано функцію map()
для перетворення значення функції sin()
з діапазону від -1
до 1
у діапазон від 0
до height
.
Переглядаємо Аналізуємо
Наприкінці, розглянемо код застосунку, який використовує функції sin()
і noise()
в тандемі для формування y
-координати.
let i = 0.02;
let start = 0;
let mix = 50; (1)
function setup() {
createCanvas(200, 200);
noFill();
}
function draw() {
background(245);
stroke(131, 103, 199); // Amethyst
beginShape();
let xoff = start;
for (let x = 0; x < width; x++) {
let n = map(noise(xoff), 0, 1, 0, mix); (2)
let s = map(sin(xoff), -1, 1, 0, height - mix); (3)
let y = n + s; (4)
vertex(x, y);
xoff += i;
}
endShape();
start += i;
}
1 | Ініціалізуємо змінну з ім’ям mix за допомогою якої додамо трішки шуму до візуалізації графіка функції sin() . |
2 | Використовуємо функцію map() , яка перетворює значення, що повертає функція noise() , з діапазону від 0 до 1 у діапазон від 0 до mix . |
3 | Використовуємо функцію map() , яка перетворює значення, що повертає функція sin() , з діапазону від -1 до 1 у діапазон від 0 до height - mix . |
4 | Обчислюємо значення y -координати як суму значень, отриманих у пунктах 2 і 3, для побудови вершин графіка. |
У результаті отримуємо графік синусоїди з додаванням шуму Перліна.
Створений графік функції шуму Перліна відображає з плином часу значення одновимірного простору шуму Перліна, проте сам графік будуються у двовимірному просторі полотна. |
Переглядаємо Аналізуємо
Як бачимо, шум, як генератор псевдовипадкових чисел, можна додавати як джерело додаткових деталей, яких бракує в очевидній структурі.
Для налаштування рівня деталізації функції шуму Перліна використовується функція noiseDetail() .
Функція noiseDetail()
може отримувати як аргументи два значення:
-
кількість октав, які будуть використані шумом - за стандартним налаштуванням
4
; чим більше октав, тим дрібніші деталі, але зменшується плавність; -
коефіцієнт падіння для кожної октави - за стандартним налаштуванням параметри окремої октави зменшується рівно вдвічі, порівняно з попередньою октавою, починаючи з
50%
для1
-ї октави.
Змінюючи ці два параметри, результат, який повертає функція noise()
, можна адаптувати відповідно до власних конкретних потреб.
let i = 0.02;
let start = 0;
function setup() {
createCanvas(200, 200);
noFill();
noiseDetail(15);
}
function draw() {
background(245);
stroke(131, 103, 199); // Amethyst
beginShape();
let xoff = start;
for (let x = 0; x < width; x++) {
let y = map(noise(xoff), 0, 1, 0, height);
vertex(x, y);
xoff += i;
}
endShape();
start += i;
}
У цьому разі використовується 15
октав, відповідно графік функції шуму Перліна буде більш деталізованим та з гострими вершинами.
Переглядаємо Аналізуємо
4.5.4. Використання фракталів
У фізиці фрактали природним чином виникають при моделюванні нелінійних процесів, таких, як турбулентний плин рідини, складні процеси дифузії, полум’я, хмари тощо.
Фрактали виникають при аналізі економічних та фінансових процесів у формі графіків котирувань валюти на біржі, які мають вигляд типової броунівської траєкторії .
Фрактали використовуються при моделюванні пористих матеріалів, наприклад, в нафтохімії. У біології вони застосовуються для моделювання популяцій і для опису систем внутрішніх органів (внутрішня поверхня легень, система кровоносних судин).
Розуміння фрактальної побудови спростило багато сфер наукових досліджень. Дивна особливість фракталів - повторення аналогічного патерну (зразка) в різних масштабах - дозволяє, вивчивши малу частину якої-небудь події або явища, припускати про будову цілого.
Фрактальні криві, що нескінченно самоподібні, мають нескінченну довжину. Відповідно, програмно їх неможливо намалювати повністю. Тому фрактальні криві малюють в деякому наближенні, заздалегідь фіксуючи максимально допустиму глибину рекурсії.
Розглянемо кілька прикладів використання рекурсії для побудови різних фракталів.
4.5.5. Створення фракталів
Множина Кантора
Створимо застосунок, який побудує множину Кантора , що є прототипом фракталу.
Множина Кантора будується за допомогою видалення середніх третин сегментів прямої.
На першому кроці видаляється середня третина з одиничного інтервалу [0, 1]
, залишаючи [0, 1/3]
і [2/3, 1]
. На наступному кроці, видаляється середня третина кожного з отриманих інтервалів.
Цей процес повторюється до нескінченності. Множина Кантора складається із всіх точок інтервалу [0, 1]
, які залишаються після всіх повторних видалень.
Переглядаємо Аналізуємо
Як бачимо, множина Кантора починається із суцільної лінії. Тож, напишемо функцію cantor()
, яка малює горизонтальну лінію (обрано довільний напрямок лінії).
function cantor(x, y, len) {
line(x, y, x + len, y);
}
Змінні x
і y
є координатами лінії, а len
- довжина лінії.
Викличемо функцію cantor()
за допомогою поданого нижче коду і подивимось на результат.
function setup() {
createCanvas(400, 400);
}
function draw() {
background(230);
stroke("#3F37C9"); // Persian Blue
cantor(10, 20, width - 20);
}
function cantor(x, y, len) {
line(x, y, x + len, y);
}
Правило Кантора говорить нам стерти середню третину цієї лінії, яка була отримана в результаті виконання коду. Тобто, має залишитися дві лінії, одна від початку лінії до позначки однієї третини, а друга - від позначки двох третин до кінця лінії.

Отже, намалюємо другу пару ліній нижче суцільної лінії, збільшивши координату y
на 20
пікселів.
function setup() {
createCanvas(400, 400);
}
function draw() {
background(230);
stroke("#3F37C9"); // Persian Blue
cantor(10, 20, width - 20);
}
function cantor(x, y, len) {
line(x, y, x + len, y);
y += 20;
line(x, y, x + len / 3, y); // від початку лінії до 1/3
line(x + (len * 2) / 3, y, x + len, y); // від 2/3 до кінця лінії
}
Щоб намалювати наступний (третій) рядок ліній в множині Кантора, необхідно написати вже чотири рядки виклику функцій line()
.
function setup() {
createCanvas(400, 400);
}
function draw() {
background(230);
stroke("#3F37C9"); // Persian Blue
cantor(10, 20, width - 20);
}
function cantor(x, y, len) {
line(x, y, x + len, y);
y += 20;
line(x, y, x + len / 3, y); // від початку лінії до 1/3
line(x + (len * 2) / 3, y, x + len, y); // від 2/3 до кінця лінії
y += 20;
line(x, y, x + len / 9, y); // від початку лінії до 1/9
line(x + (len * 2) / 9, y, x + (len * 3) / 9, y); // від 2/9 до 3/9
line(x + (len * 6) / 9, y, x + (len * 7) / 9, y); // від 6/9 до 7/9
line(x + (len * 8) / 9, y, x + len, y); // від 8/9 до кінця лінії
}
Далі знадобиться вісім, а потім шістнадцять викликів line()
і так далі. Щоб не писати стільки коду, застосуємо цикл for
. Але розрахувати формулу для кожної ітерації є також справою не з легких.
Замість того, щоб безпосередньо викликати функцію line()
, можна просто викликати саму функцію cantor()
, оскільки функція cantor()
малює лінію від точки (x, y)
із заданою довжиною len
.
Тобто, можна замінити рядки
line(x, y, x + len / 3, y); // від початку лінії до 1/3
line(x + (len * 2) / 3, y, x + len, y); // від 2/3 до кінця лінії
на виклики функції cantor()
рекурсивно
cantor(x, y, len / 3);
cantor(x + (len * 2) / 3, y, len / 3);
У підсумку, функція cantor()
відпрацює і для наступних рядків множини Кантора, оскільки викликає себе знову і знову.
Залишається останній і дуже важливий крок - функція cantor()
повинна зупинитися, коли довжина лінії стане менше як 1
піксель. Тому у код додамо перевірку, при якій рекурсивна функція буде виконуватися, якщо довжина лінії більшою чи дорівнюватиме 1
пікселю.
Вправа 41
Виконати код для перегляду візуалізації множини Кантора.
function setup() {
createCanvas(400, 400);
}
function draw() {
background(230);
stroke("#3F37C9"); // Persian Blue
cantor(10, 20, width - 20);
}
function cantor(x, y, len) {
if (len >= 1) {
line(x, y, x + len, y);
y += 20;
cantor(x, y, len / 3);
cantor(x + (len * 2) / 3, y, len / 3);
}
}
Крива Коха
Вправа 42
Виконати код, який візуалізує криву Коха. Провести 7
ітерацій. За потреби звернутися до довідки по функціях p5.js
.
let i = 0; // кількість ітерацій
function setup() {
createCanvas(600, 350);
}
function draw() {
background(253, 255, 252);
// відобразити номер ітерації в лівому верхньому куті полотна
noStroke();
fill(4, 167, 119);
text(i, 10, 20);
// намалювати криву Коха за допомогою рекурсивних викликів
createKoch(i, 0, 250, 600, 250);
}
function createKoch(i, x1, y1, x2, y2) {
// кінець рекурсивним викликам функції
if (i == 0) {
// намалювати лінію від точки (x1, y1) до (x2, y2)
strokeWeight(2);
stroke(217, 3, 104);
line(x1, y1, x2, y2);
return;
}
let alpha = atan2(y2 - y1, x2 - x1); // кут повороту
let r = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); // відстань між точками
// обчислення координат 3 точок трикутника
// C
// / \
// ----A B----
// ліва точка (ліва вершина трикутника)
let xa = x1 + (r * cos(alpha)) / 3;
let ya = y1 + (r * sin(alpha)) / 3;
// центральна точка (верхня вершина трикутника)
let xc = xa + (r * cos(alpha - PI / 3)) / 3;
let yc = ya + (r * sin(alpha - PI / 3)) / 3;
// права точка (права вершина трикутника)
let xb = x1 + (2 * r * cos(alpha)) / 3;
let yb = y1 + (2 * r * sin(alpha)) / 3;
// рекурсивні виклики функції для 4 отриманих ліній
createKoch(i - 1, x1, y1, xa, ya);
createKoch(i - 1, xa, ya, xc, yc);
createKoch(i - 1, xc, yc, xb, yb);
createKoch(i - 1, xb, yb, x2, y2);
}
// збільшення кількості ітерацій натисканням миші
function mousePressed() {
i += 1;
}
Переглядаємо Аналізуємо
4.5.6. Ресурси
Корисні джерела
4.5.7. Контрольні запитання
Міркуємо Обговорюємо
-
Що таке «фрактали»?
-
Навести приклади фракталів.
-
Як класифікують фрактали?
4.5.8. Практичні завдання
Початковий
-
У
Processing IDE
в режиміJavaScript
відкрити, переглянути та запустити код прикладів побудови відомих фракталів із меню (Ctrl+Shift+O), далі , і .
Середній
-
Побудувати фігуру у фігурі. За основу взяти код, який будує трикутник у трикутнику.
function setup() {
createCanvas(200, 200);
}
function draw() {
background(255);
stroke(195, 172, 206); // Lilac
fill(195, 172, 206, 100);
tria(width / 2, height / 2, 200);
}
function tria(a, b, c) {
triangle(a - c / 2, b + c / 2, a + c / 2, b + c / 2, a, b - c / 2);
if (c > 5) {
tria(a, b, c / 2);
}
}
Високий
-
Використовуючи код із попереднього завдання про трикутники створити трикутник Серпінського.
-
Створити ескіз, в якому використати шум Перліна. Наприклад, це може бути гірський пейзаж як на малюнку.

Екстремальний
-
Намалювати фрактал із квадратів, орієнтовний зразок якого представлений на малюнку.

5. Об’єкти та класи
У цьому розділі ви ознайомитеся з основами об’єктоорієнтованого програмування в JavaScript і використання його принципів у роботі з бібліотекою p5.js .
|
5.1. Поняття об’єкта, класу як об’єктного типу даних
У навколишньому світі нас оточують об’єкти різних класів.
Наприклад, у живій природі є клас птахів і є об’єкти цього класу - лелека, яструб, орел, пелікан тощо, які мають спільні (дзьоб, крила, хвіст) і відмінні (забарвлення, вид оперення, розміри) властивості. Але усі ці об’єкти є птахами.
Наприклад, до класу легкових автомобілів входять автомобілі, що мають спільні (кермо, чотири колеса, двигун) і відмінні (колір, тип кузова - седан, кросовер, хетчбек, тип двигуна - бензиновий, дизельний, електричний, гібридний) властивості й усі залишаються об’єктами класу легкових автомобілів.
Клас визначає властивості, якими володіють усі його об’єкти - представники цього класу. Значення властивостей у кожного окремого об’єкта можуть бути свої.
Використовуючи аналогію з архітектури та будівництва, клас - це креслення будинку, а об’єкт - конкретний будинок. Побудовані за кресленням будинки можуть відрізнятися один від одного кольором, кількістю поверхів, формою даху тощо.
Клас - це певний шаблон, на основі якого створюються його об’єкти. |
Окрім властивостей, об’єкти класів можуть мати здатність щось робити. Наприклад, об’єкти класу птахів - літати, полювати тощо, об’єкти класу легкових автомобілів - їхати, повертати, гальмувати тощо.
Якщо взяти конкретну людину - вона є об’єктом класу людей. Людина має такі властивості як колір очей, ріст, маса тощо (дані) і може виконувати певні дії (функції) - їсти, ходити, їздити на велосипеді тощо.
Поняття об’єкта, як певної сутності, що об’єднує в собі властивості (дані) і виконувані об’єктом дії (функції), лежить в основі концепції об’єктоорієнтованого програмування. Об’єктоорієнтоване програмування надає надзвичайно ефективний спосіб організації застосунків для моделювання об’єктів реального світу.
5.1.1. Основи об’єктоорієнтованого програмування
Розглянемо основи об’єктоорієнтованого програмування загалом, не в контексті певної мови програмування.
Згідно з термінологією об’єктоорієнтованого програмування дані зберігаються у змінних, які називаються полями даних (також властивостями або атрибутами), а функції називаються методами класу.
На основі одного класу можна створити безліч об’єктів, що відрізнятимуться один від одного значеннями полів даних. Об’єкти класу ще називають екземплярами чи примірниками класу.
Щоб створити конкретний об’єкт, необхідно визначити клас з описом властивостей і методів об’єктів.
Наприклад, визначимо клас з назвою Персона з такими характеристиками:
-
властивості (Ім’я, Вік, Стать та Інтереси);
-
метод РозповістиПроСебе, який друкує повідомлення на основі значень властивостей: Мені [Вік] років. Мої захоплення: [Інтереси].;
-
метод Привітатися, який друкує повідомлення: Привіт, я - [Ім’я]!.
Заданий клас є шаблоном, який визначає, які характеристики повинна мати реальна людина, створена на основі цього класу. Опишемо схематично цей шаблон.
Клас: Персона
Атрибути:
Ім'я
Вік
Стать
Інтереси
Методи:
РозповістиПроСебе(Мені [Вік] років. Мої захоплення: [Інтереси].)
Привітатися(Привіт, я - [Ім'я]!)
Створення простої моделі складнішої сутності, яка представляє її найбільш важливі аспекти таким способом, щоб з нею було зручно працювати, називається абстракцією. Абстракція - це один із принципів об’єктоорієнтованого програмування. |
На основі класу Персона можна створити його екземпляри - реальних людей, що містять дані та функціональні можливості, визначені в класі.
Створимо дві реальні людини на основі класу Персона.
Перша людина:
Об'єкт: персона1
Властивості:
Ім'я: Дарина
Вік: 18
Стать: Жіноча
Інтереси: Танці, Плавання, Іноземні мови
Методи:
РозповістиПроСебе(Мені 18 років. Мої захоплення: Танці, Плавання, Іноземні мови.)
Привітатися(Привіт, я - Дарина!)
Друга людина:
Об'єкт: персона2
Властивості:
Ім'я: Максим
Вік: 14
Стать: Чоловіча
Інтереси: Футбол, Комп'ютерні ігри
Методи:
РозповістиПроСебе(Мені 14 років. Мої захоплення: Футбол, Комп'ютерні ігри.)
Привітатися(Привіт, я - Максим!)
Дані та функції об’єкта збережені (інкапсульовані) всередині об’єкта. Це спрощує структуру і доступ до них. Інкапсуляція - один з механізмів в сучасних об’єктоорієнтованих мовах програмування. |
Розглянемо приклад, коли необхідно створити конкретні типи людей, наприклад, учнів та вчителів.
В об’єктоорієнтованому програмуванні можна створювати нові класи на основі інших класів. Нові дочірні класи створюються для успадкування даних і характеристик батьківського класу. У такий спосіб можна використовувати функціональні можливості загальні для всіх об’єктів замість того, щоб дублювати їх.
Коли функціональність різниться між класами, безпосередньо у класах можна визначати спеціалізовані методи.
Успадкування - це створення нового класу об’єктів шляхом додавання нових елементів (атрибутів і/або методів) до вже наявного. Успадкування - один з механізмів в сучасних об’єктоорієнтованих мовах програмування. |
Отже, на основі батьківського класу Персона
Клас: Персона
Атрибути:
Ім'я
Вік
Стать
Інтереси
Методи:
РозповістиПроСебе(Мені [Вік] років. Мої захоплення: [Інтереси].)
Привітатися(Привіт, я - [Ім'я]!)
створимо дочірні класи, які успадкують дані та методи батьківського класу.
Вчителі та учні мають багато спільних характеристик, таких як ім’я, стать, вік тощо, тому зручно визначити їх лише один раз у батьківському класі.
У клас Вчитель додамо лише новий атрибут Предмет для зберігання інформації про предмет, що викладає вчитель, і змінимо вміст повідомлення для методу Привітатися, щоб вітання вчителя містило інформацію про предмет викладання.
Успадковано від: Персона
Клас: Вчитель
Атрибути:
Предмет
Методи:
Привітатися(Привіт, моє ім'я - [Ім'я]. Я викладаю предмет [Предмет].)
У класі Учень змінимо параметр методу Привітатися, а атрибути будуть успадковуватись від батьківського класу Персона.
Успадковано від: Персона
Клас: Учень
Методи:
Привітатися(Мої вітання! Я - [Ім'я].)
Класи-нащадки Вчитель і Учень та батьківський клас Персона містять методи з однаковою назвою Привітатися, але для методів вказані різні параметри.
Реалізація тієї ж функціональності для декількох класів називають поліморфізмом - один з механізмів в сучасних об’єктоорієнтованих мовах програмування. Використовуючи поліморфізм, методи батьківського класу замінюються новими, що реалізують специфічні для даного нащадка дії. |
Тепер можна створити об’єкти із дочірніх класів. Наприклад, створимо об’єкт на основі дочірнього класу Вчитель.
Об'єкт: вчитель1
Властивості:
Ім'я: Ірина
Вік: 29
Стать: Жіноча
Інтереси: IT, Іноземні мови
Предмет: Англійська мова
Методи:
РозповістиПроСебе(Мені 29 років. Мої захоплення: ІТ, Іноземні мови.)
Привітатися(Привіт, моє ім'я - Ірина. Я викладаю предмет Англійська мова.)
5.1.2. Об’єкти в JavaScript
JavaScript
спроєктований на основі концепції простих об’єктів.
Згідно з цією концепцією, об’єкт - це набір властивостей, де кожна властивість складається з ключа (імені) та значення, яке асоціюється з цим ключем.
Об’єкт можна уявити у вигляді книжкової шафи.
Окрема книга (значення певної властивості) зберігається на своїй жанровій полиці (властивість), яка підписана, наприклад, Фантастика (ключ). За ключем полицю легко знайти, взяти з полиці почитати книгу (отримати значення властивості) або додати на полицю нову книгу (змінити значення властивості).
За таких умов книжкова шафа може містити багато жанрових полиць, але на полицях можна розмістити лише по одній книзі заданого жанру. У разі, коли значенням властивості є інший об’єкт, на полицю можна покласти кілька книг, наприклад, серію книг детективного жанру. Значенням властивості може бути функція, яку називають методом об’єкта.
Поруч із простими типами даних, на зразок int , float , boolean та інших, що можуть зберігати одне значення, об’єкти у JavaScript мають тип даних Object , який призначений для зберігання колекцій значень, зокрема й інших об’єктів.
|
Розглянемо, як створювати об’єкти в JavaScript
в контексті роботи з бібліотекою p5.js
.
Щоб створити порожній об’єкт, використовують спосіб із фігурними дужками {}
:
let colors = {};
Отож, ми створили порожній об’єкт і отримали покликання на цей об’єкт, використовуючи змінну colors
.
Якщо об’єкт оголосити як константу з використанням const , він може бути змінений. Річ у тім, що зарезервоване слово const захищає від змін саму змінну colors , а не її вміст. Оголошення через const викличе помилку (Uncaught TypeError: Assignment to constant variable ) у тому разі, коли змінній colors буде спроба присвоїти значення.
|
При використанні літерального синтаксису (застосування фігурних дужок {}
) можна відразу помістити в об’єкт властивості у вигляді пар ключ: значення, розділених комою.
Створимо об’єкт із такими властивостями:
let colors = { (1)
black: 0, // Black (2)
white: 255, // White (3)
"light gray": "#CDCED0" // Light Gray (4)
}
1 | Ініціалізація об’єкта colors . |
2 | Під ключем black в об’єкті зберігається значення 0 . |
3 | Під ключем white в об’єкті зберігається значення 255 . |
4 | Під ключем "light gray" в об’єкті зберігається значення "#CDCED0" . |
Імена (ключі) властивостей об’єкта є рядками JavaScript . Якщо ім’я властивості складається з декількох слів ("light gray" ), воно обов’язково записується в лапках або застосовується верблюжа нотація (lightGray ). Всі інші типи даних, які використовуються як ключі, будуть автоматично перетворені у рядки. Наприклад, якщо використовувати число 1 як ключ, то воно перетвориться в рядок "1" .
|
Цікавимось
Значеннями властивостей об’єкта можуть бути різні типи даних. В об’єкті colors
- це два числа і рядок.
Застосувавши аналогію із книжковою шафою, можна сказати, що об’єкт colors
- це книжкова шафа з трьома полицями (об’єкт містить три властивості), підписаними black
, white
і "light gray"
. Тепер на ці полиці можна додати нові книги, видалити книги, прочитати назви книг на полицях тощо, тобто, змінити значення властивостей об’єкта colors
.
Щоб переглянути, що містить об’єкт colors
, можна використати консоль вебпереглядача:
let colors = {
...
}
console.log(colors);
Результатом буде об’єкт з ключами (назвами кольорів) і значеннями (числовими й рядковим значеннями кольору):
{black: 0, white: 255, light gray: "#CDCED0"}
Об’єкти - це пари ключ: значення. Кожен ключ зберігає значення, а кожна пара ключ: значення є властивістю об’єкта. |
Щоб отримати значення властивостей об’єкта, використовується крапкова нотація - вказується назва об’єкта і через крапку записується ім’я його властивості, значення якої необхідно отримати:
let colors = {
...
}
console.log(colors.black); // 0
console.log(colors.white); // 255
У раніше створений об’єкт у будь-який момент можна додати нові властивості, використовуючи крапкову нотацію:
let colors = {
...
}
colors.tyrianPurple = "#5F0F40"; // Tyrian Purple
colors.rubyRed = "#9A031E"; // Ruby Red
colors.darkOrange = "#FB8B24"; // Dark Orange
console.log(colors);
Тепер об’єкт colors
міститиме нові властивості:
{black: 0, white: 255, light gray: "#CDCED0", tyrianPurple: "#5F0F40", rubyRed: "#9A031E", darkOrange: "#FB8B24"}
Для видалення властивості з об’єкта використовують оператор delete
:
let colors = {
...
}
delete colors.black;
Для властивостей, імена яких складаються з декількох слів, присвоєння чи зміна значення за допомогою крапкової нотації не працює:
let colors = {
...
}
colors.black = 0; // крапкова нотація спрацьовує, оскільки ім'я властивості складається з одного слова
colors.Midnight Green Eagle Green = "#0F4C5C"; // синтаксична помилка
Для таких випадків існує альтернативний спосіб доступу до властивостей через квадратні дужки []
. Такий спосіб спрацює з будь-яким ім’ям властивості:
let colors = {
...
}
colors["Midnight Green Eagle Green"] = "#0F4C5C";
console.log(colors);
Тепер об’єкт має такі властивості:
{black: 0, white: 255, light gray: "#CDCED0", tyrianPurple: "#5F0F40", rubyRed: "#9A031E", darkOrange: "#FB8B24", Midnight Green Eagle Green: "#0F4C5C"}
У разі, коли ключами в об’єкті є цілі числа або ключі є рядками, які містять прогалини, крапкова нотація не спрацює і для доступу до значень використовують квадратні дужки. Наприклад, colors[4] , colors["4"] , colors["Midnight Green Eagle Green"] .
|
Об’єкти іноді називають асоціативними масивами, оскільки кожна властивість об’єкта має ключ у вигляді рядка, який можна використовувати для доступу до властивості. |
У разі, коли необхідно перевірити існування певної властивості в JavaScript
-об’єкті, використовують зарезервоване слово in
:
"ключ" in об'єкт
Перевіримо, чи має об’єкт colors
певні властивості:
let colors = {
...
}
console.log("light gray" in colors); // true
console.log("Dark Cornflower Blue" in colors); // false
console.log("white" in colors); // true
let currentColor = "darkOrange";
console.log(currentColor in colors); // true
Для перегляду усіх властивостей об’єкта використовується цикл for...in
, який відрізняється від звичайного циклу for
:
for (let key in object) {
// тіло циклу виконується для кожної властивості об'єкта
}
Наприклад, надрукуємо усі властивості об’єкта colors
:
let colors = {
...
}
for (let color in colors) {
// ключі та значення ключів
console.log(color, colors[color]);
}
Вправа 43
Додати нові властивості для об’єкта colors
. Надрукувати в консолі вебпереглядача вміст об’єкта colors
.
На цю мить об’єкт colors
містить лише дані. Отож, всередині об’єкта colors
визначимо метод з назвою paintItTyrianPurple
, який буде встановлювати колір Tyrian Purple
для тла полотна.
let colors = {
...
tyrianPurple: "#5F0F40", // Tyrian Purple
...
paintItTyrianPurple: function() {
background(this.tyrianPurple);
}
};
Назва для методу обирається відповідно до того, що він має робити. Для оголошення методів об’єкта існує короткий синтаксис: не використовувати двокрапку і зарезервоване слово sketch.js
|
А тепер використаємо наш об’єкт colors
у застосунку, який зафарбовує тло полотна кольором Tyrian Purple
через певний проміжок часу.
let colors = { (1)
black: 0, // Black
white: 255, // White
"light gray": "#CDCED0", // Light Gray
tyrianPurple: "#5F0F40", // Tyrian Purple
rubyRed: "#9A031E", // Ruby Red
darkOrange: "#FB8B24", // Dark Orange
"Midnight Green Eagle Green": "#0F4C5C", // Midnight Green Eagle Green
paintItTyrianPurple() { (2)
background(this.tyrianPurple);
}
};
function setup() {
createCanvas(400, 400);
}
function draw() {
background(colors.darkOrange); (3)
// викликаємо метод paintItTyrianPurple() після 200 кадру
if (frameCount > 200) { (4)
colors.paintItTyrianPurple();
}
}
1 | Оголошення глобальної змінної colors , яка є покликанням на створений об’єкт із вказаними властивостями. |
2 | Однією із властивостей об’єкта є метод paintItTyrianPurple() , який при виклику зафарбує тло полотна значенням властивості tyrianPurple цього ж об’єкта colors . Щоб мати можливість використовувати ключі, які визначені для цього ж об’єкта, необхідно мати можливість посилатися на сам об’єкт. У JavaScript для таких цілей використовується зарезервоване слово this . |
3 | Встановлення кольору Dark Orange для тла при запуску застосунку, використовуючи властивість colors.darkOrange зі створеного об’єкта. |
4 | Якщо значення системної змінної frameCount стає понад 200 , тоді з об’єкта colors викликається метод paintItTyrianPurple() за допомогою крапкової нотації. Це відбудеться через три секунди (трішки більше) від запуску застосунку, оскільки за стандартним налаштуванням кадри оновлюються з частотою 60 кадрів в секунду. |
Зарезервоване слово this завжди буде гарантувати застосування правильного значення, коли контекст даних змінюється.
|
Як ми переконалися, об’єкти є корисними як структури для зберігання даних.
Вправа 44
Визначити ще один метод в об’єкті colors
і викликати його.
Для закріплення розуміння об’єктів, створимо ще один об’єкт - коло. Коло матиме кілька властивостей, що визначають його зовнішній вигляд, а також матиме кілька методів, які описують його поведінку.
let newCircle; (1)
function setup() {
createCanvas(200, 200);
newCircle = { (2)
x: width / 2,
y: height / 2,
sizeCircle: 75,
paint() { (3)
ellipse(this.x, this.y, this.sizeCircle, this.sizeCircle);
},
enlarge() { (4)
if (this.sizeCircle < 180) {
this.sizeCircle += 1;
}
}
};
}
function draw() {
background(220);
noStroke();
fill(46, 196, 182); // Tiffany Blue
newCircle.paint(); (5)
newCircle.enlarge(); (6)
}
1 | Оголошення глобальної змінної newCircle . |
2 | Створення об’єкта із властивостями: x та y , які визначають координати кола, sizeCircle , що визначає розмір кола. Змінна newCircle є покликанням на створений об’єкт. Створення об’єкта відбувається у функції setup() , оскільки для значень властивостей координат використовуються системні змінні width і height бібліотеки p5.js , які не визначені поза межами функції setup() . |
3 | Оголошення методу paint() , у якому використовується вбудована функція ellipse() бібліотеки p5.js , параметрами якої є властивості цього ж об’єкта, доступ до яких відбувається завдяки використанню зарезервованого слова this . Нагадаємо, що зарезервоване слово this вказує на сам об’єкт і дозволяє отримувати значення властивостей об’єкта, які визначені всередині об’єкта. |
4 | Оголошення методу enlarge() , який збільшує розмір кола на одну одиницю при кожному його виклику, доки розмір не стане понад 180 . Доступ до властивості sizeCircle також відбувається за допомогою зарезервованого слова this . |
5 | Виклик методу paint() для малювання кола на полотні. |
6 | Виклик методу enlarge() для збільшення розміру кола на одиницю, доки розмір не перевищує 180 . |
Переглядаємо Аналізуємо
З огляду на те, що об’єкти реального світу мають властивості, а іноді й поведінку, організація коду за допомогою об’єктів, що можуть мати властивості, які описують їх, і методи, що визначають їх поведінку, спрощує розуміння коду.
Вправа 45
Додати до об’єкта ще одну властивість, яка визначає колір, і метод, який зафарбовує коло значенням цієї властивості.
5.1.3. Ресурси
Корисні джерела
5.1.4. Контрольні запитання
Міркуємо Обговорюємо
-
Назвати приклади класів і об’єктів, що належать цим класам.
-
Що називають «атрибутами» і «методами» об’єкту? Навести приклади.
-
Що таке «об’єктоорієнтоване програмування»?
-
Пояснити такі поняття: а) «абстракція»; б) «інкапсуляція»; в) «успадкування»; г) «поліморфізм».
-
Як створюється об’єкт в
JavaScript
? -
Як отримати доступ до конкретної властивості об’єкта? Як переглянути відразу усі властивості об’єкта?
-
Для чого використовується зарезервоване слово
this
?
5.1.5. Практичні завдання
Початковий
-
Розглянути автомобіль як об’єкт. Які дані може мати автомобіль? Які методи? Продовжити ланцюжки властивостей.
// дані
color
...
// методи
toggleHeadlights()
...
Середній
-
Створити об’єкт прямокутника, розміри якого при запуску застосунку зменшуються до нуля, як представлено у демонстрації.
Високий
-
Обрати певний клас об’єктів з реального світу. Описати клас: вказати кілька атрибутів і методів, які отримають об’єкти, створені на основі класу. Створити три об’єкти. При виконанні завдання використати текстовий опис для класу та об’єктів.
-
Створити об’єкт кульки, яка вертикально падає униз із центру полотна. Досягаючи нижньої межі полотна кулька з’являється біля верхньої межі полотна і знову починає рухатися униз, як представлено у демонстрації.
Екстремальний
-
Створити об’єкт персонажа гри Pac-Man . Додатково «навчити» об’єкт рухатися, використовуючи клавіші клавіатури, як представлено у демонстрації.
5.2. Об’єкти, властивості, конструктори, методи
5.2.1. Функція-конструктор
Звичайний синтаксис {}
дозволяє створити лише один об’єкт. Але частіше виникає потреба створити багато однотипних об’єктів, наприклад, автомобілів, будинків, геометричних фігур (прямокутників, кіл тощо) і т. д.
Для створення багатьох об’єктів можна використати синтаксис звичайної функції JavaScript
.
Скористаємось цим і опишемо функцію createRectangle()
для створення об’єктів - прямокутників. Функція createRectangle()
буде приймати значення координат x
та y
об’єкта прямокутника, значення його розміру d
і кольору заливки c
як аргументи.
function setup() {
...
}
function draw() {
...
}
function createRectangle(x, y, d, c) {
const obj = {}; (1)
obj.x = x; (2)
obj.y = y;
obj.d = d;
obj.c = c;
obj.paint = function() { (3)
fill(this.c);
rect(this.x, this.y, this.d * 2, this.d);
};
return obj; (4)
}
1 | В тілі функції запишемо код для створення порожнього об’єкта obj за допомогою літерального синтаксису, що використовує фігурні дужки {} . |
2 | Визначимо для об’єкта obj властивості x , y , d і c , значеннями яких будуть значення параметрів функції createRectangle() . |
3 | Визначимо для об’єкта obj метод paint() , який у функціях fill() і rect() бібліотеки p5.js використовує значення властивостей об’єкта. |
4 | Повернемо із функції створений об’єкт obj . |
Тепер створимо кілька прямокутників, викликавши функцію createRectangle()
. Збережемо покликання на створені об’єкти у змінних r1
, r2
і r3
та звернемось до методів створених об’єктів.
let r1, r2, r3;
function setup() {
createCanvas(400, 400);
r1 = createRectangle(140, 170, 60, "#8A1543"); // Claret
r2 = createRectangle(40, 270, 60, "#D55C39"); // Flame
r3 = createRectangle(240, 70, 60, "#D6BA3A"); // Old Gold
}
function draw() {
background(220);
r1.paint();
r2.paint();
r3.paint();
}
function createRectangle(x, y, d, c) {
const obj = {};
obj.x = x;
obj.y = y;
obj.d = d;
obj.c = c;
obj.paint = function() {
fill(this.c);
rect(this.x, this.y, this.d * 2, this.d);
};
return obj;
}
Результатом використання синтаксису {}
і звичайної функції для створення об’єктів буде три об’єкти прямокутника.
JavaScript
надає також інший спосіб створення об’єктів - за допомогою функції-конструктора.
Перепишемо звичайну функцію вище у вигляді функції-конструктора:
function setup() {
...
}
function draw() {
...
}
function Rectangle(x, y, d, c) {
// const this = {}; неявно
this.x = x;
this.y = y;
this.d = d;
this.c = c;
this.paint = function() {
fill(this.c);
rect(this.x, this.y, this.d * 2, this.d);
};
// return this; неявно
}
Ім’я функції-конструктора зазвичай починається з великої літери - ця умовність використовується для спрощення розпізнавання функції-конструктора в коді. |
Як бачимо, функція-конструктор має всі ознаки звичайної функції, хоча вона нічого явно не повертає і явно не створює об’єкт - вона просто визначає властивості й методи.
Тепер викличемо функцію-конструктор для створення трьох прямокутників за допомогою оператора new
:
let r4, r5, r6;
function setup() {
createCanvas(400, 400);
r4 = new Rectangle(140, 170, 60, "#8A1543"); // Claret
r5 = new Rectangle(40, 70, 60, "#67158A"); // Blue Violet Color Wheel
r6 = new Rectangle(240, 270, 60, "#D67E3A"); // Bronze
}
function draw() {
background(220);
r4.paint();
r5.paint();
r6.paint();
}
function Rectangle(x, y, d, c) {
// const this = {}; неявно
this.x = x;
this.y = y;
this.d = d;
this.c = c;
this.paint = function() {
fill(this.c);
rect(this.x, this.y, this.d * 2, this.d);
};
// return this; неявно
}
Коли виконується виклик new Rectangle(...)
, за лаштунками відбуваються такі дії:
-
Створюється новий порожній об’єкт, покликанням на який буде зарезервоване слово
this
. -
Виконується код функції-конструктора, який зазвичай модифікує
this
, наприклад, додає в нього нові властивості. -
Повертається значення
this
.
Завдяки this , створені об’єкти використовують ті значення, які були присвоєні їм при створенні, а не будь-які інші значення. Важливо викликати функцію-конструктор з зарезервованим словом new . Якщо цього не зробити, зарезервоване слово this усередині функції-конструктора буде покликанням на глобальний, а не на конкретний об’єкт.
|
Тепер за допомогою функції-конструктора Rectangle()
можна створювати будь-які інші прямокутники зі своїми значеннями параметрів. Доступ до властивостей чи методів створених у такий спосіб об’єктів відбувається через крапкову нотацію.
Спосіб із функцією-конструктором створення об’єктів набагато зручніший і читабельніший, ніж щоразове використання літерала об’єкта для створення багатьох об’єктів. Власне, основна мета функцій-конструкторів - зручне створення однотипних об’єктів.
Функцію-конструктор можна розглядати як шаблон для створення нових об’єктів. |
Вправа 46
Створити кілька об’єктів за допомогою функції-конструктора Rectangle()
.
Напишемо функцію-конструктор для створення об’єкта кола у разі, коли жодних параметрів у функцію-конструктор не передається. Властивості та їх початкові значення і методи визначимо у самій функції-конструкторі.
Водночас використаємо для опису функції-конструктора синтаксис не як у звичайної функції, а як у функції без назви, як такої. Покликання на функцію-конструктор збережемо з ім’ям Circle
. Спосіб опису функції за допомогою зарезервованого слова function
і без її назви, як такої, використовується для опису методів у функції-конструкторі.
let circle1; (1)
function setup() {
createCanvas(200, 200);
circle1 = new Circle(); (2)
}
function draw() {
background(220);
fill(183, 9, 76); // Amaranth Purple
noStroke();
circle1.paint();
circle1.enlarge();
}
let Circle = function() { (3)
this.x = width / 2;
this.y = height / 2;
this.sizeCircle = 75;
this.paint = function() {
ellipse(this.x, this.y, this.sizeCircle, this.sizeCircle);
};
this.enlarge = function() {
if (this.sizeCircle < 180) {
this.sizeCircle += 1;
}
};
};
1 | Оголошуємо глобальну змінну circle1 . |
2 | Використовуємо зарезервоване слово new для створення нового об’єкта кола під назвою circle1 з функції-конструктора Circle() . Об’єкт отримує свої властивості з функції-конструктора. |
3 | Оголошення функції-конструктора Circle() . Для запису у методах значень властивостей використовуються зарезервоване слово this , крапкова нотація та імена властивостей. |
Переглядаємо Аналізуємо
Використовуючи функцію-конструктор можна продовжувати створювати нові кола. Новостворені кола - це окремі об’єкти, які можуть мати різні властивості. Значення цих властивостей можна змінювати після створення об’єктів.
Продемонструємо це, використовуючи такий код:
let circle1, circle2, circle3; (1)
function setup() {
createCanvas(600, 200);
circle1 = new Circle(); (2)
circle2 = new Circle();
circle3 = new Circle();
}
function draw() {
background(220);
fill(229, 195, 209); // Queen Pink
noStroke();
circle1.paint();
circle1.enlarge();
circle2.x = 100; (3)
circle2.paint();
circle2.enlarge();
circle3.x = 500; (3)
circle3.paint();
circle3.enlarge();
}
let Circle = function() {
this.x = width / 2;
this.y = height / 2;
this.sizeCircle = 75;
this.paint = function() {
ellipse(this.x, this.y, this.sizeCircle, this.sizeCircle);
};
this.enlarge = function() {
if (this.sizeCircle < 180) {
this.sizeCircle += 1;
}
};
};
1 | Оголошення трьох глобальних змінних circle1 , circle2 та circle3 . |
2 | Ці змінні стають трьома об’єктами кола при виклику функції-конструктора Circle() . |
3 | Тепер, коли є три окремі об’єкти кола, можна змінити їх властивості. У цьому разі відбувається зміна значення x -координати об’єктів. |
Переглядаємо Аналізуємо
5.2.2. Класи в JavaScript
Клас - це певний шаблон представлення об’єктів реального світу, на основі якого конструюються об’єкти, їхні властивості (дані) і поведінка (функції).
За допомогою цього шаблону можна створити просту модель складнішої сутності реального світу. Модель створюють так, щоб вона відображала найбільш важливі аспекти сутності таким способом, щоб з нею було зручно працювати.
Такий підхід зумовлює певне абстрагування, оскільки реальні об’єкти потрібно перевести в ідею об’єкта, і при цьому певні деталі реальних об’єктів залишаються поза увагою.
Абстракція - один із важливих принципів об’єктоорієнтованого програмування. |
Як відомо, згідно з термінологією об’єктоорієнтованого програмування дані зберігаються у змінних, які називаються полями даних (також властивостями або атрибутами), а функції називаються методами класу.
На основі одного класу можна створити безліч об’єктів, що відрізнятимуться один від одного значеннями полів даних. Об’єкти класу ще називають екземплярами чи примірниками класу.
Синтаксис сучасного JavaScript
містить спеціальну конструкцію class
, яка використовується для групування пов’язаних даних та функцій і надає можливості для об’єктоорієнтованого програмування.
Класи в JavaScript були введені в стандарті ECMAScript 2015 .
|
Опишемо клас з назвою Shape
, на основі якого можна буде створювати об’єкти різної форми, що рухаються на полотні.
function setup() {
...
}
function draw() {
...
}
class Shape { (1)
constructor() { (2)
this.x = 100; (3)
this.y = 100;
this.d = 25;
this.xSpeed = random(1);
this.ySpeed = random(1);
}
}
1 | Створення класу з ім’ям Shape за допомогою зарезервованого слова class . |
2 | Оголошення метода constructor() для створення та ініціалізації об’єктів. |
3 | Використання зарезервованого слова this для встановлення властивостей та їх значень (даних) для створених об’єктів. Даними створених об’єктів будуть: значення координат x та y , розмір d і початкові випадкові значення xSpeed і ySpeed приросту кожної із координат об’єкта під час його руху на полотні. |
Використаємо клас Shape
для створення конкретного об’єкта - кола.
let circle1; (1)
function setup() {
createCanvas(200, 200);
circle1 = new Shape(); (2)
print(circle1.x, circle1.y, circle1.d); (3)
}
function draw() {
background(220);
ellipse(circle1.x, circle1.y, circle1.d); (4)
}
class Shape { (5)
constructor() {
this.x = 100;
this.y = 100;
this.d = 25;
this.xSpeed = random(1);
this.ySpeed = random(1);
}
}
1 | Оголошення глобальної змінної circle1 . |
2 | Створення конкретного об’єкта кола з ім’ям circle1 за допомогою зарезервованого слова new і класу Shape . |
3 | Доступ до даних створеного об’єкта circle1 і друк в консолі вебпереглядача, використовуючи функцію print() із бібліотеки p5.js , значень x -координати та y -координати й діаметра кола d . |
4 | Використання даних об’єкта (circle1.x , circle1.y і circle1.d ) для малювання на полотні кола за допомогою функції ellipse() . |
5 | Опис класу Shape . |
Результатом виконання застосунку буде нерухомий об’єкт кола на полотні й значення координат та діаметра створеного кола, надруковані в консолі вебпереглядача:
100 100 25
Окрім даних, які надає клас Shape
об’єктам, створених на його основі, у класі можна описувати методи об’єктів.
Отож, опишемо у класі Shape
метод movement()
, який визначатиме рух об’єктів, і метод paint()
для малювання об’єкта та створимо кілька кіл на основі класу.
let circle1, circle2; (1)
function setup() {
createCanvas(200, 200);
circle1 = new Shape(); (3)
circle2 = new Shape();
}
function draw() {
background(220);
circle1.paint(); (4)
circle1.movement();
circle2.paint();
circle2.movement();
}
class Shape { (2)
constructor() {
this.x = 100;
this.y = 100;
this.d = 25;
this.xSpeed = random(1);
this.ySpeed = random(1);
}
paint() {
noFill();
strokeWeight(4);
stroke("#527B57"); // Amazon;
ellipse(this.x, this.y, this.d, this.d);
}
movement() {
this.x = this.x + this.xSpeed;
this.y = this.y + this.ySpeed;
if (this.x - this.d / 2 < 0 || this.x > width - this.d / 2) {
this.xSpeed = this.xSpeed * -1;
}
if (this.y - this.d / 2 < 0 || this.y > height - this.d / 2) {
this.ySpeed = this.ySpeed * -1;
}
}
}
Синтаксис класів відрізняється від літералів об’єктів. Усередині класів ставити кому між методами класу не потрібно, оскільки це викликає синтаксичну помилку. |
1 | Оголошення глобальних змінних circle1 і circle2 . |
2 | Визначення в описі класу Shape двох методів: paint() - для малювання кола на полотні та movement() - для зміни положення кола на полотні. У цих методах для доступу до значень властивостей об’єкта використовується зарезервоване слово this . |
3 | Створення об’єктів двох кіл. Імена змінних circle1 і circle2 вказують на ці два об’єкти. |
4 | Виклик методів для об’єктів кіл за допомогою крапкової нотації. |
Переглядаємо Аналізуємо
Цього разу дані й функції кола збережені (інкапсульовані) всередині самого об’єкта кола.
Коли об’єкт містить не лише дані, але і правила їх обробки, оформлені у вигляді методів, це називається інкапсуляцією. Інкапсуляція спрощує доступ до даних і методів об’єктів та є одним із механізмів в сучасних об’єктоорієнтованих мовах програмування. |
За допомогою метода movement()
здійснюється рух кіл на полотні.
Початкові значення координат x
та y
, діаметр d
створених кіл є однаковими, тому за допомогою методу paint()
усі об’єкти з’являються в одній точці полотна і мають той самий розмір.
Значення будь-яких даних об’єктів можна змінювати в момент створення самих об’єктів. Це реалізується наданням вхідних аргументів для методу constructor()
класу.
let circle1, circle2;
function setup() {
createCanvas(200, 200);
circle1 = new Shape(120, 50, 25, "#872D78"); // Violet Crayola (1)
circle2 = new Shape(60, 130, 55, "#E2A41E"); // Goldenrod
}
function draw() {
background(220);
circle1.paint();
circle1.movement();
circle2.paint();
circle2.movement();
}
class Shape {
constructor(x, y, d, c) { (2)
this.x = x;
this.y = y;
this.d = d;
this.c = c;
this.xSpeed = random(1);
this.ySpeed = random(1);
}
paint() {
noFill();
strokeWeight(4);
stroke(this.c);
ellipse(this.x, this.y, this.d);
}
movement() {
this.x = this.x + this.xSpeed;
this.y = this.y + this.ySpeed;
if (this.x - this.d / 2 < 0 || this.x > width - this.d / 2) {
this.xSpeed = this.xSpeed * -1;
}
if (this.y - this.d / 2 < 0 || this.y > height - this.d / 2) {
this.ySpeed = this.ySpeed * -1;
}
}
}
Коли викликається new Shape(...)
:
1 | Створюються нові об’єкти circle1 і circle2 . |
2 | Запускається constructor із вказаними аргументами, які стають властивостями this.x , this.y , this.d і this.c об’єкта. |
Тепер об’єкти кіл володіють власними значеннями координат, розміру і кольору.
Переглядаємо Аналізуємо
В JavaScript на відміну від оголошення функції, яку можна викликати до її оголошення, опис класу необхідно робити перед використанням класу для створення об’єктів, інакше виникне помилка, у разі, коли опис і використанням класу розташовані в одному блоці коду.
|
У прикладах вище опис класу знаходився поза межами функцій setup()
і draw()
, а сам клас використовувався для створення об’єктів у функції setup()
.
Код класу можна розмістити також в окремому файлі з ім’ям як у самого класу Shape.js
поруч з файлом index.html
.
class Shape {
constructor(x, y, d, c) {
this.x = x;
this.y = y;
this.d = d;
this.c = c;
this.xSpeed = random(1);
this.ySpeed = random(1);
}
paint() {
noFill();
strokeWeight(4);
stroke(this.c);
ellipse(this.x, this.y, this.d);
}
movement() {
this.x = this.x + this.xSpeed;
this.y = this.y + this.ySpeed;
if (this.x - this.d / 2 < 0 || this.x > width - this.d / 2) {
this.xSpeed = this.xSpeed * -1;
}
if (this.y - this.d / 2 < 0 || this.y > height - this.d / 2) {
this.ySpeed = this.ySpeed * -1;
}
}
}
Тепер, щоб використовувати клас, необхідно приєднати файл Shape.js
у файл index.html
перед файлом sketch.js
.
...
<script src="./Shape.js"></script>
<script src="./sketch.js"></script>
</body>
</html>
При виконанні застосунку результат буде аналогічним попередньому, але тепер код застосунку став значно структурованішим.
Як відомо, клас визначає як дані, так і поведінку своїх об’єктів. Для певного заданого класу (який називається суперкласом або батьківським) можна створити підклас (який називається дочірнім), об’єкти якого будуть поводитися трохи інакше, але решту поведінки успадкують від суперкласу.
Успадкування - це одна з ключових концепцій об’єктоорієнтованого програмування. |
Створимо на основі суперкласу Shape
підклас Rectangle
.
Спочатку створимо файл Rectangle.js
, який буде містити клас Rectangle
, і приєднаємо файл до сторінки index.html
:
...
<script src="./Shape.js"></script>
<script src="./Rectangle.js"></script>
<script src="./sketch.js"></script>
</body>
</html>
У JavaScript
для зв’язку між класами на зразок Shape
(суперклас) і Rectangle
(дочірній клас) використовується зарезервоване слово extends
.
Суперклас:
class Shape {
constructor(x, y, d, c) {
this.x = x;
this.y = y;
this.d = d;
this.c = c;
this.xSpeed = random(1);
this.ySpeed = random(1);
}
paint() {
noFill();
strokeWeight(4);
stroke(this.c);
ellipse(this.x, this.y, this.d);
}
movement() {
this.x = this.x + this.xSpeed;
this.y = this.y + this.ySpeed;
if (this.x - this.d / 2 < 0 || this.x > width - this.d / 2) {
this.xSpeed = this.xSpeed * -1;
}
if (this.y - this.d / 2 < 0 || this.y > height - this.d / 2) {
this.ySpeed = this.ySpeed * -1;
}
}
}
Підклас, який визначатиме клас прямокутників:
class Rectangle extends Shape { (1)
constructor(x, y, d, c) {
super(x, y, d, c); (2)
}
paint() {
fill(this.c);
strokeWeight(3);
stroke(this.c);
rect(this.x, this.y, this.d * 2, this.d);
}
movement() {
super.movement();
}
}
1 | Запис class Rectangle extends Shape можна прочитати так: клас Rectangle розширює клас Shape. |
2 | Підклас Rectangle викликає конструктор свого суперкласу Shape за допомогою зарезервованого слова super . |
У підкласі Rectangle
ми визначили конструктор підкласу, який приймає аргументи x
, y
, r
, c
. У цьому разі необхідно обов’язково вказати виклик конструктора суперкласу за допомогою зарезервованого слова super(...)
, а усередині дужок розмістити аргументи, які потрібно передати конструктору суперкласу.
Якщо у підкласі Rectangle
не описувати його власний конструктор, він створиться автоматично і передасть усі аргументи, які були вказані при створенні об’єкта, конструктору суперкласу.
Зверніть увагу, методи paint()
і movement()
у дочірньому класі і суперкласі мають однакові назви. В методі movement()
у дочірньому класі використовується зарезервоване слово super
для виклику методу movement()
із суперкласу. У цьому разі об’єкти, створені на основі класів Shape
і Rectangle
матимуть однакову поведінку руху.
А от метод paint()
хоча й має однакові назви для суперкласу і підкласу, але відрізняється вмістом. Який із методів paint()
буде викликатися при створенні об’єктів на основі цих класів?
Створимо об’єкти кола і прямокутника на основі класів.
let circle1, rectangle1;
function setup() {
createCanvas(200, 200);
circle1 = new Shape(120, 50, 25, "#872D78"); // Violet Crayola (1)
rectangle1 = new Rectangle(60, 130, 55, "#E2A41E"); // Goldenrod (2)
}
function draw() {
background(220);
circle1.paint(); (3)
circle1.movement();
rectangle1.paint(); (4)
rectangle1.movement();
}
1 | Створення об’єкта circle1 на основі суперкласа Shape . |
2 | Створення об’єкта rectangle1 на основі підкласа Rectangle . |
3 | Виклик методів paint() і movement() для об’єкта circle . Це методи суперкласа. |
4 | Виклик методів paint() і movement() для об’єкта rectangle1 . Метод movement() - із суперкласа, оскільки в самому методі підкласа записаний виклик метода movement() із суперкласа Shape за допомогою зарезервованого слова super . Метод paint() - це метод із підкласа Rectangle і він перевизначає (підміняє) метод paint() в класі Shape . |
Переглядаємо Аналізуємо
Коли метод, що викликається, залежить від того, на який об’єкт вказує покликання (circle чи rectangle у цьому разі), це називається поліморфізмом. Поліморфізм - один із механізмів в сучасних об’єктоорієнтованих мовах програмування. Використовуючи поліморфізм, методи батьківського класу замінюються новими методами, що реалізують специфічні для даного нащадка дії.
|
З демонстрації роботи застосунку видно, що відбивання прямокутника від меж полотна відбувається не зовсім так, як очікується. Тому змінимо код у методі movement()
підкласу Rectangle
і використаємо механізм поліморфізму.
class Rectangle extends Shape {
constructor(x, y, d, c) {
super(x, y, d, c);
}
paint() {
...
}
movement() {
this.x = this.x + this.xSpeed;
this.y = this.y + this.ySpeed;
if (this.x < 0 || this.x > width - this.d * 2) {
this.xSpeed = this.xSpeed * -1;
}
if (this.y < 0 || this.y > height - this.d) {
this.ySpeed = this.ySpeed * -1;
}
}
}
Переглядаємо Аналізуємо
Тепер обидва методи paint()
і movement()
із підкласу Rectangle
перевизначають однойменні методи в класі Shape
.
Вправа 47
Створити кілька об’єктів за допомогою суперкласу Shape
і підкласу Rectangle
і надрукувати в консолі вебпереглядача певні властивості створених об’єктів.
Цікавимось Додатково
5.2.3. Ресурси
Корисні джерела
5.2.4. Контрольні запитання
Міркуємо Обговорюємо
-
Для чого використовується функція-конструктор?
-
Як створити клас в
JavaScript
? -
Пояснити, що означають поняття: а) «абстракція»; б) «інкапсуляція»; в) «успадкування»; г) «поліморфізм».
5.2.5. Практичні завдання
Початковий
-
Описана функція-конструктор
Rectangle()
, яка створює три об’єкти прямокутника. Отримати доступ до значень властивостей створених об’єктів і надрукувати їх в консолі вебпереглядача.
let r1, r2, r3;
function setup() {
createCanvas(400, 400);
r1 = new Rectangle(140, 170, 60, "#8A1543"); // Claret
r2 = new Rectangle(240, 70, 60, "#67158A"); // Blue Violet Color Wheel
r3 = new Rectangle(40, 270, 60, "#808A15"); // Olive
}
function draw() {
background(220);
r1.paint();
r2.paint();
r3.paint();
}
function Rectangle(x, y, d, c) {
this.x = x;
this.y = y;
this.d = d;
this.c = c;
this.paint = function () {
fill(this.c);
rect(this.x, this.y, this.d * 2, this.d);
};
}
-
Заповнити прогалини у файлі
sketch.js
, щоб утворився застосунок, в якому на основі класу створюється кілька різноколірних кіл різного розміру. Орієнтовний взірець представлено у демонстрації.
let circle1, ..., circle3;
function setup() {
createCanvas(200, 200);
circle1 = new Circle(...);
circle2 = new Circle(...);
... = new Circle(...);
}
function draw() {
background(220);
noStroke();
circle1.paint();
circle2.paint();
...;
}
class Circle {
constructor(c) {
this.x = random(width);
this.y = random(height);
this.d = random(150);
... = c;
}
paint() {
fill(...);
ellipse(..., ..., ..., ...);
}
}
Середній
-
Використати клас
Rectangle
для створення застосунку, в якому відбуватиметься рух кількох об’єктів - прямокутників, як представлено у демонстрації.
class Rectangle {
constructor(x, y, d, c) {
this.x = x;
this.y = y;
this.d = d;
this.c = c;
}
paint() {
fill(this.c);
rect(this.x, this.y, this.d * 2, this.d);
}
movement() {
if (this.x > width + this.d) {
this.x = -this.d;
}
this.x += 1;
}
}
-
Змінити швидкості руху прямокутників із попереднього завдання, як представлено у демонстрації.
Високий
-
Описати структуру класу Людина, використовуючи інструментарій бібліотеки
p5.js
. Наприклад, властивістьhairColor
(колір волосся) матиме значення кольору у форматіRGB
, властивістьstature
(зріст) буде цілим числом і т. д. Такий підхід застосувати також для методів класу. Ініціалізувати конкретний об’єкт класу і викликати методи для цього об’єкта. -
Створити застосунок, що імітує рух автомобіля. Наприклад, автомобіль може прискорюватися чи гальмуватися, вмикати/вимикати фари тощо. Орієнтовний варіант поведінки автомобіля представлено у демонстрації.
Екстремальний
-
Створити застосунок, що імітує рух автомобілів по автомагістралі. Орієнтовний взірець роботи застосунку представлено у демонстрації.
5.3. Події та обробники подій. Взаємодія об’єктів
У цьому розділі увага приділяється принципам взаємодії на прикладі взаємодії двох об’єктів. |
Використання об’єктів в застосунках наближено реалізує логіку навколишнього світу. Як і в реальному світі, в об’єктоорієнтованому програмуванні об’єкти можуть взаємодіяти один з одним.
Про механізм взаємодії об’єктів можна міркувати, як про обмін повідомленнями між об’єктами - коли об’єкту потрібний інший об’єкт для виконання будь-якої дії, він надсилає повідомлення іншому об’єкту через один з його методів і чекає відповіді, яка повертається у вигляді певного значення. Отож, поглянемо як об’єкти можуть «спілкуватися» між собою на прикладі взаємодії об’єктів двох кіл.
Спочатку опишемо клас Circle
, на основі якого будуть створюватися об’єкти.
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
}
class Circle {
constructor(x, y, r, c) { (1)
this.x = x;
this.y = y;
this.r = r;
this.c = c;
this.overlapping = false;
}
movement() { (2)
this.x = this.x + random(-3, 3);
this.y = this.y + random(-3, 3);
}
paint() { (3)
ellipseMode(RADIUS);
stroke(255);
strokeWeight(3);
fill(this.c);
ellipse(this.x, this.y, this.r, this.r);
}
}
1 | Конструктор класу отримує кілька вхідних параметрів майбутніх об’єктів: значення координат x та y об’єкта кола на полотні, радіус кола r і колір його заливки c . Ці значення стають властивостями об’єкта при його створенні. Ще одна властивість, яку отримує об’єкт, - this.overlapping - визначена в самому конструкторі. Властивість this.overlapping містить логічне значення, що визначає, чи об’єкт торкається іншого об’єкта на полотні (true ) чи ні (false ). Початкове значення цієї властивості false . |
2 | Метод movement() визначає поведінку об’єктів - випадкові переміщення на полотні на невеликі відстані від точки їх створення. |
3 | Метод paint() використовується для малювання об’єктів кіл на полотні. |
У методі paint() увімкнений режим ellipseMode(RADIUS) - перші два параметри функції ellipse() - це координати x і y центральної точки фігури, а третій і четвертий параметри - це половина ширини та висоти фігури. Причина використання цього режиму - інтерпретація значення властивості this.r об’єкта як значення радіуса кола.
|
Створимо два об’єкти кола на основі описаного нами класу Circle
і продемонструємо визначену для них поведінку:
let circle1;
let circle2;
function setup() {
createCanvas(200, 200);
circle1 = new Circle(100, 100, 20, "rgba(255, 214, 64, 0.8)"); // Mustard
circle2 = new Circle(100, 100, 40, "rgba(0, 214, 196, 0.8)"); // Turquoise
}
function draw() {
background(220);
circle1.paint();
circle2.paint();
circle1.movement();
circle2.x = mouseX;
circle2.y = mouseY;
}
class Circle {
constructor(x, y, r, c) {
this.x = x;
this.y = y;
this.r = r;
this.c = c;
this.overlapping = false;
}
movement() {
this.x = this.x + random(-3, 3);
this.y = this.y + random(-3, 3);
}
paint() {
ellipseMode(RADIUS);
stroke(255);
strokeWeight(3);
fill(this.c);
ellipse(this.x, this.y, this.r, this.r);
}
}
Переглядаємо Аналізуємо
Як видно з демонстрації, об’єкти рухаються на полотні відповідно до власної поведінки, не взаємодіючи один з одним.
Перший об’єкт кола здійснює невеликі хаотичні переміщення на полотні завдяки методу movement()
. Другий об’єкт кола хоча й має метод movement()
у своєму арсеналі, але для нього ми передбачили іншу поведінку.
У функції draw()
щоразу відбувається звернення до властивостей координат другого кола і присвоєння їм значень координат вказівника миші. У такий спосіб ми інтерактивно керуємо рухом другого об’єкта кола на полотні.
Змінимо наш застосунок так, щоб об’єкти впливали один на одного і цей вплив можна було візуалізувати. Як приклад взаємодії між об’єктами, розглянемо перекривання кіл на полотні.
Об’єкти починатимуть взаємодіяти за умови настання події - наближення кіл на відстань d
один від одного. У момент настання цієї події відбуватиметься взаємодія об’єктів, наслідком якої, наприклад, буде зміна кольору тла полотна.
Візьмемо мінімальне значення відстані між об’єктами, по суті, коли об’єкти торкаються один одного. Оскільки об’єкти є колами, то найменше значення відстані між колами буде описуватися нерівністю:
Об’єкти кіл почнуть взаємодіяти, як тільки відстань d
між ними буде меншою за суму їх радіусів r1 + r2
(без врахування товщини межі кіл).
Додамо у клас Circle
метод, який буде слідкувати за тим, чи настала подія наближення об’єктів на відстань d
і використаємо цей метод у функції draw()
.
let circle1;
let circle2;
function setup() {
createCanvas(200, 200);
circle1 = new Circle(100, 100, 20, "rgba(255, 214, 64, 0.8)"); // Mustard
circle2 = new Circle(100, 100, 40, "rgba(0, 214, 196, 0.8)"); // Turquoise
}
function draw() {
background(220);
circle1.intersects(circle2); (4)
if (circle1.overlapping || circle2.overlapping) { (5)
background(128, 113, 130); // Old Lavender
}
circle1.paint();
circle2.paint();
circle1.movement();
circle2.x = mouseX;
circle2.y = mouseY;
}
class Circle {
constructor(x, y, r, c) {
this.x = x;
this.y = y;
this.r = r;
this.c = c;
this.overlapping = false;
}
movement() {
this.x = this.x + random(-3, 3);
this.y = this.y + random(-3, 3);
}
paint() {
ellipseMode(RADIUS);
stroke(255);
strokeWeight(3);
fill(this.c);
ellipse(this.x, this.y, this.r, this.r);
}
intersects(other) { (1)
this.overlapping = false;
other.overlapping = false;
let d = dist(this.x, this.y, other.x, other.y); (2)
if (d < this.r + other.r) { (3)
this.overlapping = true;
other.overlapping = true;
}
}
}
1 | Описуємо метод intersects() , який приймає один параметр - об’єкт other . В методі встановлюємо значення false для властивостей this.overlapping (поточний об’єкт, для якого викликається метод) і other.overlapping (об’єкт other ) - спочатку об’єкти не перекриваються. Умовно метод можна назвати слухачем події наближення об’єктів на відстань d , меншу суми радіусів об’єктів, оскільки він постійно виконується у функції draw() і обчислює значення відстаней d між щоразу новими положеннями об’єктів кіл. |
2 | Оголошуємо змінну d , яка буде вказувати на обчислене значення відстані між об’єктами за допомогою функції dist() . Перша пара координат - this.x і this.y - координати центра поточного об’єкта кола, друга пара координат other.x і other.y - координати центра об’єкта кола other . |
3 | За допомогою вказівки розгалуження if перевіряємо виконання умови, яку ми обрали для взаємодії об’єктів d < this.r + other.r . Якщо вона виконується, то властивості this.overlapping і other.overlapping отримують значення true , тобто об’єкти перекриваються. |
4 | Викликаємо метод intersects() для поточного об’єкта circle1 і з аргументом у вигляді другого об’єкта circle2 . |
5 | За допомогою вказівки розгалуження if перевіряємо значення властивостей circle1.overlapping або circle2.overlapping на істинність. У разі значення true для будь-якої властивості, змінюється колір тла полотна. Умовно цей блок коду можна назвати обробником події, оскільки він містить код, який змінює роботу застосунку з настанням події. У разі двох об’єктів перевіряти можна лише одну із властивостей. |
Переглядаємо Аналізуємо
Вправа 48
Змінити поведінку для другого об’єкта кола, викликавши для нього метод movement()
.
Базові поняття події, слухача і обробника події детально розглядаються в Додатку B. |
5.3.1. Ресурси
Корисні джерела
5.3.2. Контрольні запитання
Міркуємо Обговорюємо
-
Завдяки чому у застосунку реалізується взаємодія між об’єктами?
-
Оберіть два об’єкти з реального світу та опишіть їхню взаємодію.
5.3.3. Практичні завдання
Початковий
-
У класі
Circle
описаний методchangeColor()
, який змінює колір заливки об’єкта. Викликати цей метод для об’єкта при його взаємодії з іншим об’єктом, як представлено у демонстрації.
let circle1;
let circle2;
function setup() {
createCanvas(200, 200);
circle1 = new Circle(100, 100, 20);
circle2 = new Circle(100, 100, 40);
}
function draw() {
background(220);
circle1.intersects(circle2);
if (circle1.overlapping || circle2.overlapping) {
// виклик методу у разі, коли об'єкти взаємодіють
} else {
// виклик методу у разі, коли об'єкти не взаємодіють
}
circle1.paint();
circle2.paint();
circle2.x = mouseX;
circle2.y = mouseY;
}
class Circle {
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
this.c = "rgba(255, 214, 64, 0.8)"; // Mustard
this.overlapping = false;
}
paint() {
ellipseMode(RADIUS);
stroke(255);
strokeWeight(3);
fill(this.c);
ellipse(this.x, this.y, this.r, this.r);
}
intersects(other) {
this.overlapping = false;
other.overlapping = false;
let d = dist(this.x, this.y, other.x, other.y);
if (d < this.r + other.r) {
this.overlapping = true;
other.overlapping = true;
}
}
changeColor(currentColor) {
this.c = color(currentColor);
}
}
Середній
-
Описати у класі
Circle
із попереднього завдання метод, який змушує об’єкти рухатися і, як наслідок, взаємодіяти. Орієнтовний взірець роботи застосунку представлено у демонстрації.
Високий
-
Створити застосунок, в якому при взаємодії двох об’єктів розміри одного з них збільшуються. Коли ж об’єкти віддаляються, розмір повертається до початкового значення. Орієнтовний взірець роботи застосунку представлено у демонстрації.
Екстремальний
-
Створити застосунок, в якому рухаються два об’єкти кола. При зближенні об’єктів між їх центрами малюється лінія, що їх з’єднує, а коли об’єкти віддаляються, то лінія зникає. Рух об’єктів відбувається по коловій траєкторії. Орієнтовний зразок роботи застосунку представлено у демонстрації.
5.4. Проєктування взаємодії програмних об’єктів
При взаємодії об’єктів в інтерактивних 2D
-застосунках застосовують різні алгоритми для виявлення зіткнень між об’єктами.
Виявлення зіткнення (detect collision ) - це обчислювальна задача виявлення перетину двох або більше об’єктів. Виявлення зіткнень є класичним питанням обчислювальної геометрії та має застосування в різних областях обчислювальної техніки, насамперед у комп’ютерній графіці, комп’ютерних іграх і комп’ютерному моделюванні.
|
Алгоритми виявлення зіткнень залежать від типу фігур, які можуть зіткнутися, наприклад, прямокутник з прямокутником, прямокутник з колом, коло з колом тощо.
Як тільки настає подія, що два об’єкти перетинаються, то можна виконати певний код, який, наприклад, змінює властивості чи поведінку об’єктів, які взаємодіють.
Код, що обробляє події зіткнення об’єктів, може складатися зі звичайних перевірок за допомогою вказівки розгалуження if
та її розширення else if
, але й може містити складні алгоритми, які обробляють сотні й тисячі об’єктів, імітуючи фізику реального світу.
Розглянемо найпоширеніші способи, які використовуються для виявлення зіткнень у двовимірних застосунках.
При виявленні зіткнень необхідно правильно розуміти координати розташування фігур на полотні. Тому обов’язково враховуйте те, який режим інтерпретації координат зараз увімкнений. Окремі режими вмикаються функціями на зразок ellipseMode() , rectMode() та іншими.
|
5.4.1. Зіткнення з межею
Як приклад взаємодії розглянемо застосунок, в якому здійснюється імітації падіння м’яча на нижню межу полотна, відбивання його від межі й черговий рух у напрямку до межі. Щоразу висота підняття м’яча зменшується. Це триває до тих пір, доки м’яч не зупиниться.
let ball;
function setup() {
createCanvas(200, 200);
ball = new Ball(); (4)
}
function draw() {
background(220);
ball.paint(); (5)
ball.movement();
}
class Ball {
constructor() { (1)
this.c = color("#07A9A3"); // Light Sea Green
this.r = 30;
this.x = width / 2;
this.y = this.r;
this.velocity = 0;
this.gravity = 0.1;
}
paint() { (2)
ellipseMode(RADIUS);
fill(this.c);
stroke(255);
strokeWeight(2);
ellipse(this.x, this.y, this.r, this.r);
}
movement() { (3)
this.y = this.y + this.velocity;
this.velocity = this.velocity + this.gravity;
if (this.y + this.r > height) {
this.velocity = this.velocity * -0.75;
this.y = height - this.velocity - this.r;
}
}
}
1 | У конструкторі класу Ball визначені властивості об’єкта м’яча: this.c - колір м’яча, this.r - радіус м’яча, this.x і this.y - початкові координати м’яча на полотні, this.velocity - початкова швидкість руху м’яча (величина, яка змінює значення y -координати, у такий спосіб імітуючи вертикальний рух) і this.gravity - значення «гравітації» (величина, яка змінює значення швидкості руху). Ключове слово this є покликанням на об’єкт м’яча. |
2 | Метод paint() використовується для створення об’єкта м’яча, використовуючи його властивості. |
3 | У методі movement() реалізовано виявлення зіткнення з нижньою межею полотна за допомогою вказівки розгалуження if . |
4 | Створення об’єкта м’яча ball . |
5 | Виклик методів об’єкта ball . |
Розглянемо детальніше метод movement()
, у якому реалізовано виявлення зіткнення для цієї задачі.
Як тільки м’яч починає рухатися донизу, значення його властивості ball.velocity
починає зростати від нуля, а отже відповідно зростає значення координати ball.y
.
В умові вказівки розгалуження if
щоразу перевіряється істинність нерівності this.y + this.r > height
- м’яч перетинає нижню межу полотна?
В момент дотику м’яча до нижньої межі полотна властивість ball.velocity
отримує від’ємне значення ball.velocity * -0.75
і знову починає збільшуватися. При цьому рух продовжується тепер вертикально вгору, оскільки ball.y
зменшується.
При майже нульовому значенню ball.velocity
координата ball.y
суттєво не змінюється - м’яч «зависає». Значення ball.velocity
знову продовжує зростати від нуля, а отже відповідно зростає значення координати ball.y
- м’яч рухається донизу знову.
Процеси підіймання і падіння м’яча продовжуються доти, доки такі зміни властивості ball.velocity
в додатній і від’ємний боки стають незначними. В цей момент координата ball.y
набуває практично однакових значень і знаходиться на відстані радіуса м’яча ball.r
від нижньої межі полотна. Рядок this.y = height - this.velocity - this.r;
дозволяє зробити зупинку м’яча більш плавною.
Переглядаємо Аналізуємо
Вправа 49
Проаналізувати поведінку м’яча за допомогою друку в консолі вебпереглядача значень його властивостей під час руху.
Опишемо виявлення зіткнення у разі взаємодії об’єкта під час його руху з кількома межами полотна.
let ball;
function setup() {
createCanvas(200, 200);
ball = new Ball(); (4)
}
function draw() {
background(220);
ball.paint(); (5)
ball.movement();
}
class Ball {
constructor() { (1)
this.c = color("#07A9A3"); // Light Sea Green
this.r = 20;
this.x = this.r;
this.y = height / 2;
this.xVelocity = 0.7;
this.yVelocity = 1;
}
paint() { (2)
ellipseMode(RADIUS);
fill(this.c);
stroke(255);
strokeWeight(2);
ellipse(this.x, this.y, this.r, this.r);
}
movement() { (3)
this.x += this.xVelocity;
this.y += this.yVelocity;
if (this.x < this.r || this.x > width - this.r) {
this.xVelocity *= -1;
}
if (this.y < this.r || this.y > height - this.r) {
this.yVelocity *= -1;
}
}
}
1 | У конструкторі класу Ball визначені властивості об’єкта м’яча: this.c - колір м’яча, this.r - радіус м’яча, this.x і this.y - початкові координати м’яча на полотні, this.xVelocity - горизонтальна складова швидкості руху м’яча (величина, яка змінює значення x -координати) і this.yVelocity - вертикальна складова швидкості руху м’яча (величина, яка змінює значення y -координати). |
2 | Метод paint() використовується для створення об’єкта м’яча, використовуючи його властивості. |
3 | У методі movement() реалізовано виявлення зіткнення з усіма межами полотна за допомогою вказівок розгалуження if . |
4 | Створення об’єкта м’яча ball . |
5 | Виклик методів об’єкта ball . |
В методі movement()
збільшуються значення координат м’яча, а вказівка розгалуження if
перевіряє, чи зіткнувся м’яч з лівою, чи правою межею полотна, і якщо так, властивість this.xVelocity
змінює своє значення на протилежне (якщо значення було додатнім - стає від’ємним і навпаки). Як наслідок змінюється значення координати this.x
- м’яч змінює напрямок руху, інакше кажучи, відбивається від лівої чи правої меж полотна.
Аналогічно відбувається і для верхньої та нижньої меж. Також в умовах вказівок розгалуження if
було враховано значення радіуса м’яча this.r
, щоб відбивання відбувалось при дотику м’яча до межі, а не десь за межею.
Переглядаємо Аналізуємо
Розглянемо випадок руху об’єктів прямокутної форми та відбивання їх від меж полотна.
За стандартним налаштуванням функція для малювання прямокутника rect()
інтерпретує перші два параметри як верхній лівий кут фігури, тоді як третій і четвертий параметри є її шириною та висотою. Враховуючи це, напишемо метод movement()
, який буде виявляти зіткнення прямокутника з межами полотна.
let rectangle1;
let rectangle2;
let rectangle3;
function setup() {
createCanvas(200, 200);
rectangle1 = new Rectangle();
rectangle2 = new Rectangle();
rectangle3 = new Rectangle();
}
function draw() {
background(120);
rectangle1.paint();
rectangle2.paint();
rectangle3.paint();
rectangle1.movement();
rectangle2.movement();
rectangle3.movement();
}
class Rectangle {
constructor() {
this.d = random(20, 40);
this.x = width / 2;
this.y = height / 2;
this.c = color(random(255), random(255), random(255), random(200, 220));
this.xVelocity = random(0.5, 1);
this.yVelocity = random(0.5, 1);
}
paint() {
stroke(255);
strokeWeight(3);
fill(this.c);
rect(this.x, this.y, this.d * 2, this.d);
}
movement() {
this.x += this.xVelocity;
this.y += this.yVelocity;
if (this.x < 0 || this.x > width - this.d * 2) {
this.xVelocity *= -1;
}
if (this.y < 0 || this.y > height - this.d) {
this.yVelocity *= -1;
}
}
}
Код метода movement()
обчислює відстань до правої та до нижньої меж полотна, враховує ширину й висоту прямокутника відповідно та порівнює ці відстані з поточним значенням координат лівого верхнього кута прямокутника.
Якщо значення координат this.x
і this.y
є більшими за ці відстані, то властивість this.xVelocity
змінює своє значення на протилежне (якщо значення було додатнім - стає від’ємним і навпаки). Прямокутник змінює напрямок руху, інакше кажучи, відбивається від лівої чи нижньої меж полотна.
Така перевірка відбувається і для верхньої (this.y < 0
) і лівої (this.x < 0
) меж.
Переглядаємо Аналізуємо
Вправа 50
Змінити код застосунку так, щоб від меж полотна відбивалися об’єкти квадратної форми.
5.4.2. Зіткнення між колами
У попередньому розділі ми розглянули, як відбувається виявлення зіткнень між об’єктами кіл. Ще раз наведемо код застосунку, який візуалізує виявлення зіткнень між колами.
let circle1;
let circle2;
function setup() {
createCanvas(200, 200);
circle1 = new Circle();
circle2 = new Circle();
}
function draw() {
background(220);
circle1.intersects(circle2);
if (circle1.overlapping || circle2.overlapping) {
circle2.changeColor("rgba(233, 218, 84, 0.8)"); // Minion Yellow
}
circle1.paint();
circle2.paint();
circle2.changeColor("rgba(193, 71, 119, 0.8)"); // Fuchsia Rose
circle2.x = mouseX;
circle2.y = mouseY;
}
class Circle {
constructor() {
this.x = random(width);
this.y = random(height);
this.r = random(20, 35);
this.c = color("rgba(7, 169, 163, 0.8)"); // Light Sea Green
this.overlapping = false;
}
paint() {
ellipseMode(RADIUS);
stroke(255);
strokeWeight(3);
fill(this.c);
ellipse(this.x, this.y, this.r, this.r);
}
intersects(other) {
this.overlapping = false;
other.overlapping = false;
let d = dist(this.x, this.y, other.x, other.y);
if (d < this.r + other.r) {
this.overlapping = true;
other.overlapping = true;
}
}
changeColor(currentColor) {
this.c = color(currentColor);
}
}
Переглядаємо Аналізуємо
5.4.3. Зіткнення із вказівником миші
Окремим випадком виявлення зіткнень є взаємодія вказівника миші з іншими об’єктами.
Вказівник миші як об’єкт має дві властивості - координати точки вказівника на полотні. Значення цих властивостей можна отримати із системних змінних бібліотеки p5.js
- mouseX
і mouseY
відповідно.
У разі, коли об’єктом є коло, розв’язання задачі зводиться до обчислення відстані d
між точкою вказівника миші й точкою центру кола за допомогою функції dist() , яка обчислює відстань між двома точками.
Умовою виявлення зіткнення буде нерівність:
де r
- радіус кола, а зарезервоване слово this
вказує на об’єкт кола.
Наведемо зразок інтерактивного застосунку, який реалізує вищезгаданий підхід.
let circle1;
function setup() {
createCanvas(200, 200);
circle1 = new Circle();
}
function draw() {
circle1.intersects();
if (circle1.overlapping) {
circle1.changeColor("rgba(233, 218, 84, 0.8)"); // Minion Yellow
background("rgba(7, 169, 163, 0.8)"); // Light Sea Green
} else {
circle1.changeColor("rgba(7, 169, 163, 0.8)"); // Light Sea Green
background("rgba(233, 218, 84, 0.8)"); // Minion Yellow
}
circle1.paint();
}
class Circle {
constructor() {
this.x = random(width);
this.y = random(height);
this.r = random(20, 35);
this.overlapping = false;
}
paint() {
ellipseMode(RADIUS);
stroke(255);
strokeWeight(3);
fill(this.c);
ellipse(this.x, this.y, this.r, this.r);
}
intersects() {
this.overlapping = false;
let d = dist(this.x, this.y, mouseX, mouseY);
if (d < this.r) {
this.overlapping = true;
}
}
changeColor(currentColor) {
this.c = color(currentColor);
}
}
Якщо відстань d
між центром кола і точкою вказівника миші буде меншою за радіус кола r
, то вказівник миші перебуває всередині кола, отже він взаємодіє з колом.
Результатом взаємодії вказівника миші й кола є зміна кольору тла полотна і кола.
Переглядаємо Аналізуємо
У разі, коли замість кола використовується прямокутник, обчислення відстані між точкою вказівника і точкою всередині прямокутника не дасть потрібних результатів.
Замість цього необхідно перевірити, чи знаходиться точка вказівника миші між лівим і правим, верхнім і нижнім межами прямокутника.
За стандартним налаштуванням функція для малювання прямокутника rect()
інтерпретує перші два параметри як верхній лівий кут фігури, тоді як третій і четвертий параметри є її шириною та висотою. Відштовхуючись від цього, напишемо метод, який буде виявляти зіткнення прямокутника з вказівником миші, а результатом взаємодії буде зміна властивостей прямокутника і полотна.
Умовою виявлення зіткнення будуть нерівності:
\$mouseX < this.x + this.d * 2\$
\$mouseY > this.y\$
\$mouseY < this.y + this.d\$
де mouseX
і mouseY
- координати точки вказівника миші, this.x
і this.y
- координати верхнього лівого кута об’єкта прямокутника, this.x + this.d * 2
- ширина об’єкта прямокутника, this.y + this.d
- висота об’єкта прямокутника.
Умова буде істинною, коли усі ці нерівності будуть істинними. Тому усі нерівності в коді застосунку необхідно об’єднати логічними операторами &&
, щоб у підсумку точно знати, що вказівник миші розташований десь всередині прямокутника.
У блоці коду із розгалуженнями у функції draw()
об’єкти за допомогою методу changeColor()
будуть отримувати різні значення кольору в залежності від того, чи фіксуються виявлення зіткнень, чи ні.
let rectangle1;
function setup() {
createCanvas(200, 200);
rectangle1 = new Rectangle();
}
function draw() {
rectangle1.intersects();
if (rectangle1.overlapping) {
background("rgb(7, 169, 163)"); // Light Sea Green
rectangle1.changeColor("rgb(233, 218, 84)"); // Minion Yellow
} else {
background("rgb(233, 218, 84)"); // Minion Yellow
rectangle1.changeColor("rgb(7, 169, 163)"); // Light Sea Green
}
rectangle1.paint();
}
class Rectangle {
constructor() {
this.x = random(width / 2);
this.y = random(height / 2);
this.d = random(40, 60);
this.c = 0;
this.overlapping = false;
}
paint() {
stroke(255);
strokeWeight(3);
fill(this.c);
rect(this.x, this.y, this.d * 2, this.d);
}
intersects() {
this.overlapping = false;
if (
mouseX > this.x &&
mouseX < this.x + this.d * 2 &&
mouseY > this.y &&
mouseY < this.y + this.d
) {
this.overlapping = true;
}
}
changeColor(currentColor) {
this.c = color(currentColor);
}
}
Переглядаємо Аналізуємо
5.4.4. Зіткнення між прямокутниками
Застосуємо ту саму логіку, як і з прямокутником та вказівником миші, для виявлення зіткнень між двома прямокутниками.
Умова виявлення зіткнення знову складатиметься із кількох нерівностей, в яких використовуватимуться властивості обох об’єктів:
\$this.x ≤ other.x + other.d * 2\$
\$this.y + this.d ≥ other.y\$
\$this.y ≤ other.y + other.d\$
Ключові слова this
і other
вказують на різні об’єкти, значення властивостей яких використовуються у нерівностях: this.x
і this.y
- координати верхнього лівого кута першого прямокутника, other.x
та other.y
- координати верхнього лівого кута другого прямокутника, this.d * 2
та other.d * 2
- значення ширина прямокутників, а this.y + this.d
та other.y + other.d
- значення висоти прямокутників відповідно.
Знову умова буде істинною, коли усі ці нерівності будуть істинними. Тому усі нерівності в коді застосунку необхідно об’єднати логічними операторами &&
.
let rectangle1;
let rectangle2;
function setup() {
createCanvas(200, 200);
rectangle1 = new Rectangle();
rectangle2 = new Rectangle();
}
function draw() {
rectangle1.intersects(rectangle2);
if (rectangle1.overlapping || rectangle2.overlapping) {
background("rgb(7, 169, 163)"); // Light Sea Green
rectangle1.changeColor("rgb(233, 218, 84)"); // Minion Yellow
} else {
background("rgb(233, 218, 84)"); // Minion Yellow
rectangle1.changeColor("rgb(7, 169, 163)"); // Light Sea Green
}
rectangle1.paint();
rectangle2.paint();
rectangle2.x = mouseX;
rectangle2.y = mouseY;
}
class Rectangle {
constructor() {
this.x = width / 3;
this.y = height / 3;
this.d = random(20, 40);
this.overlapping = false;
this.c = 0;
}
paint() {
stroke(255);
strokeWeight(3);
fill(this.c);
rect(this.x, this.y, this.d * 2, this.d);
}
intersects(other) {
this.overlapping = false;
other.overlapping = false;
if (
this.x + this.d * 2 >= other.x &&
this.x <= other.x + other.d * 2 &&
this.y + this.d >= other.y &&
this.y <= other.y + other.d
) {
this.overlapping = true;
other.overlapping = true;
}
}
changeColor(currentColor) {
this.c = color(currentColor);
}
}
Переглядаємо Аналізуємо
Розглянемо складніший випадок, коли один прямокутник на полотні є статичним, а інші рухаються і відбиваються, як від меж полотна, так і від меж статичного прямокутника.
Щоб реалізувати таку поведінку, необхідно визначити, де опиниться рухомий прямокутник, у разі, коли б перешкоди не існувало й порівняти координати точки розташування лівого верхнього кута прямокутника, що рухається, з координатами точки лівого верхнього кута фіксованого прямокутника.
Отже, окрім виявлення зіткнення між рухомими прямокутниками та межами полотна, виявлення зіткнення між статичним і рухомими прямокутниками можна описати за допомогою нерівностей, які враховують горизонтальну xVelocity
\$other.x + other.xVelocity < this.x + this.d * 2\$
\$other.y + other.d > this.y\$
\$other.y < this.y + this.d\$
і вертикальну yVelocity
\$other.x < this.x + this.d * 2\$
\$other.y + other.d + other.yVelocity > this.y\$
\$other.y + other.yVelocity < this.y + this.d\$
складові руху.
За допомогою слів this
(нерухомий прямокутник) і other
(рухомі прямокутники) відбувається доступ до значень властивостей прямокутників: x
і y
- координати верхнього лівого кута прямокутників, d * 2
- ширина прямокутника, y + d
- висота прямокутника, x + xVelocity
- горизонтальна координата наступної появи рухомого прямокутника, y + yVelocity
- вертикальна координата наступної появи рухомого прямокутника.
Для кожної складової руху усі нерівності повинні будуть істинними, тому в коді застосунку їх необхідно об’єднати логічними операторами &&
.
let rectangle1;
let rectangle2;
let rectangle3;
function setup() {
createCanvas(200, 200);
rectangle1 = new Rectangle();
rectangle2 = new Rectangle();
rectangle3 = new Rectangle();
}
function draw() {
background(120);
rectangle1.intersects(rectangle2);
rectangle1.intersects(rectangle3);
rectangle1.paint();
rectangle2.paint();
rectangle3.paint();
}
class Rectangle {
constructor() {
this.d = random(20, 30);
this.x = random(width - this.d * 2);
this.y = random(height - this.d);
this.c = color(random(255), random(255), random(255), random(200, 220));
this.xVelocity = random(0.5, 1);
this.yVelocity = random(0.5, 1);
}
paint() {
stroke(255);
strokeWeight(3);
fill(this.c);
rect(this.x, this.y, this.d * 2, this.d);
}
intersects(other) {
other.x += other.xVelocity;
other.y += other.yVelocity;
if (other.x < 0 || other.x > width - other.d * 2) {
other.xVelocity *= -1;
}
if (other.y < 0 || other.y > height - other.d) {
other.yVelocity *= -1;
}
if (
other.x + other.d * 2 + other.xVelocity > this.x &&
other.x + other.xVelocity < this.x + this.d * 2 &&
other.y + other.d > this.y &&
other.y < this.y + this.d
) {
other.xVelocity *= -1;
}
if (
other.x + other.d * 2 > this.x &&
other.x < this.x + this.d * 2 &&
other.y + other.d + other.yVelocity > this.y &&
other.y + other.yVelocity < this.y + this.d
) {
other.yVelocity *= -1;
}
}
}
У класі оголошений метод intersects()
, який враховує рух і відбивання прямокутників як від меж полотна, так і від меж статичного прямокутника.
Виклики метода rectangle1.intersects(rectangle2)
і rectangle1.intersects(rectangle3);
визначають об’єкт rectangle1
як статичний прямокутник, а rectangle2
і rectangle3
- рухомі прямокутники.
Переглядаємо Аналізуємо
У разі потреби можна змінити як об’єкт, для якого метод intersects()
викликається, так і аргумент метода, з яким він викликається.
У цьому розділі ми розглянули лише загальні підходи реалізації виявлення зіткнень між кількома об’єктами, що мають форму кола, прямокутника чи квадрата. Існують й інші способи розрахунку просторового розташування на основі R-дерева і дерева квадрантів . |
5.4.5. Ресурси
Корисні джерела
5.4.6. Контрольні запитання
Міркуємо Обговорюємо
-
Що таке «detect collision»?
-
Які труднощі можуть виникати при проєктуванні взаємодії програмних об’єктів?
-
Відшукайте задачі, які неможливо розв’язати за допомогою розглянутих підходів виявлення зіткнень.
5.4.7. Практичні завдання
Початковий
-
На основі коду застосунку, в якому відбувається взаємодія статичного і рухомих прямокутників, реалізувати аналогічну взаємодію для об’єктів квадратної форми. Орієнтовний зразок роботи застосунку представлено у демонстрації.
Середній
-
Створити застосунок, який демонструє взаємодію двох об’єктів квадратної форми. Орієнтовний взірець роботи застосунку представлено у демонстрації.
Високий
-
Створити застосунок, в якому відбувається взаємодія круглого об’єкта з прямокутним об’єктом. Результатом взаємодії може бути будь-яка поведінка, наприклад, як у демонстрації.
Екстремальний
-
Створити застосунок, в якому текстовий рядок розділяється на символи, які наносяться на квадратні об’єкти-наліпки різного розміру і кольору. Кожну наліпку з символом можна переміщувати на полотні вказівником миші та складати з символів початковий текстовий рядок. Орієнтовний взірець роботи застосунку представлено у демонстрації.
-
Створити застосунок, який демонструє адитивність колірної
RGB
-моделі, що описує спосіб синтезу кольору, за якою червоне, зелене та синє світло накладаються разом, змішуючись у різноманітні кольори. Процес отримання білого кольору будемо спрощено вважати таким, коли усі кольорові кола не накладаються разом, а утворюють ланцюжок зіткнень. Орієнтовний взірець роботи застосунку представлено у демонстрації.
5.5. Доцільність створення класів та об’єктів для розв’язання задач
Переважна більшість застосунків, які розглядаються у підручнику, побудовані за допомогою функцій - блоків інструкцій, які маніпулюють даними. Такий підхід до створення застосунків називається процедурним програмуванням.
Як відомо, написання коду з використанням концепції об’єктів, які можуть містити дані та методи, називають об’єктоорієнтованим програмуванням.
Поміркуймо над перевагами й недоліками використання об’єктоорієнтованого програмування для розв’язання задач в контексті використання бібліотеки p5.js
.
В об’єктоорієнтованому способі написання коду все обертається навколо об’єктів, які створюються на основі класів. Класи є, по суті, шаблонами, які наділяють створені об’єкти схожими чи однаковими властивостями та методами. Ця парадигма надає надзвичайно ефективний спосіб організації застосунків для моделювання об’єктів реального світу.
Втім, способів щось моделювати є надзвичайно багато. Тому не завжди інтуїтивно зрозуміло, яким повинен бути клас. Так написання класу може бути доволі складним процесом.
Очевидно, що для простих застосунків немає сенсу використовувати класи, хіба що з навчальною метою. У цьому разі можна скористатися звичайними глобальними змінними й весь функціонал застосунку описати у вбудованих функціях setup()
і draw()
. Якщо треба, у файл ескізу можна додати кілька власних функцій.
У разі, коли потрібно об’єднати певні властивості під одним ім’ям, корисно використовувати об’єкти, які є зручним способом організації інформації.
Наприклад, для зберігання властивостей кольору можна створити об’єкт colors
let colors = {
black: 0, // Black
white: 255, // White
"light gray": "#CDCED0" // Light Gray
}
і звертатись до його властивостей десь у коді за допомогою крапкової нотації.
Зростання обсягу коду застосунку може бути тим фактором, що дані, які зберігаються у змінних, і функції, які оперують цими даними, варто перемістити в об’єкт чи у клас - інкапсулювати.
Інкапсуляція, як один із ключових принципів об’єктоорієнтованого програмування, дозволяє класам групувати дані (властивості) та функції (методи), які пов’язані між собою, і приховувати їх внутрішню реалізацію від зовнішніх елементів коду. Це сприяє зниженню залежності між різними частинами застосунку і забезпечує більшу безпеку коду.
За такої умови класи можуть зберігати в окремих файлах і бути відповідальними за свою частину функціонала застосунку. Це сприяє покращенню читабельності, підтримки та розширення коду застосунку.
Ще однією причиною використання класу може бути можливість його використання для створення багатьох об’єктів, які успадковують методи.
Наприклад, у коді застосунку
let circle1, circle2;
function setup() {
createCanvas(200, 200);
circle1 = new Circle();
circle2 = new Circle();
}
function draw() {
background(220);
circle1.intersects(circle2);
if (circle1.overlapping) {
background(120);
}
circle1.paint();
circle2.changeColor("rgba(135, 35, 250, 0.8)"); // Electric Violet
circle2.paint();
circle1.move();
circle2.move();
}
class Circle {
constructor() {
this.c = "rgba(255, 214, 64, 0.8)"; // Mustard
this.r = 20;
this.x = random(this.r, width - this.r);
this.y = random(this.r, height - this.r);
this.xVelocity = random(1, 1.5);
this.yVelocity = random(1, 1.5);
this.overlapping = false;
}
paint() {
ellipseMode(RADIUS);
noStroke();
fill(this.c);
ellipse(this.x, this.y, this.r, this.r);
}
move() {
this.x += this.xVelocity;
this.y += this.yVelocity;
if (this.x < this.r || this.x > width - this.r) {
this.xVelocity *= -1;
}
if (this.y < this.r || this.y > height - this.r) {
this.yVelocity *= -1;
}
}
intersects(other) {
this.overlapping = false;
let d = dist(this.x, this.y, other.x, other.y);
if (d < this.r + other.r) {
this.overlapping = true;
}
}
changeColor(currentColor) {
this.c = color(currentColor);
}
}
використовується клас для створення двох рухомих об’єктів, що взаємодіють. Кожен з об’єктів отримує методи класу, тому окремо їх описувати не потрібно.
Класи можна легко тестувати окремо від інших частин застосунку. Це дозволяє виявляти та виправляти помилки в коді, забезпечуючи більшу надійність програмного продукту.
Загалом, використання класів та об’єктів допомагає зробити код більш структурованим, зрозумілим, масштабованим та легко підтримуваним. Проте, коли застосунок розширюється завдяки додаванню нових класів, успадкування і поліморфізм можуть спричинити плутанину.
Також, якщо потрібно додати нові методи або властивості до всіх наявних об’єктів класу, можуть виникнути проблеми з розширенням, особливо якщо екземпляри класу вже існують.
У підсумку, використання класів забезпечує ефективність в розв’язанні задач та допомагає побудувати добре організований, гнучкий і надійний код. При цьому пам’ятаючи, що неправильне використання класів може призвести до зростання складності проєкту.
Переглядаємо Аналізуємо
Вправа 51
Переписати код взаємодії рухомих об’єктів у процедурному стилі без використання принципів об’єктоорієнтованого програмування.
5.5.1. Ресурси
Корисні джерела
5.5.2. Контрольні запитання
Міркуємо Обговорюємо
-
За яких умов у створенні застосунку варто застосувати принципи об’єктоорієнтованого програмування?
-
Навести приклади задач, коли використання об’єктоорієнтованого програмування, на вашу думку, є недоцільним.
5.5.3. Практичні завдання
Початковий
-
Наведений нижче код застосунку, який обчислює площу прямокутника за відомими значеннями його сторін, використовує процедурний стиль програмування. Перепишіть код у стилі об’єктоорієнтованого програмування. Напишіть клас
Rectangle
, який має властивостіw
(ширина прямокутника),h
(висота прямокутника),c
- колір заливки. Клас має містити також два методи:paint()
- для малювання прямокутника іcalculateArea()
- для обчислення площі прямокутника як добуток його ширини й висоти. Орієнтовний зразок роботи застосунку представлено на малюнку.

let w = 120;
let h = 60;
function setup() {
createCanvas(200, 200);
rectMode(CENTER);
textAlign(CENTER, CENTER);
textSize(40);
console.log(calculateArea(w, h));
}
function draw() {
background(245);
fill(61, 52, 139); // Tekhelet
rect(width / 2, height / 2, w, h);
fill(230, 175, 46); // Xanthous
text(calculateArea(w, h), width / 2, height / 2);
}
function calculateArea(w, h) {
return w * h;
}
Середній
-
Створити застосунок, в якому на полотні рухається об’єкт, наприклад, прямокутник, а при дотику до меж полотна, об’єкт трансформується в інший об’єкт, наприклад, в еліпс. Трансформації відбуваються циклічно. Орієнтовний зразок роботи застосунку представлено у демонстрації.
Високий
-
Створити застосунок, в якому у будь-якій точці полотна створюється джерело частинок, що розлітаються у різні боки із прискоренням. Об’єкти, які умовно назвемо чорними дірами, також рухаються, перетинаючи полотно, і поглинають частинки, які трапилися на їхньому шляху. Орієнтовний зразок роботи застосунку представлено у демонстрації.
Екстремальний
-
Створити власну версію гри
Snake Game
(Змійка ). Для гри можна визначити свої правила. Наприклад, коли змія торкається межі полотна, то вона з’являється біля протилежної межі полотна. Якщо змія торкається перешкоди, то її хвіст виростає, а лічильник, розташований на її голові, і швидкість її руху збільшуються. Гравець керує клавішами-стрілками або клавішамиWASD
і програє у разі, коли змія наткнеться на саму себе. Орієнтовний взірець роботи застосунку представлено у демонстрації.
6. Мультимедіа
В цьому розділі ви ознайомитесь з поняттями масиву, трансформації та їх застосування при роботі з мультимедійними даними. |
6.1. Поняття, реалізація та застосування масивів
Створимо застосунок, у якому відбуватиметься горизонтальний рух двох кіл від лівої межі до правої межі полотна.
Щоб імітувати рух кола горизонтально, необхідно збільшувати значення його x
-координати. Оскільки є два кола, опишемо дві глобальні змінні, значення яких будуть змінюватися у функції draw()
.
let x1 = -5;
let x2 = 5;
function setup() {
createCanvas(200, 200);
noStroke();
}
function draw() {
background(75);
fill(255, 200); // White
x1 += 0.5;
x2 += 0.5;
ellipse(x1, 20, 20, 20);
ellipse(x2, 40, 20, 20);
}
Змінимо код, щоб на полотні з’явилась більша кількість фігур.
let x1 = -5;
let x2 = 5;
let x3 = -20;
let x4 = 20;
let x5 = 45;
function setup() {
createCanvas(200, 200);
noStroke();
}
function draw() {
background(75);
fill(255, 200); // White
x1 += 0.5;
x2 += 0.5;
x3 += 0.5;
x4 += 0.5;
x5 += 0.5;
ellipse(x1, 30, 20, 20);
ellipse(x2, 60, 20, 20);
ellipse(x3, 90, 20, 20);
ellipse(x4, 120, 20, 20);
ellipse(x5, 150, 20, 20);
}
Припустимо, потрібно намалювати 100
таких фігур. Якщо слідувати нашому підходу, то необхідно було б зробити оголошення 100
змінних і присвоєння оновлених значень кожній окремо, що є не найкращим способом. У цьому разі слід використати масив.
6.1.1. Масиви в JavaScript
Масив - тип даних в JavaScript
, який є впорядкованою колекцією значень, що має ім’я. Кожне значення масиву називається елементом масиву. Кожний елемент масиву має індекс - позицію в масиві, яка позначається цілим числом. Відлік індексів починається від нуля.
В JavaScript
елемент масиву може належати будь-якому типу даних, а різні елементи масиву можуть мати різні типи даних. Можливість зберігати в масивах JavaScript
елементи будь-якого типу дозволяє створювати складні структури даних, на зразок масиву об’єктів чи масиву масивів.
Проілюструємо концептуальну структуру масиву на прикладі масиву об’єктів.

Найпростіший спосіб створити масив - використати літерал масиву - квадратні дужки []
. Типовий алгоритм створення масиву можна описати так:
-
Використати зарезервоване слово
let
(абоconst
). -
Вказати назву масиву.
-
Записати значення елементів масиву, розділених комами, у квадратних дужках
[]
.
Приклади створення масивів:
let r = 155;
let lavenderGray = "#C3BDD0";
let empty = []; (1)
let coordsX = [-5, 5, -20, 20, 45]; (2)
let misc = [2.5, r, true, lavenderGray, [r + 100, r - 100], {x: 3, y: 7}, coordsX] (3)
1 | Створення порожнього масиву empty без елементів. |
2 | Створення масиву coordsX з п’ятьма елементами, значеннями яких є цілі числа. |
3 | Створення масиву misc із сімома елементами, значеннями яких є різні типи даних, зокрема об’єкти та інші масиви. |
Доступ до елементів масиву здійснюється за допомогою операції []
. У цьому разі спочатку вказується покликання на масив (ім’я масиву), а всередині квадратних дужок записується вираз, результатом обчислення якого має бути цілочисельне значення (індекс елемента масиву), яке позначає місце елемента у масиві.
Наприклад:
let coordsX = [-5, 5, -20, 20, 45];
console.log(coordsX[0]); // -5
console.log(coordsX[3]); // 20
console.log(coordsX[5]); // undefined
console.log(coordsX[100]); // undefined
Як видно з результату, якщо звертатися до елементів за індексом, який дорівнює або більший за кількість елементів у масиві, отримуємо значення undefined
.
Цікавимось
Будь-який масив в JavaScript
має властивість length
, яка відрізняє масиви від звичайних об’єктів JavaScript
. Властивість length
вказує на кількість елементів у масиві, інакше кажучи, містить інформацію про довжину масиву.
Отримаємо значення length
для масиву coordsX
:
let coordsX = [-5, 5, -20, 20, 45];
console.log(coordsX[4]); // 45
console.log(coordsX.length); // 5
Значення властивості length на одиницю більше найбільшого індексу в масиві.
|
Квадратні дужки можна використовувати як для отримання (читання), так і для зміни (запису) значень елементів масиву.
let coordsX = [-5, 5, -20, 20, 45];
coordsX[0] = 10; // записати елемент 0 (1)
coordsX[5] = 75; // записати елемент 5 (2)
coordsX[99] = 0; // записати елемент 99 (3)
console.log(coordsX[0]); // прочитати елемент 0 (4)
console.log(coordsX[4]); // прочитати елемент 4 (5)
console.log(coordsX[5]); // прочитати елемент 5 (6)
Проаналізуємо наші дії.
1 | Змінюємо у масиві coordsX значення елемента з індексом 0 . Було значення -5 , стало - 10 . |
2 | Змінюємо у масиві coordsX значення елемента з індексом 5 . Елемент з таким індексом у масиві відсутній. Якби ми звернулися до цього елемента, то отримали б значення undefined . Для цього елемента встановлюємо значення 75 . |
3 | Змінюємо у масиві coordsX значення елемента з індексом 99 . Елемент з таким індексом у масиві відсутній. Якби ми звернулися до цього елемента, то отримали б значення undefined . Для цього елемента встановлюємо значення 0 . |
4 | Отримуємо значення елемента з індексом 0 . Це число 10 , яке ми встановили у першому пункті. |
5 | Отримуємо значення елемента з індексом 4 . Це число 45 . Воно залишилося з моменту ініціалізації масиву. |
6 | Отримуємо значення елемента з індексом 5 . Це число 75 , яке ми встановили у другому пункті. |
Зараз значення властивості length
для масиву coordsX
дорівнює 100
, що є набагато більше, ніж елементів у масиві. Перевіримо це в консолі вебпереглядача, надрукувавши значення length
і увесь масив coordsX
загалом:
console.log(coordsX.length); // 100
console.log(coordsX); // (100) [10, 5, -20, 20, 45, 75, пусто × 93, 0]
Щоб краще зрозуміти отримані результати, запишемо їх інакше:
(100) [10, 5, -20, 20, 45, 75, undefined, undefined, ..., undefined, undefined, 0]
Як бачимо, властивість length
на одиницю більше найбільшого індексу в масиві, а на місці пропущених індексів, розташовані значення undefined
.
У цьому разі масив coordsX
називається розрідженим.
Розріджений масив - це масив, елементи якого не мають неперервних індексів, що починаються з нуля. |
Зазвичай властивість length
вказує на кількість елементів у масиві, але коли масив є розрідженим, значення length
може бути більшим за кількість елементів у масиві. Властивість length
завжди перевищує індекс будь-якого елемента у масиві.
Наприклад:
let a = []; // масив без елементів, властивість length = 0
a[1000] = 0; // масив отримав один елемент, властивість length = 1001
Якщо встановити для властивості length
цілочисельне значення, яке менше поточного значення length
, тоді будь-які елементи, чиї індекси перевищують або дорівнюють цьому значенню, будуть видалені з масиву.
Наприклад:
let coordsX = [-5, 5, -20, 20, 45]; // спочатку у масиві 5 елементів
coordsX.length = 3; // тепер у масиві три елементи [-5, 5, -20]
coordsX.length = 0; // видалення усіх елементів масиву, масив став порожнім []
coordsX.length = 1000; // довжина масиву 1000, але елементи у масиві відсутні
Масиви в JavaScript є динамічними, тобто в разі потреби можуть збільшуватися чи зменшуватися.
|
Окрім присвоєння значень за новими індексами, існує й інший спосіб додати елементи у масив - за допомогою методу push()
, який додає один або більше значень в кінець масиву.
Наприклад:
let colors = []; (1)
colors.push("red"); (2)
colors.push("green", "blue"); (3)
1 | Розпочинаємо з порожнього масиву colors . |
2 | Додати значення "red" в кінець масиву colors . Тепер масив має вигляд: ["red"] . |
3 | Додати ще два значення "green" і "blue" в кінець масиву colors . Тепер масив має вигляд: ["red", "green", "blue"] . |
Як і при видаленні властивостей об’єктів, для видалення елементів масиву використовують оператор delete
.
Наприклад:
let colors = ["red", "green", "blue"];
delete colors[2]; // тепер у масиві colors з індексом 2 міститься undefined
console.log(colors.length); // 3, оскільки оператор delete не впливає на довжину масиву
Використання оператора delete
з елементом масиву не змінює значення властивості length
і не зсовує елементи з більшими індексами, щоб заповнити прогалину, яка залишається після видалення. Після видалення елемента з масиву за допомогою delete
масив стає розрідженим.
Цікавимось Додатково
Дізнайтеся про функції p5.js для роботи з масивами із розділу Data офіційної документації бібліотеки.
|
Повернемось до нашого прикладу з колами, що рухаються горизонтально, і використаємо масив для зберігання значень x
-координат кожного із рухомих кіл.
Усередині функції setup()
за допомогою циклу for
заповнимо масив circles
випадковими значеннями з рухомою крапкою, що позначатимуть x
-координати кіл, а потім ці числа, взяті із масиву, використаємо за допомогою for
у функції draw()
для встановлення значень координат кіл та імітації їхнього руху.
let circles = []; (1)
let k = 100; (2)
function setup() {
createCanvas(200, 200);
noStroke();
fill(255, 200); // White
for (let i = 0; i < k; i++) {
circles.push(random(-20, 200)); (3)
}
}
function draw() {
background(75);
for (let i = 0; i < circles.length; i++) { (4)
circles[i] += 0.5; (5)
let y = i * 2; (6)
ellipse(circles[i], y, 20, 20); (7)
}
}
Розглянемо наведений код детальніше.
1 | Оголошуємо порожній масив з ім’ям circles за допомогою квадратних дужок [] . |
2 | Оголошуємо змінну k - кількість елементів масиву. |
3 | Заповнення масиву circles випадковими числами з рухомою крапкою з діапазону [-20, 200) (включно із -20 , але не включаючи 200 ) за допомогою методу push() . |
4 | Проходження по масиву circles з метою отримання значень його елементів. |
5 | Збільшення i -го елемента масиву circles на 0.5 (збільшення x -координати кола для імітації його горизонтального руху). Запис colors[i] - доступ до i -того елемента масиву colors . |
6 | Оголошення змінної y і присвоєння їй значення y -координати кола. |
7 | Використання функції ellipse() для малювання кола за x -координатою, яка береться з масиву, та y -координатою, що обчислюється в тілі циклу. |
Переглядаємо Аналізуємо
Для циклічного проходу по масиву можна також використовувати цикл for..of
. Створимо масив colors
, який буде містити значення кольорів, і візуалізуємо роботу циклу for..of
.
let colors = [
"rgba(255, 194, 81, 0.8)", // Maximum Yellow Red
"rgba(82, 255, 200, 0.8)", // Sea Green Crayola
"rgba(245, 127, 76, 0.8)", // Mandarin
"rgba(135, 129, 11, 0.8)", // Olive
"rgba(135, 11, 87, 0.8)" // Pansy Purple
];
function setup() {
createCanvas(200, 200);
noStroke();
noLoop(); // зупиняє безперервне виконання коду у draw()
}
function draw() {
background(75);
for (let c of colors) {
fill(c);
ellipse(random(width), random(height), 20, 20);
}
}
У циклі for..of
із масиву береться черговий елемент, значення якого використовується як аргумент функції fill()
для кольору зафарбовування кола. Функція noLoop()
зупиняє безперервне виконання коду у draw()
, тому утворюється статичний малюнок із п’яти кольорових кіл на полотні.
Переглядаємо Аналізуємо
Щоб використовувати цикл for..of
і знати індекс кожного елемента масиву, необхідно використовувати метод entries()
масиву разом з деструктурованим присвоєнням. Застосуємо такий підхід для масиву статичних кіл.
let colors = [
"rgba(255, 194, 81, 0.8)", // Maximum Yellow Red
"rgba(82, 255, 200, 0.8)", // Sea Green Crayola
"rgba(245, 127, 76, 0.8)", // Mandarin
"rgba(135, 129, 11, 0.8)", // Olive
"rgba(135, 11, 87, 0.8)", // Pansy Purple
];
function setup() {
createCanvas(200, 200);
noStroke();
noLoop(); // зупиняє безперервне виконання коду у draw()
}
function draw() {
background(75);
for (let [index, item] of colors.entries()) { (1)
let x = random(width); (2)
let y = random(height);
fill(255); // White
textSize(10);
textAlign(CENTER, CENTER);
text(index, x, y); (3)
fill(item); (4)
ellipse(x, y, 20, 20);
}
}
1 | Застосовуємо метод entries() до масиву colors , щоб отримати пари значень [index, item] у формі масиву. На кожній ітерації циклу виконується деструктуроване присвоєння: елемент index отримує значення індексу елемента масиву colors , а елемент item - значення елемента масиву colors . Імена для index і item можна обрати на свій вибір. |
2 | Оголошення змінних для зберігання випадкових значень координат кіл на полотні. |
3 | Використання index , що містить значення індексу елемента масиву colors , для відображення на полотні числа за допомогою функції text() . Для налаштування числового напису на полотні використовуються функції textSize() і textAlign() . |
4 | Використання item , що містить значення елемента масиву colors , як аргументу функції fill() для зафарбовування кола. |
Переглядаємо Аналізуємо
У підсумку, об’єднаємо два застосунки в один.
let colors = [
"rgba(255, 194, 81, 0.8)", // Maximum Yellow Red
"rgba(82, 255, 200, 0.8)", // Sea Green Crayola
"rgba(245, 127, 76, 0.8)", // Mandarin
"rgba(135, 129, 11, 0.8)", // Olive
"rgba(135, 11, 87, 0.8)", // Pansy Purple
];
let circles = [];
let k = 100;
function setup() {
createCanvas(200, 200);
noStroke();
for (let i = 0; i < k; i++) {
circles.push(random(-20, 200));
}
}
function draw() {
background(75);
for (let [index, item] of circles.entries()) {
circles[index] += 0.5;
let y = index * 2;
let c = index % colors.length;
fill(255); // White
textSize(10);
textAlign(CENTER, CENTER);
text(index, item, y);
fill(colors[c]);
ellipse(item, y, 20, 20);
}
}
Оскільки у масиві circles
є сто елементів (від 0
до 99
включно), а у масиві colors
- всього п’ять (від 0
до 4
включно), то у застосунку за допомогою виразу index % colors.length
реалізовано повторне використання значення кольору заливки кіл.
У виразі index % colors.length
використовується оператор %
, який позначає операцію остача від ділення. Механізм повторного використання кольору представлений у таблиці.
Значення index |
Операція index % colors.length |
Результат index % colors.length |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Вправа 52
Використати масив, що зберігає значення розмірів кіл, для побудови фігур.
6.1.2. Масив масивів
До цього часу ми мали справу з одномірними масивами, тобто масивами, в яких для позначення місця елемента у масиві використовувався один індекс. Існують і багатовимірні масиви. Наприклад, двовимірний масив можна уявити як таблицю, що складається з рядків і стовпців, інакше кажучи, як масив масивів.
Кількість індексів визначає розмірність масиву. |
Проілюструємо концептуальну структуру двовимірного масиву на прикладі двовимірного масиву об’єктів.

Для доступу до значень у двовимірному масиві використовується подвійна операція []
. Спочатку необхідно записати покликання на масив (ім’я масиву), а потім всередині перших квадратних дужок записати індекс рядка, в якому перебуває елемент масиву, а у других квадратних дужках - індекс стовпця.
Щоб отримати значення конкретного об’єкта у двовимірному масиві, наприклад як на ілюстрації, необхідно записати shapes[2][1]
.
Використаємо поняття двовимірного масиву для створення застосунку, який застилає полотно пронумерованою квадратною піксельною плиткою.
let rows = 10; (1)
let columns = 10; (2)
let p = 0; (3)
let colors = new Array(rows); (4)
function setup() {
createCanvas(200, 200);
noStroke();
for (let i = 0; i < colors.length; i++) {
colors[i] = new Array(columns); (5)
for (let j = 0; j < colors[i].length; j++) {
colors[i][j] = random(0, 230); (6)
}
}
noLoop();
}
function draw() {
background(220);
for (let i = 0; i < colors.length; i++) {
let y = i * rows * 2; (7)
for (let j = 0; j < colors[i].length; j++) {
let x = j * columns * 2; (7)
fill(colors[i][j]); (8)
rect(x, y, rows * 2, columns * 2);
fill(255); // White
textSize(10);
textAlign(CENTER, CENTER);
text(p, x + rows, y + columns); (9)
p += 1; (10)
}
}
}
Пригадаємо аналогію двовимірного масиву з таблицею і розглянемо код застосунку детальніше.
1 | Оголошуємо глобальну змінну rows , яка визначатиме кількість рядків у двовимірному масиві. |
2 | Оголошуємо глобальну змінну columns , яка визначатиме кількість стовпців у двовимірному масиві. |
3 | Оголошуємо глобальну змінну p , яка буде позначати номер пікселя. Черговий піксель на полотні буде мати значення на одиницю більше p . |
4 | Створення одномірного масиву colors за допомогою функції-конструктора Array() . Це спосіб створення масиву із вказаною довжиною rows і значеннями undefined . Створення масиву за допомогою Array() можна використовувати, коли наперед відомо скільки елементів буде містити масив. |
5 | У зовнішньому циклі for на кожній ітерації у масив colors додаємо черговий елемент - новий одномірний масив довжиною columns . Інакше кажучи, наповнюємо масив colors елементами, які є масивами. |
6 | У внутрішньому циклі for на кожній ітерації звертаємось до елементів двовимірного масиву, який був утворений в пункті 5, за допомогою запису colors[i][j] , де i та j - це індекси рядків та стовпців двовимірного масиву colors відповідно. Для конкретного елемента двовимірного масиву присвоюємо випадкове значення кольору. |
7 | Заповнивши двовимірний масив значеннями, проходимо знову по масиву за допомогою двох циклів for і на кожній ітерації обчислюємо координати x і y верхнього лівого кута чергового прямокутника, який буде відігравати роль одного пікселя. |
8 | Встановлюємо колір пікселя, взявши значення кольору з масиву, і малюємо піксель на полотні у координатах, обчислених у пункті 7. |
9 | Друкуємо порядковий номер пікселя. |
10 | Збільшуємо значення номера пікселя на одиницю. |
Переглядаємо Аналізуємо
Якщо надрукувати значення colors
в консолі вебпереглядача, то можна проаналізувати значення, що зберігаються у двовимірному масиві.
(10) [Array(10), Array(10), Array(10), Array(10), Array(10), Array(10), Array(10), Array(10), Array(10), Array(10)]
0: Array(10)
0: 119.63071363090914
1: 103.34870238078383
2: 196.1683547436366
3: 149.147481174876
4: 207.27953901302422
5: 164.28415434344262
6: 55.499118390416854
7: 124.06536828614632
8: 108.8618965094095
9: 39.526639550447705
1: Array(10)
2: Array(10)
3: Array(10)
4: Array(10)
5: Array(10)
6: Array(10)
7: Array(10)
8: Array(10)
9: Array(10)
Вправа 53
Змінити код застосунку для створення кольорової піксельної плитки.
6.1.3. Масив об’єктів
Як вже було зазначено, масиви можна використовувати для зберігання об’єктів.
Напишемо код застосунку, в якому у масив буде додаватися по одному об’єкту в разі натискання будь-якої кнопки миші, і видалятися об’єкти з масиву будуть також по одному, але коли буде натискатися будь-яка клавіша на клавіатурі. Звичайно цей процес буде візуалізований на полотні застосунку.
Отже, код застосунку може бути таким:
let circles = []; (1)
function setup() {
createCanvas(200, 200);
noStroke();
}
function draw() {
background(220);
for (let i = 0; i < circles.length; i++) {
circles[i].paint(); (4)
}
}
function mousePressed() {
circles.push(new Circle(mouseX, mouseY)); (3)
}
function keyPressed() {
circles.pop(); (5)
}
class Circle { (2)
constructor(x, y) {
this.x = x;
this.y = y;
this.r = random(10, 30);
this.c = color(random(255), random(255), random(255), random(200, 255));
}
paint() {
ellipseMode(RADIUS);
fill(this.c);
ellipse(this.x, this.y, this.r * 2, this.r * 2);
}
}
1 | Оголошуємо порожній масив circles . |
2 | Описуємо клас Circle для створення об’єктів кола. Усі об’єкти кола будуть мати такі властивості: координати центру кола, які будуть надаватися конструктору класу при створенні кола, і випадкові значення радіуса кола та кольору заливки, визначені всередині конструктора класу. Клас міститиме метод paint() для малювання об’єктів на полотні. |
3 | За допомогою метода push() додаємо в кінець масиву circles черговий об’єкт кола, який створюється за допомогою виклику new Circle(mouseX, mouseY) , де mouseX і mouseY - це значення координат вказівника миші, які стають координатами центру побудови щоразу нового об’єкта кола. Додавання об’єкта кола як елемента у масив circles відстежується і виконується за допомогою функції mousePressed() , яка викликається один раз після кожного натискання будь-якої кнопки миші на полотні. |
4 | У функції draw() у циклі for для кожного елемента масиву circles застосовується метод paint() , який отримує кожний об’єкт кола при його створенні у пункті 3. |
5 | За допомогою метода pop() з кінця масиву видаляється по одному елементу. Це відбувається тоді, коли спрацьовує функція keyPressed() , яка викликається один раз під час кожного натискання будь-якої клавіші на клавіатурі. |
Переглядаємо Аналізуємо
При натисканні будь-якої клавіші миші відбувається створення об’єктів кіл й додавання їх у кінець масиву. Щоразове натискання будь-якої клавіші на клавіатурі видаляє з кінця масиву елемент, значенням якого є конкретне коло. Прохід по масиву circles
у функції draw()
малює усі кола, об’єкти яких зберігаються в масиві на цей момент.
Розглянемо приклад взаємодії об’єктів із застосуванням масиву. У цьому разі застосунок буде імітувати безладний рух об’єктів. Коли між об’єктами відбуватиметься зіткнення, інакше кажучи, вони будуть взаємодіяти, об’єкти будуть ставати прозорими, залишаючи лише власні контури.
Отже, проаналізуємо код застосунку:
let k = 10;
let circles = new Array(k); (1)
function setup() {
createCanvas(200, 200);
for (let i = 0; i < circles.length; i++) {
circles[i] = new Circle(); (3)
}
}
function draw() {
background(120);
for (let i = 0; i < circles.length; i++) {
circles[i].paint();
circles[i].movement();
for (let j = 0; j < circles.length; j++) {
if (i !== j && circles[i].intersects(circles[j])) { (4)
circles[i].changeColor();
circles[j].changeColor();
}
}
}
}
class Circle { (2)
constructor() {
this.r = random(3, 10);
this.x = random(this.r, width - this.r);
this.y = random(this.r, height - this.r);
this.a = 200;
this.c = color(random(255), random(255), random(255), this.a);
this.xSpeed = random(0.5);
this.ySpeed = random(0.5);
}
paint() {
stroke("#AFEAF5"); // Blizzard Blue
ellipseMode(RADIUS);
fill(this.c);
ellipse(this.x, this.y, this.r * 2, this.r * 2);
}
movement() {
this.x = this.x + this.xSpeed;
this.y = this.y + this.ySpeed;
if (this.x - this.r * 2 < 0 || this.x > width - this.r * 2) {
this.xSpeed = this.xSpeed * -1;
}
if (this.y - this.r * 2 < 0 || this.y > height - this.r * 2) {
this.ySpeed = this.ySpeed * -1;
}
}
intersects(other) {
let d = dist(this.x, this.y, other.x, other.y);
if (d < this.r * 2 + other.r * 2) {
return true;
}
return false;
}
changeColor() {
this.a = 0;
this.c = color(120, this.a);
}
}
1 | Створюємо масив circles з k елементами. На цей момент усі значення елементів масиву - undefined . |
2 | Описуємо клас Circle , який визначатиме властивості створених на основі класу об’єктів. Об’єктами будуть кола різного розміру і кольору. Оголошуємо кілька методів: paint() - малювання об’єктів полотні, movement() - рух об’єктів на полотні, intersects() - виявлення зіткнень об’єктів, changeColor - встановлення прозорості об’єктів. |
3 | Наповнюємо масив circles об’єктами кіл. |
4 | У функції draw() використовуємо два цикли for . Перший використовується для проходження по масиву з метою застосування для кожного елемента методів paint() і movement() . Другий - також для проходження по елементах масиву і перевірки умови на виявлення зіткнень між колами за допомогою метода intersects() . Частина i !== j умови вказівки розгалуження if виключає випадок зіткнення об’єкта із самим собою. У разі зіткнень на об’єктах, що взаємодіють, викликається метод changeColor() , який встановлює їхню прозорість. |
Переглядаємо Аналізуємо
Вправа 54
Використати двовимірний масив для зберігання об’єктів.
6.1.4. Ресурси
Корисні джерела
6.1.5. Контрольні запитання
Міркуємо Обговорюємо
-
Що таке «масив»? Навести приклади даних, які можна зберігати у масиві.
-
Які є способи створення масиву в
JavaScript
? -
Назвати особливості одномірних і двовимірних масивів.
-
Що таке «розріджений масив» в
JavaScript
? -
Як використати в коді застосунку певне значення елемента масиву?
6.1.6. Практичні завдання
Початковий
-
У коді застосунку не вистачає фрагмента, який описує масив
colors
зі значеннями кольорів. Виправте це, щоб застосунок запрацював.
function setup() {
createCanvas(200, 200);
noStroke();
noLoop();
}
function draw() {
background(220);
for (let i = 0; i < colors.length; i++) {
fill(colors[i]);
rect(random(200), random(200), random(20, 60), random(10, 30));
}
}
-
Використати проходження по масиву для покращення структури коду застосунку.
let d = [15, 75, 25, 45, 20];
function setup() {
createCanvas(200, 200);
noStroke();
noLoop();
}
function draw() {
background(220);
fill(random(255), random(255), random(255), random(200, 230));
ellipse(random(200), random(200), d[0], d[0]);
fill(random(255), random(255), random(255), random(200, 230));
ellipse(random(200), random(200), d[1], d[1]);
fill(random(255), random(255), random(255), random(200, 230));
ellipse(random(200), random(200), d[2], d[2]);
fill(random(255), random(255), random(255), random(200, 230));
ellipse(random(200), random(200), d[3], d[3]);
fill(random(255), random(255), random(255), random(200, 230));
ellipse(random(200), random(200), d[4], d[4]);
}
Середній
-
Використовуючи масив, в якому елементами є значення горизонтальних координат, створити застосунок, що імітує рух хмаринок на небі. Для відображення зображення хмаринки (☁️) використати функцію text() . Орієнтовний зразок роботи застосунку представлено в демонстрації.
-
Створити застосунок, що генерує випадкову мапу лабіринту. Один з екземплярів мапи представлений на малюнку.

Високий
-
Створити візуалізацію двовимірного масиву. На малюнку зображений масив з 15 елементів, кожен з яких є масивом з випадковою кількістю елементів. Елементи масиву позначені квадратами, а їх значення - колами.

-
Створити масив об’єктів, які рухаються на полотні та взаємодіють між собою. Результатом взаємодії об’єктів може бути, наприклад, зміна їхнього кольору як представлено в демонстрації.
-
Створити застосунок, в якому при натисканні на будь-якому об’єкті на полотні вказівником миші об’єкт зникає з полотна. Орієнтовний взірець роботи застосунку представлено в демонстрації.
-
Створити застосунок, в якому при натисканні на будь-якому об’єкті на полотні вказівником миші з’являється ще кілька об’єктів. Орієнтовний зразок виконання застосунку представлено в демонстрації.
Екстремальний
-
На основі коду застосунку для генерації мапи, створити застосунок, який додає на мапу гравця, що з’являється у випадковому вільному від перешкод місці мапи та може рухатися в лабіринті за допомогою клавіш-стрілок клавіатури. Орієнтовний взірець роботи застосунку представлено в демонстрації.
-
Створити застосунок, в якому об’єкти кіл різного розміру якомога щільно розташовуються на полотні, не перекриваючи один одного, як представлено на малюнку.

6.2. Текстові рядки як масиви символів
В JavaScript
будь-які текстові дані є рядками.
Текстові рядки є масивами символів Unicode в кодуванні UTF-16
. Текстові рядки неможливо змінити, а доступ до символів рядка відбувається за допомогою квадратних дужок []
.
Наприклад:
let s = "JavaScript";
console.log(s[0]); // J
console.log(s[6]); // r
console.log(s[1000]); // undefined
В JavaScript текстові рядки розміщуються в лапках різних типів. Одинарні та подвійні лапки працюють, по суті, однаково, а якщо використовувати зворотні лапки, то в такий рядок можна вставляти довільні вирази, загорнувши їх в конструкцію ${вираз} .
|
В JavaScript
за допомогою методу join()
, можна перетворити усі елементи масиву в рядки та виконати їх об’єднання (конкатенацію) в єдиний рядок. Метод join()
може отримувати як аргумент рядок, що буде розділювачем елементів масиву в кінцевому рядку. Якщо рядок розділювача не вказувати, то як розділювач буде використовуватися кома.
Наприклад:
let words = ["Javascript", "is", "my", "favorite"];
console.log(words.join()); // Javascript,is,my,favorite
console.log(words.join(", ")); // Javascript, is, my, favorite
Метод split()
в JavaScript
працює навпаки - він створює масив, розділяючи рядок на окремі частини, які стають елементами масиву.
Наприклад:
let s = "Javascript is my favorite";
console.log(s.split()); // ["Javascript is my favorite"]
console.log(s.split(" ")); // ["Javascript", "is", "my", "favorite"]
Бібліотека p5.js
має аналоги розглянутих та багатьох інших методів JavaScript
для роботи з текстовими рядками. Використаємо низку текстових функцій з бібліотеки p5.js
і методів JavaScript
для роботи з текстовими даними у коді застосунку, який розміщує статичний текст на полотні.
let r = "JavaScript і p5.js";
function setup() {
createCanvas(200, 200);
}
function draw() {
background(240);
textSize(20); (1)
textFont("Helvetica", 20); (2)
textAlign(CENTER); (3)
fill("#ED225D"); // Paradise Pink (4)
text(r, width / 2, 40); (5)
text(r.length, width / 2, 80); (6)
text(r.toUpperCase(), width / 2, 120); (7)
text(r.toLowerCase(), width / 2, 160); (8)
}
Розглянемо призначення використаних функцій.
1 | Функція textSize() встановлює розмір шрифту у пікселях. |
2 | Функція textFont() встановлює назву і розмір шрифту. |
3 | Функція textAlign() встановлює вирівнювання намальованого тексту і може отримувати два аргументи: вирівнювання по горизонталі (LEFT , CENTER і RIGHT ) стосується значення x функції text() і вирівнювання по вертикалі (TOP , BOTTOM , CENTER і BASELINE ) стосується значення y функції text() . |
4 | Функція fill() у цьому разі встановлює колір тексту. |
5 | Функція text() малює зазначений у першому аргументі текстовий рядок r на полотні у координатах x та y , зазначених у наступних двох аргументах відповідно. |
6 | Обчислення довжини текстового рядка r . |
7 | Переведення текстового рядка r у верхній регістр. |
8 | Переведення текстового рядка r у нижній регістр. |
Функція textFont()
встановлює поточний шрифт, який буде застосований у функції text()
. Якщо ж функцію textFont()
викликати без аргументів, вона повертає поточний шрифт, якщо він був попередньо встановлений, інакше функція повертає назву шрифту за стандартним налаштуванням, вбудованого у бібліотеку p5.js
.
За потреби у застосунок можна додати власні шрифти.
У цьому разі необхідно викликати функцію loadFont() всередині функції preload() . Це гарантуватиме, що операція завантаження шрифту буде завершена до викликів setup()
і draw()
.
Наприклад:
let myFont;
function preload() {
myFont = loadFont("fontName.otf"); // .otf, .ttf
}
function setup() {
textFont(myFont);
...
}
function draw() {
...
}
Дізнайтеся про функції p5.js для роботи з текстом з розділів Typography і Data офіційної документації бібліотеки.
|
Напишемо код застосунку, який створює ефектний горизонтальний текстовий заголовок. Оскільки текстовий рядок є масивом символів, використаємо циклічний прохід по рядку для доступу до кожного символу рядка.
let s = "Harry Potter";
let x = 0;
function setup() {
createCanvas(200, 200);
noLoop();
}
function draw() {
background(240);
fill(random(255), random(255), random(255));
textAlign(CENTER, CENTER);
for (let i = 0; i < s.length; i++) {
x += width / s.length - s[i].length; (1)
textSize(random(15, 65)); (2)
text(s[i], x, height / 2 + random(-1, 1) * 20); (3)
}
}
1 | Проходимо у циклі посимвольно до кінця текстового рядка s і збільшуємо координату x окремого символу, яка використовується для малювання його на полотні. |
2 | Встановлюємо випадкове значення для розміру шрифту в пікселях. |
3 | Малюємо окремий символ s[i] на полотні у зазначених координатах. |
У результаті виконання застосунку отримуємо текстовий рядок з випадковим розміщенням літер на полотні.

Вправа 55
Створити застосунок, який малює на полотні вертикальний текстовий заголовок.
6.2.1. Ресурси
Корисні джерела
6.2.2. Контрольні запитання
Міркуємо Обговорюємо
-
Що називають «рядками» в
JavaScript
? -
Які функції для роботи з текстом входять до складу бібліотеки
p5.js
? -
Описати алгоритм додавання власного шрифту до ескізу.
6.2.3. Практичні завдання
Початковий
-
Перетворити зазначений у коді текстовий рядок
row
у масив слів з ім’ямwords
. Результат використання утвореного масиву слів представлено у демонстрації.
let i = 0;
let row = "I love programming with JavaScript and p5.js";
const colors = [
[63, 184, 175], // Verdigris
[147, 7, 240], // Electric Violet
[125, 211, 132], // Emerald
[255, 158, 157], // Salmon Pink
[240, 200, 8], // Jonquil
[158, 242, 255], //Blizzard Blue
[255, 61, 127], // French Rose
];
function setup() {
createCanvas(200, 200);
textAlign(CENTER, CENTER);
frameRate(2); // використання нижчої частоти кадрів для уповільнення появи тексту
}
function draw() {
let currentIndex = i % words.length;
let currentColor = colors[currentIndex];
let currentWord = words[currentIndex];
background(currentColor);
fill(255);
textSize(25);
text(currentWord, width / 2, height / 2);
i += 1;
}
Середній
-
Створити застосунок, в якому по натисканню миші змінюється текст на полотні. Орієнтовний взірець роботи застосунку представлено в демонстрації.
-
Створити застосунок, що імітує роботу світлофора. Орієнтовний зразок роботи застосунку представлено в демонстрації.
-
Створити застосунок, в якому поточні координати рухомих об’єктів відображаються на полотні як представлено в демонстрації.
-
Використати псевдографічні символи для генерування зображень візерунків. Один з екземплярів візерунків представлений на малюнку.

Високий
-
Створити застосунок, у якому текст відображається дзеркально. Орієнтовний зразок ефекту представлено в демонстрації. У застосунку використані додаткові шрифти, які можна завантажити за покликанням.
Екстремальний
-
Створити текстову візитівку фільму на вибір. Орієнтовний зразок візитівки фільму «Зоряні війни Епізод VI: Повернення джедая» представлено в демонстрації. У застосунку використані додаткові шрифти, які можна завантажити за покликанням.
-
Створити анімаційний ефект для тексту, який відтворюється за натисканням миші. Орієнтовний зразок ефекту анімації наведений у демонстрації. У застосунку використаний додатковий шрифт, який можна завантажити за покликанням.
Скористайтеся функцією lerp() , яку можна застосувати для створення руху по прямій траєкторії. |
-
Створити анімацію з емодзі. Орієнтовний зразок анімації наведений у демонстрації.
6.3. Зображення як цілісний об’єкт та як масив пікселів
Можливості бібліотеки p5.js
не обмежуються малюванням геометричних фігур.
Інструментарій p5.js
дозволяє використовувати файли растрових зображень, які, як відомо, є масивом пікселів - мініатюрних квадратів, що відображаються певним кольором.
6.3.1. Завантаження зображень
Розглянемо, як приєднувати графічні файли до ескізів.
Якщо розробка виконується в онлайн-редакторі, для додавання графічних файлів в ескіз необхідно зареєструватися на сайті редактора та увійти у свій обліковий запис.
У разі використання середовища Processing IDE
у режимі p5.js
для зберігання графічних файлів використовується каталог data
, який створюється автоматично щоразу, коли відбувається додавання графічного файлу в ескіз.
Для локальної розробки буде хорошим правилом створити окремий каталог для файлів зображень і слідкувати за правильністю написання шляхів до файлів зображень. |
Отже, завантажимо растрове зображення (640х360 пікселів) в ескіз і відобразимо його на полотні.

let img; (1)
function setup() {
createCanvas(640, 360);
// завантаження зображення із файлу
img = loadImage("ingenuity.jpg"); (2)
}
function draw() {
background(220);
// розміщення зображення в реальному розмірі в точці (0, 0)
image(img, 0, 0); (3)
}
1 | Оголошуємо змінну з ім’ям img . |
2 | За допомогою методу loadImage() , який приймає один аргумент - рядок із зазначенням шляху до файлу, завантажуємо цей файл у пам’ять. Змінна з ім’ям img - це покликання, яке вказує на місце в пам’яті, в якому зберігаються значення ширини й висоти зображення, масив пікселів, які визначають значення кольору для кожного пікселя у зображенні. Шлях до зображення повинен бути вказаний відносно HTML -файлу застосунку. Також, можна передати рядок кодованого зображення base64 як альтернативу шляху до файлу. Завантаження зображення з URL -адреси може бути заблоковано через вбудовану безпеку вебпереглядача. |
3 | Метод image() відтворює зображення на полотні повністю, коли приймає три аргументи: покликання на об’єкт зображення і дві координати лівого верхнього кута зображення. Якщо ж метод приймає п’ять аргументів, до вищезазначених аргументів додаються ширина й висота прямокутника, в межах якого зображення буде показано. Це корисно, якщо необхідно показати не усе зображення, а лише його частину. |
Може так статися, що зображення не відразу стає доступним для візуалізації. Щоб бути впевненим, що зображення завантажене і готове до використання, необхідно помістити виклик методу loadImage()
у функцію preload() за аналогією додавання у застосунок сторонніх шрифтів.
let img;
function preload() {
// preload() виконується лише один раз
img = loadImage("ingenuity.jpg");
}
function setup() {
// setup() чекає, поки preload() буде виконано
createCanvas(640, 360);
}
function draw() {
background(220);
// розміщення зображення в реальному розмірі в точці (0, 0)
image(img, 0, 0);
}
У виклику методу image()
додатково можна додати ще два параметри для встановлення ширини та висоти зображення. Наприклад, розташуємо у різних місцях на полотні копію зображення іншого розміру.
let img;
function preload() {
img = loadImage("ingenuity.jpg");
}
function setup() {
createCanvas(640, 360);
}
function draw() {
background(220);
// розміщення зображення в реальному розмірі в точці (0, 0)
image(img, 0, 0);
// розміщення зображення у 2 рази меншому розмірі в точці (0, 180)
image(img, 0, height / 2, img.width / 2, img.height / 2);
}
У коді застосунку ми звернулися до властивостей ширини img.width
і висоти img.height
зображення, використовуючи крапкову нотацію.
Вправа 56
Змінити код застосунку, щоб розмір другого зображення змінювався відповідно місця розташування вказівника миші.
Завантажимо ще одне зображення у наш застосунок, щоб отримати багатошарову структуру, кожен шар якої існує окремо.

let img1, img2;
function preload() {
img1 = loadImage("ingenuity.jpg");
img2 = loadImage("astronaut.png");
}
function setup() {
createCanvas(640, 360);
}
function draw() {
background(220);
image(img1, 0, 0);
image(img2, width - mouseX * 1.5, 40);
}
Візуалізацію завантаженого зображення можна змінювати, накладаючи на нього фільтри.
Просте застосування до зображення фільтра досягається використанням функції tint() .
Функція tint()
- це, по суті, еквівалент функції fill()
, який встановлює колір та прозорість для зображення. Аргументи для tint()
визначають, скільки зазначеного кольору використовувати для кожного пікселя цього зображення, а також наскільки прозорими повинні бути ці пікселі.
Для видалення поточного значення заливки для зображення і повернення зображення до оригінальних відтінків, використовують функцію noTint() .
Щоб застосувати до зображення прозорість, не впливаючи на його колір, використовують білий колір як колір відтінку та зазначають значення для прозорості.
Наприклад, виклик функції tint(255, 128)
зробить зображення прозорим на 50%
(за стандартним налаштуванням значення прозорості належить діапазону 0-255
, який можна змінити за допомогою colorMode() ).
Отже, застосуємо фільтр до завантаженого зображення квітки.

let flower;
function preload() {
flower = loadImage("flower.jpg");
}
function setup() {
createCanvas(650, 600);
}
function draw() {
// зображення зберігає початковий стан
tint(255);
image(flower, 0, 0);
// зображення виглядає темнішим
tint(100);
image(flower, 100, 100);
// непрозорість зображення становить 50%
tint(255, 128);
image(flower, 200, 200);
// червоний відсутній, більша частина зображення - зеленого і вся - синього кольорів
tint(0, 200, 255);
image(flower, 300, 300);
// зображення в червоному кольорі й прозоре
tint(255, 0, 0, 100);
image(flower, 400, 400);
}
Функція filter() надає багато фільтрів, які можна застосувати до зображення, використовуючи різні аргументи в поєднанні з додатковими параметрами:
-
THRESHOLD
- перетворює зображення на чорно-білі пікселі залежно від того, чи є вони вище або нижче порогу, зазначеного параметром рівня. Параметр має бути від0.0
(чорний) до1.0
(білий). Якщо рівень не вказано, використовується0.5
. -
GRAY
- перетворює будь-які кольори зображення на еквіваленти градацій сірого. Жоден параметр не використовується. -
OPAQUE
- встановлює абсолютну непрозорість. Жоден параметр не використовується. -
INVERT
- встановлює для кожного пікселя його зворотне значення. Жоден параметр не використовується. -
POSTERIZE
- обмежує кожну складову зображення кількістю кольорів, указаних як параметр. Для параметра можна встановити значення від2
до255
, але результати найбільш помітні в нижчих діапазонах. -
BLUR
- виконує розмиття за Гаусом із параметром рівня, що визначає ступінь розмиття. Якщо параметр не використовується, розмиття еквівалентне розмиттю за Гаусом радіуса1
. Більші значення збільшують розмиття. -
ERODE
- зменшує світлі ділянки. Жоден параметр не використовується. -
DILATE
- збільшує освітлені області. Жоден параметр не використовується.
Застосуємо певні фільтри за допомогою функції filter()
до зображення квітки. Використаємо для цього наступний код:
let img1, img2, img3, img4, img5, img6;
function preload() {
img1 = loadImage("flower.jpg");
img2 = loadImage("flower.jpg");
img3 = loadImage("flower.jpg");
img4 = loadImage("flower.jpg");
img5 = loadImage("flower.jpg");
img6 = loadImage("flower.jpg");
}
function setup() {
createCanvas(774, 378);
noLoop();
}
function draw() {
background(222, 231, 230); // Platinum
image(img1, 0, 0); // без фільтрів
img2.filter(GRAY); // сірий
image(img2, 258, 0);
img3.filter(THRESHOLD, 0.5); // чорно-білий
image(img3, 516, 0);
img4.filter(POSTERIZE, 3); // обмеження кількості кольорів
image(img4, 0, 189);
img5.filter(INVERT); // інверсія кольорів
image(img5, 258, 189);
img6.filter(BLUR, 2); // ефект розмиття
image(img6, 516, 189);
}

У прикладі з фільтрами використовувались окремі змінні для кожного із завантажених зображень. Такий підхід, як відомо, є не дуже оптимальним. У разі використання в застосунку великої кількості зображень застосовують масиви.
Створимо застосунок, в якому за натисканням миші на зображенні щоразу над тлом полотна візуалізується нове зображення.





Збережемо зазначені зображення у каталозі застосунку і звернемось до них у коді.
let filenames = [ (1)
"cat.jpg",
"dog.jpg",
"panda.jpg",
"parrot.jpg",
"giraffe.jpg",
];
let animals = []; (2)
let index = 0; (3)
function preload() {
for (let f of filenames) {
animals.push(loadImage(f)); (4)
}
}
function setup() {
createCanvas(500, 400);
}
function draw() {
background(220);
image(animals[index], 0, 0); (5)
}
function mousePressed() {
index = (index + 1) % animals.length; (6)
}
1 | Оголошуємо масив filenames із назвами графічних файлів, що містять зображення. |
2 | Оголошуємо порожній масив animals для зберігання об’єктів зображень тварин. |
3 | Початковий індекс об’єкта зображення. |
4 | Заповнюємо масив animals об’єктами зображень тварин, використовуючи циклічний прохід по масиву filenames , що містить назви файлів зображень. |
5 | Створюємо зображення над тлом полотна, звертаючись до об’єкта зображення у масиві animals за допомогою значення індексу index для поточного зображення. |
6 | Обчислюємо значення індексу для наступного зображення у функції mousePressed() , яка викликається один раз після кожного натискання кнопки миші. |
При натисканні на черговому зображенні, якщо його розміри більші за розміри полотна застосунку, візуалізується лише та його частина, яка поміщається у полотно, інакше спостерігаємо тло полотна і зображення, яке відображається повністю.
Переглядаємо Аналізуємо
Використовуючи метод image()
можна додати у застосунок не лише зображення із файлу, але й екземпляри полотна.
Створення нового полотна відбувається за допомогою функції createGraphics() , два параметри якої визначають ширину та висоту нового полотна у пікселях.
Розглянемо, як реалізуються два полотна в одному застосунку. У нашому прикладі буде відбуватися рух і відбивання від меж двох куль. Кулі матимуть власні характеристики та рухатимуться на окремих полотнах, «не знаючи» про існування одна одної.
let cnv;
let x, y, xCnv, yCnv;
let r, rCnv;
let xSpeed, ySpeed, xSpeedCnv, ySpeedCnv;
function setup() {
createCanvas(200, 200); (1)
x = width / 2;
y = height / 2;
r = 50;
xSpeed = random(0.7);
ySpeed = random(1);
cnv = createGraphics(100, 100); (2)
xCnv = cnv.width / 2;
yCnv = cnv.height / 2;
rCnv = 15;
xSpeedCnv = random(0.8);
ySpeedCnv = random(1.1);
}
function draw() {
// основне полотно (3)
background(220);
noStroke();
fill(255);
ellipseMode(RADIUS);
ellipse(x, y, r);
if (x - r < 0 || x > width - r) {
xSpeed = xSpeed * -1;
}
if (y - r < 0 || y > height - r) {
ySpeed = ySpeed * -1;
}
x = x + xSpeed;
y = y + ySpeed;
// екземпляр нового полотна (4)
cnv.background(0, 63, 136); // Dark Cornflower Blue
cnv.noStroke();
cnv.fill(255, 213, 0); // Gold Web Golden
cnv.ellipseMode(RADIUS);
cnv.ellipse(xCnv, yCnv, rCnv);
if (xCnv - rCnv < 0 || xCnv > cnv.width - rCnv) {
xSpeedCnv = xSpeedCnv * -1;
}
if (yCnv - rCnv < 0 || yCnv > cnv.height - rCnv) {
ySpeedCnv = ySpeedCnv * -1;
}
xCnv = xCnv + xSpeedCnv;
yCnv = yCnv + ySpeedCnv;
image(cnv, width / 2, 0); (5)
}
1 | Створюємо екземпляр основного полотна і визначаємо у змінних характеристики для першої кулі. |
2 | Створюємо екземпляр нового полотна cnv за допомогою createGraphics() і визначаємо у змінних характеристики для другої кулі. |
3 | Записуємо функції бібліотеки p5.js для першої кулі як зазвичай. Перша куля буде рухатися на основному полотні. |
4 | Записуємо функції бібліотеки p5.js для другої кулі за допомогою крапкової нотації для нового об’єкту полотна cnv . |
5 | Розміщуємо нове полотно cnv над основним полотном. |
Як і у разі завантаження кількох зображень на полотно, екземпляри полотна утворюють багатошарову структуру, кожен шар якої існує окремо. |
Переглядаємо Аналізуємо
6.3.2. Пікселі
Як відомо, в комп’ютерній графіці найменшу точку зображення називають пікселем.
Растрові зображення в комп’ютерній графіці є масивом (двовимірною таблицею) точок зображення, які відображаються на екрані за допомогою пікселів. Кожна точка зображення зберігає інформацію про колір.
Наприклад, якщо взяти невеликий фрагмент растрового зображення, то пікселі (які є насправді квадратами) можна подати у вигляді двовимірної таблиці.

Цю двовимірну таблицю можна розкласти в лінію у вигляді одномірного масиву.

З огляду на таке розкладання масиву пікселів, щоб знайти піксель з координатами x = 3
, y = 1
двовимірного масиву (піксель з номером 10
), потрібно обчислити вираз
в якому x
- значення координати зліва направо, y
- значення координати згори донизу, p
- кількість пікселів зображення у ширину
Для зберігання усіх пікселів зображення бібліотека p5.js
використовує вбудований масив pixels .
Перш ніж отримати доступ до масиву pixels
, дані про пікселі повинні бути завантажені у масив pixels
за допомогою функції loadPixels() .
Якщо дані масиву будуть змінені, необхідно виконати функцію updatePixels() , щоб оновити зміни.
Поглянемо на розмір заповненого масиву pixels
для розміру зображення полотна 200x200
пікселів.
function setup() {
createCanvas(200, 200);
noLoop();
}
function draw() {
background(67, 19, 141); // Indigo
loadPixels();
console.log(pixels.length); // 160000
}
На перший погляд, результат може здивувати, оскільки, якщо перемножити значення у пікселях розмірів зображення полотна за шириною і за висотою, то результат аж ніяк не буде 160000
:
Якщо поглянути на сам масив pixels
, то він є масивом типу Uint8ClampedArray , а його вміст у нашому прикладі є таким:
Uint8ClampedArray {0: 67, 1: 19, 2: 141, 3: 255, 4: 67, ...}
Як бачимо, масив pixels
містить четвірки чисел у порядку R
, G
, B
, A
для кожного пікселя, рухаючись зліва направо по кожному рядку, спускаючись донизу. З цього стає зрозуміло, чому наші розрахунки були некоректними. Загалом масив pixels
є розміром ширина зображення у пікселях x висота зображення пікселях x 4.
Якщо використовувати ідею двовимірної таблиці, у якій координати пікселя позначаються (x, y)
(x
- значення координати зліва направо по горизонталі, y
- значення координати згори донизу по вертикалі), перші чотири значення (індекси 0-3
) у масиві pixels
будуть значеннями R
, G
, B
, A
пікселя в точці (0, 0)
. Другі чотири значення (індекси 4-7
) міститимуть значення R
, G
, B
, A
пікселя в точці (1, 0)
і т. д.


Загалом, щоб встановити значення для пікселя (x, y)
, потрібно пройтися по масиву pixels
як по двовимірному масиву і використати вищезгадану формулу розрахунку номера пікселя i
, враховуючи, що для кожного пікселя у масиві pixels
відводиться чотири значення: червоної, зеленої та синьої складових кольору й прозорості.
function setup() {
createCanvas(200, 200);
noLoop();
}
function draw() {
background(67, 19, 141); // Indigo
loadPixels();
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let i = (x + y * width) * 4; (1)
pixels[i] = 0; (2)
pixels[i + 1] = 100; (3)
pixels[i + 2] = 0; (4)
pixels[i + 3] = 255; (5)
}
}
updatePixels();
}
При навігації по звичайному двовимірному масиві зовнішній цикл проходить по рядках, а внутрішній - по стовпцях. У цьому разі координати конкретного елемента масиву (x, y) , де x - індекс рядка, y - індекс стовпця. Для масиву пікселів зовнішній цикл проходить по стовпцях, а внутрішній - по рядках. Позначення координат пікселя (x, y) залишається таким самим, але самі значення стають перевернутими, оскільки x - індекс стовпця, y - індекс рядка.
|
1 | Обчислюємо індекс i пікселя у представленні двовимірного масиву. |
2 | Присвоюємо значення червоній складовій кольору пікселя з індексом i . |
3 | Присвоюємо значення зеленій складовій кольору пікселя з індексом i . |
4 | Присвоюємо значення синій складовій кольору пікселя з індексом i . |
5 | Присвоюємо значення для прозорості пікселя з індексом i . |
У результаті виконання застосунку кожен піксель полотна застосунку буде світитися зеленим кольором.
Для роботи з пікселями безпосередньо на екрані, використовують ідею подання пікселів, за якої пікселі мають положення (x, y) у двовимірному просторі. Однак, пікселі масиву pixels мають лише один вимір, зберігаючи значення кольорів у лінійній послідовності.
|
Вправа 57
Створити застосунок, в якому кожен піксель полотна світиться одним кольором та є наполовину прозорим.
Цікавимось Додатково
У підсумку, розглянемо приклад візуалізації зображення у стилі пуантилізм , використовуючи знання про пікселі.
Для наших цілей використаємо зображення (750x501 пікселів) картини Недільне післяобіддя на острові Гранд Жатт Жоржа Сера (1884-1886).
Помістимо зображення картини, яка сама є прикладом крапкового стилю в живописі, у каталог застосунку і напишемо код, який імітує техніку пуантилізму.
let img;
let sizePoint = 10; // розмір крапок
let a = 200; // прозорість крапок
let x, y, i; // координати та індекс поточного пікселя зображення
function preload() {
img = loadImage("Georges_Seurat_Sunday_afternoon_on_the_island_of_La_Grande_Jatte_before.jpg"); (1)
}
function setup() {
createCanvas(750, 501);
background(0);
noStroke();
img.loadPixels(); (2)
}
function draw() {
x = int(random(img.width)); (3)
y = int(random(img.height));
i = (x + y * img.width) * 4; (4)
let r = img.pixels[i]; (5)
let g = img.pixels[i + 1];
let b = img.pixels[i + 2];
fill(r, g, b, a); (6)
ellipse(x, y, sizePoint, sizePoint);
}
function mousePressed() { (7)
saveCanvas("myCanvas" + frameCount, "png");
}
1 | Завантажуємо у застосунок зображення. img - ім’я, за яким можна звернутися до зображення. Розмір полотна відповідає розміру самого зображення. |
2 | Заповнюємо масив pixels значеннями пікселів завантаженого зображення img . |
3 | Випадково обчислюємо значення x -координати та y -координати пікселя зображення img . |
4 | Обчислюємо індекс i пікселя на основі координат, ширини завантаженого зображення img і знань про розміщення значень колірних складових пікселя у масиві pixels . |
5 | Використовуючи індекс i пікселя отримуємо доступ до значень r , g і b колірних складових пікселя у масиві pixels |
6 | Малюємо на полотні коло на основі отриманих значень r , g і b та наперед визначеної прозорості a та встановленого розміру sizePoint . |
7 | За допомогою функції mousePressed() , яка викликається завдяки натисканню кнопки миші, зберігаємо вигляд полотна на цей момент у файл з ім’ям myCanvasN.png , де N - номер кадру, за допомогою функції saveCanvas() . |
Результат виконання коду застосунку - зображення в стилі пуантилізму, створене за допомогою крапок. Крапки відображаються по черзі, тобто, у кожному виклику draw()
створюється одна крапка, положення якої є випадковим.

Ще один спосіб отримати доступ до окремого пікселя зображення - застосування до зображення методу get() .
Особливості роботи цього методу наступні:
-
якщо метод
get()
викликається для зображення без параметрів - отримуємо усе зображення; -
якщо
x
іy
є єдиними переданими у метод параметрами, отримуємо один піксель, який розташований у зазначених координатах; -
якщо передано усі параметри (
x
,y
,w
,h
), отримуємо із зображення прямокутну область пікселів шириноюw
і висотоюh
.
Отримавши окремий піксель зображення, можна дістатися до його значень колірних складових та прозорості. Це легко зробити за допомогою функцій:
Використаємо вищезгадані метод та функції, створивши застосунок, який повідомляє колірні характеристики пікселя зображення, над яким знаходиться вказівник миші.
let img;
function preload() {
img = loadImage("https://picsum.photos/200/300/?random");
}
function setup() {
createCanvas(img.width, img.height);
}
function draw() {
background(255);
image(img, 0, 0);
let p = img.get(mouseX, mouseY);
console.log(
`red: ${red(p)}, green: ${green(p)}, blue: ${blue(p)}, alpha: ${alpha(p)}`
);
console.log(`red: ${p[0]}, green: ${p[1]}, blue: ${p[2]}, alpha: ${p[3]}`);
}
Після запуску застосунку, рухаючи вказівник миші над зображенням, у консолі вебпереглядача отримуємо значення характеристик пікселів, над якими перебуває вказівник миші:
red: 90, green: 82, blue: 71, alpha: 255
Як видно з коду, для виведення у консоль значень колірних характеристик пікселів використовується два способи: перший - за допомогою функцій, які застосовуються до пікселя і повертають значення червоної, зеленої та синьої складових й прозорості, другий - звертаючись до пікселя як до елемента, що містить значення r
, g
, b
і a
, масиву pixels
.
У застосунку використовуються випадкові зображення із сайту Lorem Picsum - аналог класичного варіанту беззмістовного тексту Lorem Ipsum , але для зображень. |
6.3.3. Ресурси
Корисні джерела
6.3.4. Контрольні запитання
Міркуємо Обговорюємо
-
Назвати особливості растрових зображень.
-
Описати алгоритм візуалізації растрового зображення на полотні застосунку.
-
Які ефекти можна застосувати до зображень, використовуючи засоби бібліотеки
p5.js
? -
Яка структура масиву
pixels
? -
Як отримати доступ до конкретного пікселя зображення, використовуючи можливості бібліотеки
p5.js
?
6.3.5. Практичні завдання
Початковий
-
Завантажити в ескіз зображення на вибір і застосувати до зображення фільтри.
-
Використати код застосунку для створення на полотні колірних градієнтів - плавних переходів від одного кольору до іншого. Один із можливих варіантів колірного градієнта представлений на малюнку.

function setup() {
createCanvas(200, 200);
noLoop();
}
function draw() {
background(48, 150, 138); // Persian Green
loadPixels();
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let i = (x + y * width) * 4;
pixels[i] = 0;
pixels[i + 1] = 100;
pixels[i + 2] = 0;
pixels[i + 3] = 255;
}
}
updatePixels();
}
Середній
-
Створити на полотні піксельну мозаїку як на малюнку.

-
Створити на полотні чорно-білу піксельну мозаїку як на малюнку.

-
Завантажити в ескіз зображення на вибір. Зробити копію зображення і повернути її по горизонталі. Як зразок результату роботи застосунку, на малюнку представлена картина Ньютон в Саду Ідей українського художника Олега Шупляка .

Для створення потрібного ефекту використайте функцію scale() бібліотеки p5.js .
|
Високий
-
Створити застосунок із застосуванням фільтрів до фрагментів зображення. Щоб працювати з окремими фрагментами одного зображення, розділіть зображення на однакові частини, наприклад, скориставшись сервісом pinetools.com . Орієнтовний взірець роботи застосунку представлений в демонстрації. Файли фрагментів використаного в ескізі зображення можна завантажити за покликанням.
Екстремальний
-
Створити застосунок з кількома екземплярами полотна для реалізації ефекту як у демонстрації.
-
Створити застосунок, який генерує випадковий колаж з об’єктів світлин. При натисканні миші на фото зображення зникає, а на його місці залишається напівпрозора прямокутна рамка. Її створення відбувається завдяки зміні масиву пікселів конкретного зображення. Орієнтовний зразок роботи застосунку представлений в демонстрації. У застосунку використані світлини зірок світової кіноіндустрії.
6.4. Відео як цілісний об’єкт та як масив зображень. Анімації
6.4.1. Анімація
Анімація - це вид кіномистецтва, твори якого створюються із низки кадрів - зображень, які фіксують окремі послідовні фази руху (або інші зміни) об’єктів. |
Під час демонстрації послідовності зображень з частотою, прийнятною для зорового сприйняття людиною образів (16-30 кадрів за секунду), створюється ілюзія руху або інших змін. Це відбувається завдяки тому, що черговий кадр анімації ненабагато відрізняється від попереднього.
За допомогою бібліотеки p5.js
розглянемо принципи створення комп’ютерної покадрової анімації, коли кожен кадр є окремим зображенням, на якому зафіксований рух чи інші зміни об’єкта.
Для нашого прикладу використаємо зображення тла комп’ютерної гри Super Mario Bros.

і три зображення із різними положеннями персонажа гри Маріо



Розглянемо код застосунку, який дозволить нам керувати зміною кадрів анімації.
let nFrames = ["mario1", "mario2", "mario3"]; (1)
let marioImages = []; (2)
let bg; (3)
let isAnimate = true; (4)
let currentFrame = 0; (5)
let x = 0; (6)
function preload() { (7)
bg = loadImage("./mario/mariobg.png");
for (let f of nFrames) {
marioImages.push(loadImage(`./mario/${f}.png`));
}
}
function setup() {
createCanvas(bg.width, bg.height);
frameRate(25); (8)
}
function draw() {
fill(255); // White
image(bg, 0, 0); (9)
text(`${frameCount} / ${currentFrame + 1}`, 10, 20); (10)
noTint(); (14)
if (isAnimate) { (11)
// зміна кадрів
image(marioImages[currentFrame], x, bg.height - 130);
currentFrame += 1;
if (currentFrame > marioImages.length - 1) {
currentFrame = 0;
}
// рух по горизонталі (13)
x = x + 4;
if (x > bg.width - 175) {
tint(255, 128);
x = -60;
}
} else { (12)
image(marioImages[currentFrame], x, bg.height - 130);
}
}
function keyPressed() { (15)
isAnimate = !isAnimate;
}
1 | Оголошуємо масив nFrames , елементами якого є імена графічних файлів, які завантажені у створений каталог mario застосунку. |
2 | Оголошуємо порожній масив marioImages , який зберігатиме об’єкти зображень, завантажених із графічних файлів. |
3 | Змінна bg буде містити зображення тла гри. |
4 | Змінна isAnimate буде містити значення про те, чи відбувається на цей момент анімація, чи ні. |
5 | Змінна currentFrame міститиме значення номера зображення (кадру) анімації. |
6 | Змінна x буде містити значення x -координати на полотні зображення з масиву marioImages . Водночас з анімацією персонажа він буде рухатися горизонтально. |
7 | Усередині функції preload() завантажуємо зображення із каталогу mario в ескіз за допомогою функції loadImage() і поміщаємо об’єкти зображень персонажу у масив marioImages , а зображення тла - у bg . |
8 | За допомогою функції frameRate() уповільнюємо виконання функції draw() до 25 разів в секунду. |
9 | За допомогою функції image() розміщуємо зображення тла гри на полотні. |
10 | Виводимо у лівому верхньому куті полотна значення frameCount , яке містить кількість викликів функції draw() , і номер зображення (кадру) анімації, розпочавши відлік з одиниці. |
11 | Якщо isAnimate містить істинне значення true , за допомогою функції image() розміщуємо у зазначених координатах на полотні зображення з масиву marioImages , яке має індекс currentFrame . Збільшуємо значення currentFrame , щоб розмістити наступне зображення. Це відбувається доти, доки номер currentFrame поточного зображення є на одиницю меншим довжини масиву marioImages . Як тільки значення currentFrame стане більшим за індекс останнього зображення, то відлік розпочнеться спочатку, з нуля. У такий спосіб відбувається зациклення появи зображень персонажу на полотні. |
12 | Якщо isAnimate набуває значення false , то анімація зупиняється. На полотні візуалізується зображення з індексом currentFrame і це буде наступне зображення у послідовності. Рахунок кадрів виклику функції draw() продовжується. |
13 | Анімація персонажу рухається горизонтально завдяки зміні значення x -координати. Як тільки анімація персонажу досягає зеленої труби, до зображень персонажу застосовується функція tint() , яка робить анімовані зображення напівпрозорими. Так досягається ефект входу персонажа у трубу. Після цього анімація отримує від’ємне значення x -координати й тому з’являється поза лівою межею полотна. Усі вищенаведені дії повторюється знову. |
14 | Використовуємо функцію noTint() для повернення зображень персонажу до оригінальних відтінків. |
15 | Функція keyPressed() викликається щоразу, коли натискається будь-яка клавіша на клавіатурі. Вона керує зупинкою та продовженням анімації, перемикаючи значення isAnimate на протилежне. |
Переглядаємо Аналізуємо
Тепер виконаємо зворотний процес - перетворимо виконання застосунку (покадрова анімація персонажа гри і його горизонтальний рух) на послідовність окремих зображень (кадрів) за допомогою функції saveFrames() .
Функцію saveFrames()
помістимо у функцію mousePressed()
, а це значить, що зберігати кадри застосунку будемо за натисканням миші.
Застосувати функцію saveFrames()
можна двома способами.
Перший спосіб - завантаження на комп’ютер зображень (кадрів) як окремих файлів за допомогою діалогового вікна вебпереглядача.
function mousePressed() {
saveFrames(`mario-world-${frameCount}`, "png", 2, 3);
}
Перший аргумент функції saveFrames()
- це назва графічного файлу. Для нашого ескізу назви будуть на зразок mario-world-60.png
, mario-world-61.png
, mario-world-62.png
і т. д.
У формуванні імені графічного файлу використовується системна змінна frameCount
, яка містить кількість кадрів, що були відображені з моменту запуску застосунку. Всередині функції setup()
значення frameCount
дорівнює 0
, після першого виклику draw()
значення frameCount
дорівнює 1
.
Другий аргумент зазначає розширення графічного файлу (png
або jpg
). Третій аргумент - це тривалість в секундах для збереження кадрів, а четвертий - кількість кадрів за секунду.
У підсумку, вебпереглядач запропонує зберегти 6
зображень (2 * 3 = 6
).
Значення тривалості і кількості кадрів повинні бути менше або дорівнювати 15 і 22 відповідно, що означає, що можна завантажувати максимум кадри тривалістю 15 секунд зі швидкістю 22 кадри на секунду, додаючи до 330 кадрів. Це робиться для того, щоб уникнути проблем з пам’яттю, оскільки досить велике полотно може дуже легко заповнити пам’ять комп’ютера та призвести до збою застосунку або вебпереглядача.
|
Другий спосіб - друк в консолі масиву об’єктів зображень з ім’ям data
або іншим на вибір.
function mousePressed() {
saveFrames(`mario-world-${frameCount}`, "png", 2, 5, data => {
print(data);
});
}
У цьому разі розмір масиву data
дорівнює загальній кількості кадрів - 10
(2 * 5 = 10
)
Вправа 58
Змінити код застосунку для уповільнення і прискорення анімації.
Реалізуємо ще один приклад анімації - ефект переходу - плавної зміни прозорості двох зображень на полотні.
Спочатку перше зображення візуалізується прозорим, а друге - непрозорим. При переміщенні вказівника миші по екрану зліва направо рівень прозорості першого зображення зменшується, а другого, відповідно, збільшується. У разі руху у зворотному напрямку має спостерігатися зворотний ефект.
Для наших цілей використаємо зображення роботів Boston Dynamics, що танцюють, та зображення картини Танець в Остерії Вільгельма Марстранда (1860 рік).
Для створення ефекту переходу у коді застосунку використаємо механізм розрахунку значення x
-координати вказівника миші у діапазоні значень прозорості від 0
до 255
.
let img1, img2;
function preload() {
img1 = loadImage("Wilhelm_Marstrand_Dance_in_an_Osteria.jpg");
img2 = loadImage("Boston_Dynamics_dance.png");
}
function setup() {
createCanvas(600, 480);
background(220);
}
function draw() {
let t1 = map(mouseX, 0, img1.width, 0, 255); (1)
let t2 = map(mouseX, 0, img2.width, 255, 0);
tint(255, t1); (2)
image(img1, 0, 0);
tint(255, t2);
image(img2, 0, 0);
}
Розглянемо особливості коду:
1 | По горизонталі вказівник миші змінює свою x -координату від 0 до значення ширини зображення. Щоб значення x -координати перетворити у значення діапазону від 0 до 255 , використовуємо функцію map() , яка отримує п’ять значень. Перший аргумент - значення x -координати вказівника миші, яке необхідно перевести. Другий і третій аргументи - це значення меж діапазону з якого треба перевести, тобто вхідні значення - 0 і ширина зображення. Четвертий і п’ятий аргументи - це значення меж діапазону, у який необхідно перевести, відповідно від 0 до 255 і від 255 до 0 . Результати розрахунків зберігаємо під іменами t1 і t2 . |
2 | Використовуємо функцію tint() , яка робить обидва зображення прозорими на величини t1 і t2 відповідно. |
Переглядаємо Аналізуємо
6.4.2. Відео
Відео можна розглядати як масив нерухомих зображень (кадрів), які послідовно змінюються одне за одним, створюючи ефект руху об’єктів на екрані. Чим більша частота кадрів (кількість кадрів за секунду), тим плавнішим і природнішим буде здаватися рух.
Мінімальне значення, за якого рух буде сприйматися однорідним - приблизно 10 кадрів за секунду (це значення індивідуальне для кожної людини). У традиційному плівковому кінематографі використовується частота 24 кадри за секунду, у телебаченні використовують від 25 кадрів до 30 кадрів за секунду, а комп’ютерні зацифровані відеоматеріали гарної якості, як правило, використовують частоту 30 кадрів за секунду.
Використаємо бібліотеку p5.js
для створення застосунків, в яких відтворюється відео. Як зразок відео візьмемо відео з паркуром роботів Boston Dynamics
.
Щоб розмістити на вебсторінці застосунку відео, використаємо функцію createVideo() , яка у DOM вебсторінки застосунку створює HTML
-елемент <video>
для простого відтворення аудіо/відео.
Перший параметр функції createVideo()
може бути або рядком, що містить шлях до відеофайлу, або масивом рядків, які містять шляхи до різних форматів одного відеофайлу. Це корисно для забезпечення відтворення відео у різних вебпереглядачах з підтримкою різних форматів.
Для конвертації відео в інші формати використовуйте онлайн-сервіси. |
Виклики функцій createVideo() , createAudio() , createCapture() створюють відповідні об’єкти на основі класу p5.MediaElement , який розширює клас p5.Element - базовий клас для усіх елементів, доданих до ескізу, включаючи полотно та HTML -елементи - в частині обробки аудіо та відео.
|
Використаємо кілька методів для керування опціями відтворення відео, а саму функцію createVideo()
розмістимо всередині функції preload() , яка викликається безпосередньо перед setup()
і використовується для обробки асинхронного завантаження зовнішніх файлів - у цьому разі для завантаження відео.
let atlas; (1)
function preload() {
atlas = createVideo("Atlas_Parkour.mp4");
}
function setup() {
createCanvas(640, 360);
atlas.size(width, height); (2)
atlas.speed(1); (3)
atlas.volume(0.1); (4)
atlas.showControls(); (5)
atlas.noLoop(); (6)
}
function draw() {
background(94, 144, 114); // Viridian
image(atlas, 0, 0, width, height); (7)
fill(255);
text(atlas.time(), width - 70, 20); (8)
text(atlas.duration(), width - 70, 40); (9)
}
1 | Оголошуємо змінну atlas , яка буде посилатися на об’єкт завантаженого відео. |
2 | Встановлюємо ширину і висоту HTML -елемента <video> на вебсторінці - розміри вікна програвача відео. Вікно програвача відео розміщується на вебсторінці поруч із полотном ескізу. |
3 | Встановлюємо швидкість відтворення відео: 2.0 - у два рази швидше, 1.0 - стандартно, 0.5 - у два рази повільніше. |
4 | Встановлюємо гучність відтворення звуку у діапазоні від 0.0 до 1.0 . |
5 | Відображаємо у програвачі стандартні елементи керування відтворенням відео. |
6 | Опція зупинки відтворення відео після досягнення кінця. |
7 | Розміщення копії вікна програвача відео на полотні. |
8 | Відображення на полотні у правому верхньому куті позначки часу в секундах від початку відтворення відео. |
9 | Відображення на полотні у правому верхньому куті позначки часу в секундах тривалості відтворення відео. |
Після запуску застосунку на вебсторінці ескізу з’явиться вікно програвача і полотно із початковими кадрами з відео. Якщо увімкнути відтворення відео у програвачі, відео буде відтворюватись водночас й на полотні.
Вікно програвача на вебсторінці у разі потреби можна приховати. Для цього необхідно використати метод hide() (по суті, це CSS
-правило display:none
) для приховування HTML
-елементів: atlas.hide();
. Водночас вікно відтворення відео на полотні також буде схованим, а відображатимуться полотно і позначки часу.

Щоб відео усе-таки відтворити на полотні, потрібно скористатися методом atlas.play();
, який відтворює медіаелементи.
let atlas;
function preload() {
atlas = createVideo("Atlas_Parkour.mp4");
}
function setup() {
createCanvas(640, 360);
atlas.size(width, height);
atlas.speed(1);
atlas.volume(0.1);
atlas.hide(); (1)
atlas.play(); (2)
atlas.showControls();
atlas.noLoop();
}
function draw() {
background(94, 144, 114); // Viridian
image(atlas, 0, 0, width, height);
fill(255);
text(atlas.time(), width - 70, 20);
text(atlas.duration(), width - 70, 40);
}
1 | Приховування вікна програвача відео. |
2 | Відтворювати відео як тільки запустити застосунок. |
З іншими методами для медіаелементів можна ознайомитися на сторінці p5.MediaElement довідки по функціях бібліотеки p5.js .
|
Цікавимось
Вправа 59
Використовуючи функцію createCapture() отримайте відео з вашої вебкамери та розмістіть на полотні чотири вікна трансляції наживо.

Тепер додамо у наш застосунок кнопки керування, щоб відтворення і зупинка відео відбувались за вказівкою користувача.
let atlas;
let isPlaying = false; (1)
let btnPlay; (2)
function preload() {
atlas = createVideo("Atlas_Parkour.mp4");
}
function setup() {
createCanvas(640, 360);
atlas.size(width, height);
atlas.speed(1);
atlas.volume(0.1);
atlas.hide();
btnPlay = createButton("Відтворити"); (3)
btnPlay.mousePressed(btnPlayClick); (4)
}
function draw() {
background(94, 144, 114); // Viridian
image(atlas, 0, 0, width, height);
fill(255);
text(atlas.time(), width - 70, 20);
text(atlas.duration(), width - 70, 40);
}
function btnPlayClick() { (5)
if (isPlaying) {
atlas.pause();
btnPlay.html("Відтворити");
} else {
atlas.play();
btnPlay.html("Пауза");
}
if (atlas.duration() === atlas.time()) {
atlas.stop();
btnPlay.html("Відтворити");
}
isPlaying = !isPlaying;
}
1 | Ініціалізуємо змінну isPlaying із логічним значеннням false , яка сигналізуватиме, чи відтворюється відео (true ) чи ні (false ). При запуску застосунку відео відразу відтворюватися не буде. |
2 | Оголошуємо змінну з ім’ям btnPlay , що буде покликатися на об’єкт кнопки. |
3 | Створюємо кнопку з написом Відтворити, використовуючи функцію createButton() і зберігаємо покликання на об’єкт кнопки з ім’ям btnPlay . |
4 | Застосовуємо на кнопці btnPlay метод mousePressed() , щоб визначити поведінку під час натискання кнопки btnPlay . Метод mousePressed() викликається один раз після кожного натискання кнопки миші на кнопці btnPlay і є слухачем події натискання на кнопку btnPlay . Аргумент btnPlayClick методу mousePressed() є обробником події натискання на кнопку btnPlay . |
5 | Функція btnPlayClick() є обробником події натискання на кнопку btnPlay . |
В оголошенні функції btnPlayClick() після її назви записуються круглі дужки відповідно до правил оголошення функцій у JavaScript , а у методі-слухачі mousePressed() як аргумент записуємо лише назву функції-обробника події, тобто btnPlayClick .
|
Після запуску застосунку на вебсторінці ескізу на полотні з’явиться вікно з відео, а під ним - кнопка з написом Відтворити. Після натискання на кнопку Відтворити, розпочнеться відтворення відео.
Відео розпочне відтворюватися завдяки тому, що змінна isPlaying
має значення false
і тому в тілі функції btnPlayClick()
спрацює гілка else
вказівки розгалуження, в якій до об’єкта відео atlas
буде застосований метод play()
, який, власне, і запускає відтворення. Також на кнопці зміниться напис на Пауза завдяки методу html()
, який буде застосований до об’єкта кнопки btnPlay
, а змінна isPlaying
набуде значення true
.
На момент наступного натискання на кнопку вже з написом Пауза змінна isPlaying
має значення true
і тому у тілі функції btnPlayClick()
спрацює гілка вказівки розгалуження if
, в якій до об’єкту відео atlas
буде застосований метод pause()
, що зупинить відтворення відео. Відповідно напис за допомогою методу html()
знову зміниться на Відтворити, а змінна isPlaying
набуде значення false
.
Коли відео завершить своє відтворення, кнопка матиме напис Пауза. Якщо ж після цього натиснути на кнопку, то у функції-обробнику btnPlayClick()
відбудеться перевірка умови atlas.duration() === atlas.time()
(час тривалості відео дорівнює позначці часу відтворення відео), яка поверне результат true
, і до об’єкта відео atlas
буде застосований метод stop()
, що повертає нас на перший кадр відео, а метод html()
змінить напис на кнопці на Відтворити.
Вправа 60
Додати до застосунку кнопку зупинки відтворення відео.
Додамо до нашого застосунку ще одну кнопку btnSnap
для створення знімків-зображень із відео.
Спочатку в коді застосунку у draw()
закоментуємо рядки з функціями background()
та image()
, щоб знімки не перекривались об’єктами, що створюються цими функціями. А в setup()
закоментуємо рядок з методом hide()
, щоб відобразити вікно програвача відео поруч з полотном.
let atlas;
let isPlaying = false;
let btnPlay;
let btnSnap; (1)
function preload() {
atlas = createVideo("Atlas_Parkour.mp4");
}
function setup() {
createCanvas(640, 360);
atlas.size(width, height);
atlas.speed(1);
atlas.volume(0.1);
// atlas.hide();
btnPlay = createButton("Відтворити");
btnPlay.mousePressed(btnPlayClick);
btnSnap = createButton("Знімок"); (2)
btnSnap.mousePressed(btnSnapClick); (3)
}
function draw() {
// background(94, 144, 114); // Viridian
// image(atlas, 0, 0, width, height);
fill(255);
text(atlas.time(), width - 70, 20);
text(atlas.duration(), width - 70, 40);
}
function btnPlayClick() {
if (isPlaying) {
atlas.pause();
btnPlay.html("Відтворити");
} else {
atlas.play();
btnPlay.html("Пауза");
}
if (atlas.duration() === atlas.time()) {
atlas.stop();
btnPlay.html("Відтворити");
}
isPlaying = !isPlaying;
}
function btnSnapClick() { (4)
image(atlas.get(), 0, 0, width / 2, height / 2);
}
1 | Оголошуємо змінну з ім’ям btnSnap , що буде покликатися на об’єкт кнопки. |
2 | Створюємо кнопку з написом Знімок, використовуючи функцію createButton() і зберігаємо покликання на об’єкт кнопки з ім’ям btnSnap . |
3 | Застосовуємо на кнопці btnSnap метод mousePressed() , щоб визначити поведінку під час натискання кнопки btnSnap . Метод mousePressed() викликається один раз після кожного натискання кнопки миші на кнопці btnSnap і є, як було вже зазначено вище, слухачем події натискання на кнопку btnSnap . Аргумент btnSnapClick методу mousePressed() є обробником події натискання на кнопку btnSnap . |
4 | Функція btnSnapClick() є обробником події натискання на кнопку btnSnap . У тілі функції btnSnapClick() використовуємо метод get() (якщо метод викликається без параметрів - отримуємо усе зображення; якщо x і y є єдиними переданими параметрами, отримуємо один піксель; якщо передано усі параметри, отримуємо прямокутну область пікселів із зображення) на об’єкті відео atlas для створення миттєвого знімка вікна відтворення відео і функцію image() для розміщення знімка-зображення на полотні у зазначених координатах. |
Розглянемо випадок, коли нам необхідно зберігати багато знімків екрана вікна відтворення відео та у разі потреби звертатися до будь-якого з них. Отже, для цих цілей використаємо масив з ім’ям snaps
і внесемо зміни у код застосунку.
let atlas;
let isPlaying = false;
let btnPlay;
let btnSnap;
let snaps = []; (1)
function preload() {
atlas = createVideo("Atlas_Parkour.mp4");
}
function setup() {
createCanvas(640, 360);
atlas.size(width, height);
atlas.speed(1);
atlas.volume(0.1);
atlas.hide();
btnPlay = createButton("Відтворити");
btnPlay.mousePressed(btnPlayClick);
btnSnap = createButton("Знімок");
btnSnap.mousePressed(btnSnapClick);
}
function draw() {
background(94, 144, 114); // Viridian
image(atlas, 0, 0, width, height);
image(snaps[0] || atlas, 0, 0, width / 2, height / 2); (2)
fill(255);
text(atlas.time(), width - 70, 20);
text(atlas.duration(), width - 70, 40);
}
function btnPlayClick() {
if (isPlaying) {
atlas.pause();
btnPlay.html("Відтворити");
} else {
atlas.play();
btnPlay.html("Пауза");
}
if (atlas.duration() === atlas.time()) {
atlas.stop();
btnPlay.html("Відтворити");
}
isPlaying = !isPlaying;
}
function btnSnapClick() {
snaps.push(atlas.get()); (3)
}
1 | Оголошуємо змінну з ім’ям snaps , що буде покликанням на масив зроблених знімків. |
2 | Використовуємо функцію image() для розміщення на полотні зробленого знімка-зображення у два рази меншого розміру за вікно відтворення відео. Логічний вираз snaps[0] || atlas можна інтерпретувати так: якщо у масиві є перший знімок, то він як аргумент потрапляє у функцію image , яка розміщує цей знімок на полотні, якщо ж значення першого елемента масиву snaps[0] дорівнює undefined (першого знімку ще робили), то у функцію image потрапляє об’єкт atlas , і на місці знімка відтворюється відео. Якщо перший знімок зроблено, то лише він буде демонструватися на полотні незалежно від кількості зроблених знімків. |
3 | Додаємо знімок-зображення у масив snaps щоразу при натисканні кнопки Знімок. |
Переглядаємо Аналізуємо
Змінимо код застосунку, щоб на полотні відображався останній зроблений знімок. Для цьго у draw()
запишемо цикл for
, який буде переглядати усі зроблені знімки від першого до останнього і відображати саме останній.
let atlas;
let isPlaying = false;
let btnPlay;
let btnSnap;
let snaps = [];
function preload() {
atlas = createVideo("Atlas_Parkour.mp4");
}
function setup() {
createCanvas(640, 360);
atlas.size(width, height);
atlas.speed(1);
atlas.volume(0.1);
atlas.hide();
btnPlay = createButton("Відтворити");
btnPlay.mousePressed(btnPlayClick);
btnSnap = createButton("Знімок");
btnSnap.mousePressed(btnSnapClick);
}
function draw() {
background(94, 144, 114); // Viridian
image(atlas, 0, 0, width, height);
for (let i = 0; i < snaps.length; i++) {
image(snaps[i], 0, 0, width / 2, height / 2);
}
fill(255);
text(atlas.time(), width - 70, 20);
text(atlas.duration(), width - 70, 40);
}
function btnPlayClick() {
if (isPlaying) {
atlas.pause();
btnPlay.html("Відтворити");
} else {
atlas.play();
btnPlay.html("Пауза");
}
if (atlas.duration() === atlas.time()) {
atlas.stop();
btnPlay.html("Відтворити");
}
isPlaying = !isPlaying;
}
function btnSnapClick() {
snaps.push(atlas.get());
}
Переглядаємо Аналізуємо
У підсумку розташуємо зроблені знімки в окремих віконцях на полотні.
let atlas;
let isPlaying = false;
let btnPlay;
let btnSnap;
let snaps = [];
function preload() {
atlas = createVideo("Atlas_Parkour.mp4");
}
function setup() {
createCanvas(640, 360);
atlas.size(width, height);
atlas.speed(1);
atlas.volume(0.1);
atlas.hide();
btnPlay = createButton("Відтворити");
btnPlay.mousePressed(btnPlayClick);
btnSnap = createButton("Знімок");
btnSnap.mousePressed(btnSnapClick);
}
function draw() {
background(94, 144, 114); // Viridian
image(atlas, 0, 0, width, height);
let x = 0; (1)
let y = 0;
let w = width / 10;
let h = height / 10;
for (let i = 0; i < snaps.length; i++) { (2)
image(snaps[i], x, y, w, h);
x = x + w;
if (x >= width) {
x = 0;
y = y + h;
}
}
fill(255);
text(atlas.time(), width - 70, 20);
text(atlas.duration(), width - 70, 40);
}
function btnPlayClick() {
if (isPlaying) {
atlas.pause();
btnPlay.html("Відтворити");
} else {
atlas.play();
btnPlay.html("Пауза");
}
if (atlas.duration() === atlas.time()) {
atlas.stop();
btnPlay.html("Відтворити");
}
isPlaying = !isPlaying;
}
function btnSnapClick() {
snaps.push(atlas.get());
}
1 | У функції draw() щоразу ініціалізуємо змінні x , y , w , h . Значення, на які будуть покликатися ці змінні будуть визначати положення і розміри зображень на полотні. |
2 | За допомогою циклу for переглядаємо у масиві snaps усі його елементи - знімки-зображення, зроблені за допомогою кнопки Знімок. Використовуючи функцію image() розташовуємо знімки горизонтально у рядок, змінюючи x на ширину w зображеня. Коли x набуває значення ширини полотна або більше, присвоюємо x значення 0 , щоб зображення розташовувались із початку рядка, а значення y збільшуємо на висоту зображення, щоб переміститись нижче у новий рядок. |
Переглядаємо Аналізуємо
Вправа 61
Додати до застосунку кнопку, за допомогою якої можна видаляти знімки-зображення із полотна.
Оскільки відео розглядають як масив зображень, а останні є масивами пікселів, то змінюючи значення пікселів зображення можна впливати на зображення при відтворенні відео.
Отож, реалізуємо відтворення відео в інших барвах в порівнянні з оригінальним відео.
let atlas;
let isPlaying = false;
let btnPlay;
let btnSnap;
let snaps = [];
function preload() {
atlas = createVideo("Atlas_Parkour.mp4");
}
function setup() {
createCanvas(640, 360);
atlas.size(width, height);
atlas.speed(1);
atlas.volume(0.1);
atlas.hide();
btnPlay = createButton("Відтворити");
btnPlay.mousePressed(btnPlayClick);
btnSnap = createButton("Знімок");
btnSnap.mousePressed(btnSnapClick);
}
function draw() {
background(94, 144, 114); // Viridian
atlas.loadPixels(); (1)
for (let y = 0; y < atlas.height; y++) {
for (let x = 0; x < atlas.width; x++) {
const index = (x + y * atlas.width) * 4; (2)
const r = atlas.pixels[index]; (3)
const g = atlas.pixels[index + 1];
const b = atlas.pixels[index + 2];
const a = atlas.pixels[index + 3];
atlas.pixels[index + 0] = r; (4)
atlas.pixels[index + 1] = b;
atlas.pixels[index + 2] = r;
atlas.pixels[index + 3] = a;
}
}
atlas.updatePixels(); (5)
image(atlas, 0, 0, width, height);
let x = 0;
let y = 0;
let w = width / 2; // нові розміри для знімків-зображень
let h = height / 2;
for (let i = 0; i < snaps.length; i++) {
image(snaps[i], x, y, w, h);
x = x + w;
if (x >= width) {
x = 0;
y = y + h;
}
}
fill(255);
text(atlas.time(), width - 70, 20);
text(atlas.duration(), width - 70, 40);
}
function btnPlayClick() {
if (isPlaying) {
atlas.pause();
btnPlay.html("Відтворити");
} else {
atlas.play();
btnPlay.html("Пауза");
}
if (atlas.duration() === atlas.time()) {
atlas.stop();
btnPlay.html("Відтворити");
}
isPlaying = !isPlaying;
}
function btnSnapClick() {
snaps.push(atlas.get());
}
Нагадаємо особливості роботи з пікселями зображення, щоб зрозуміти, що відбувається у позначених рядках коду.
1 | Для зберігання усіх пікселів зображення бібліотека p5.js використовує вбудований масив pixels . Перш ніж отримати доступ до масиву pixels , дані про пікселі повинні бути завантажені у масив pixels за допомогою функції loadPixels() . |
2 | Масив pixels містить четвірки чисел у порядку R , G , B , A для кожного пікселя. Для роботи з пікселями безпосередньо на екрані, використовують ідею подання пікселів, за якої пікселі мають положення (x, y) у двовимірному просторі. Однак, пікселі масиву pixels мають лише один вимір, зберігаючи значення кольорів у лінійній послідовності. Тому використовуємо вираз index = (x + y * atlas.width) * 4 для обчислення індексу index пікселя у представленні двовимірного масиву. |
3 | Щоразу ініціалізуємо змінні r , g , b , a зі значеннями четвірки чисел для кожного пікселя. |
4 | Змінюємо значення складових кольору, зокрема, складову r (червоний колір) залишаємо без змін, для складової g (зелений колір) присвоюємо значення складової b (синій колір) і для складової b (синій колір) присвоюємо значення складової r (червоний колір). Значення прозорості a залишаємо без змін. |
5 | Після редагування даних масиву pixels необхідно виконати функцію updatePixels() , щоб оновити зміни у масиві. |
У разі потреби два цикли, які використовуються в коді застосунку для читання даних про пікселі, можна замінити одним циклом:
for (let i = 0; i < atlas.pixels.length; i = i + 4) {
const r = atlas.pixels[i + 0];
const g = atlas.pixels[i + 1];
const b = atlas.pixels[i + 2];
const a = atlas.pixels[i + 3];
atlas.pixels[i + 0] = r;
atlas.pixels[i + 1] = b;
atlas.pixels[i + 2] = r;
atlas.pixels[i + 3] = a;
}
Переглядаємо Аналізуємо
6.4.3. Ресурси
Корисні джерела
6.4.4. Контрольні запитання
Міркуємо Обговорюємо
-
Що таке «комп’ютерна анімація»?
-
Як захопити відео із вебкамери, використовуючи бібліотеку
p5.js
? -
Для яких цілей можна використати масив
pixels
при опрацюванні відеофайлу за допомогою бібліотекиp5.js
?
6.4.5. Практичні завдання
Відео, які можна використати для виконання практичних завдань:
Початковий
-
Створити застосунок, який призупиняє відтворення відео у певний момент. Використайте власний відеофайл або відео із запропонованого списку, в якому демонструється приземлення першого ступеня ракети-носія Falcon 9 на океанську платформу. Орієнтовний зразок роботи застосунку представлений в демонстрації.
-
Створити застосунок, який розміщує на полотні кілька копій відео і до кожної із них застосовує графічні ефекти у формі напівпрозорих рамок-фільтрів певних кольорів. Використайте власний відеофайл або відео із запропонованого списку, в якому демонструється краєвид планети Марс, відзнятий марсоходом Curiosity . Орієнтовний зразок роботи застосунку представлений в демонстрації.
Середній
-
Створити застосунок, який повідомляє колір пікселя над яким перебуває вказівник миші у вікні відтворення відео. Використайте власний відеофайл або відео з трояндою із запропонованого списку. Орієнтовний зразок роботи застосунку представлений в демонстрації.
-
Створити застосунок, в якому глядач може керувати відтворенням відео за допомогою кнопок. Окрім кнопок відтворення і паузи, необхідно створити кнопки для керування швидкістю відтворення: значення
2.0
відтворюватиме відео у два рази швидше, значення0.5
відтворюватиме із вдвічі меншою швидкістю, а значення1.0
- з нормальною швидкістю. Використайте власний відеофайл або відео із запропонованого списку, в якому демонструється рух клітин. Орієнтовний зразок роботи застосунку представлений в демонстрації.
Високий
-
Створити застосунок, який отримує доступ до пікселів зображення з відео і встановлює для них нові значення так, щоб відео відтворювалось у градаціях сірого кольору. Використайте власний відеофайл або відео із трояндою із запропонованого списку. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок, який розповідає певну історію за допомогою зображень, що змінюються. Зупинити/продовжити анімацію можна за допомогою натискання будь-якої клавіші на клавіатурі. Файли із зображеннями, які використовуються в ескізі, можна завантажити за покликанням. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Екстремальний
-
Створити застосунок, в якому пікселі з відео масштабуються і малюються у формі квадратів на полотні, утворюючи зображення у стилі піксель-арту. Використайте власний відеофайл або відео із запропонованого списку, в якому демонструється рух клітин. Орієнтовний зразок роботи застосунку представлений в демонстрації.
-
Створити застосунок, який відстежуватиме у відео ті пікселі, які були відзначені натисканням миші. Використайте власний відеофайл або відео із запропонованого списку про виконання кидків в баскетболі. Орієнтовний взірець роботи застосунку представлений в демонстрації.
6.5. Трансформації та моделювання руху
6.5.1. Основи моделювання руху
Застосунки з ефектом руху на полотні вже неодноразово створювалися у попередніх темах. Поглянемо ще раз на основні підходи у реалізації таких застосунків і спробуємо узагальнити принципи їх створення. |
Як відомо, код у тілі функції draw()
виконується у циклі аж до зупинки застосунку. Один такий прохід (ітерація) через тіло функції draw()
називається кадром, а кількість кадрів, які малюються щосекунди - частотою кадрів.
Пригадаємо, що це означає, на прикладі коду нижче.
function setup() {
createCanvas(200, 200)
}
function draw() {
background(220);
circle(100, 100, 50);
}
Щоразовий виклик функцій background()
і circle()
у тому порядку, як вони записані, буде окремим кадром, в якому буде малюватися полотно сірого кольору, а на полотні - коло білого кольору. За стандартним налаштуванням за секунду намалюється 60 кадрів, але за потреби це значення можна змінити за допомогою функції frameRate() . Наприклад, виклик функції frameRate(15)
встановлює швидкість 15 кадрів за секунду.
Оскільки кожен наступний кадр містить виклики функцій з однаковими аргументами, зображення фігури на полотні буде статичним. Щоб досягнути ефекту руху на полотні, необхідно використати змінні, значення яких будуть змінюватися у кожному кадрі.
Наприклад, використаємо для діаметра кола як аргумент у функції circle()
змінну d
, значення якої буде збільшуватися чи зменшуватися у кожному кадрі.
let d = 0; (1)
let outside = false; (2)
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
if (d > width) { (3)
outside = true;
} else if (d < 0) {
outside = false;
}
if (outside) { (4)
d -= 1;
} else {
d += 1;
}
circle(100, 100, d); (5)
}
1 | Ініціалізуємо змінну d зі значенням 0 , яка буде набувати цілих значень від нуля до значення ширини полотна й у зворотному порядку. |
2 | Ініціалізуємо змінну outside з логічним значенням false , яка буде змінювати своє значення на true , якщо діаметр кола d збільшиться до значення ширини полотна, а коли діаметр кола d зменшиться до нуля - на false . |
3 | У вказівці розгалуження if перевіряємо значення діаметра кола d : якщо діаметр кола став більшим за ширину полотна, встановлюємо true для outside , інакше, якщо діаметр кола став менше нуля, - false . |
4 | Перевіряємо за допомогою наступної вказівки розгалуження if значення outside і збільшуємо чи зменшуємо значення діаметра на одиницю відповідно. |
5 | Малюємо коло з діаметром d . |
Пункти 3-5 визначають дії, що відбувається в одній ітерації, в одному кадрі, в якому намальоване коло з певним значенням діаметра d
. Кожен наступний кадр містить також намальоване коло, але вже з іншим значенням d
.
Завдяки послідовній зміні кадрів створюється ілюзія наближення єдиного кола до спостерігача, а при досягненні межі полотна, - віддалення кола від спостерігача, хоча за лаштунками є не одна фігура, а по одній фігурі в кожному кадрі.
Переглядаємо Аналізуємо
Для створення горизонтального руху на полотні також можна використовувати змінні, що набуватимуть різних значень у кожному кадрі.
У наступному прикладі фігура переміщується зліва направо завдяки зміні значення x
у черговому кадрі:
let d = 50;
let x = -d / 2; (1)
let velocity = 0.5; (2)
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
circle(x, height / 2, d); (3)
x += velocity; (4)
}
1 | Ініціалізація змінної x зі значенням -d / 2 (-d / 2 = -50 / 2 = -25 ) - це початкове значення x -координати центру кола. Завдяки від’ємному значенню центр кола буде розташовуватись зліва від лівої межі полотна на відстані, яка дорівнює радіусу кола. |
2 | Змінна velocity визначає крок збільшення значення змінної x у кожному кадрі. Змінюючи значення velocity можна керувати швидкістю руху фігури на полотні (саме тому обрана для змінної назва velocity ). |
3 | Малювання кола з діаметром d у точці з x -координатою, що дорівнює значенню x , і y -координатою, що дорівнює height / 2 (значення цієї координати є однаковим для усіх кадрів, тому коло по вертикалі завжди знаходиться у центрі полотна). На полотні рух кола можна буде побачити, коли значення x -координати центра кола за модулем буде меншою за радіус кола. |
4 | Збільшення значення змінної x на величину velocity у кожному наступному кадрі. |
Переглядаємо Аналізуємо
Ілюзія руху на полотні створюється за допомогою послідовності зображень кола у кожному кадрі, які змінюють один одного. Сам ефект руху досягається через інерційність зору, коли наш мозок сприймає послідовність схожих кадрів-зображень як рух.
Після запуску коду, можна помітити, що коло повністю ховається за правою межею полотна, коли значення змінної x
перевищує його радіус. Величина x
, як і раніше, зростає, коло малюється, але все далі правої межі полотна і тому його не видно.
Змінимо код так, щоб коло з’являлося знову біля лівої межі полотна після зникнення за його правою межею.
let d = 50;
let x = -d / 2;
let velocity = 0.5;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
circle(x, height / 2, d);
if (x > width + d / 2) {
x = -d / 2;
}
x += velocity;
}
У кожній ітерації виконання функції draw()
величина x
порівнюється зі значенням ширини полотна width
плюс радіус кола (d / 2
). Якщо результат порівняння true
, x
присвоюється значення -d / 2
і коло малюється у точці з координатами (-25, height / 2)
, тобто зліва від лівої межі полотна. Далі процес повторюється.
Проілюструємо принцип роботи застосунку.

Додамо у застосунок можливість змінювати напрямок руху кола після досягнення межі полотна, тобто відбиватися від вертикальних меж полотна. Спочатку змінимо початкове значення x
-координати побудови центру кола (x = d = 50
), щоб коло починало свій рух вже на полотні.
let d = 50;
let x = d;
let velocity = 0.5;
let side = 1; (1)
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
circle(x, height / 2, d);
if (x > width - d / 2 || x < d / 2) { (2)
side = -side;
}
x += velocity * side; (3)
}
1 | Ініціалізуємо змінну side зі значенням 1 . Коло буде рухатися вправо при значенні side = 1 , а вліво - при side = -1 . |
2 | Перевіряємо умови відбивання кола від правої (x > width - d / 2 ) або від лівої (x < d / 2 ) меж полотна. |
3 | Враховуємо напрям у процесі зміни x -координати центру кола. |
Переглядаємо Аналізуємо
Щоб створити вертикальний рух і відбивання від горизонтальних меж полотна, слід змінювати y
-координату і перевіряти, чи виходить коло за верхню або нижню межі полотна.
let d = 50;
let y = d;
let velocity = 0.5;
let side = 1;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
circle(width / 2, y, d);
if (y > width - d / 2 || y < d / 2) {
side = -side;
}
y += velocity * side;
}
Переглядаємо Аналізуємо
Для реалізації руху на полотні одночасно у горизонтальному і вертикальному напрямках, необхідно враховувати зміну значень x
-координати та y
-координати та перевіряти вихід об’єкта за 4 межі. Цього разу не будемо використовувати окремої змінної, яка буде визначати напрямок (у попередніх прикладах це була змінна side
), а відразу використовуватимемо значення -1
для зміни руху в обидвох напрямках.
let d = 50;
let x = d;
let y = d;
let velocityX = 0.5; (1)
let velocityY = 0.8; (2)
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
circle(x, y, d);
if (x > width - d / 2 || x < d / 2) { (3)
velocityX = velocityX * -1;
}
if (y > height - d / 2 || y < d / 2) { (4)
velocityY = velocityY * -1;
}
x += velocityX;
y += velocityY;
}
1 | Ініціалізація змінної velocityX з початковим значенням швидкості під час руху горизонтально. |
2 | Ініціалізація змінної velocityY з початковим значенням швидкості під час руху вертикально. |
3 | Якщо коло, що рухається, виходить за вертикальні межі полотна, змінюємо крок зміни x -координати на протилежне за знаком значення множенням на -1 (якщо було додатне, то змінюється на від’ємне, і навпаки). Так коло почне рухатися горизонтально у протилежний бік. |
4 | Якщо коло, що рухається, виходить за горизонтальні межі полотна, змінюємо крок зміни y -координати на протилежне за знаком значення множенням на -1 (якщо було додатне, то змінюється на від’ємне, і навпаки). Так коло почне рухатися вертикально у протилежний бік. |
Переглядаємо Аналізуємо
У наведених прикладах лінійний рух об’єкта з однієї точки полотна в іншу відбувається завдяки обчисленням проміжних позицій об’єкта у кожному кадрі, а використання змінних на зразок velocity
, velocityX
і velocityY
дозволяє керувати тим, як швидко змінюються координати об’єкта.
Використовуючи з бібліотеки p5.js
функцію random() , яка повертає випадкове дробове десяткове число (число з рухомою крапкою), можна моделювати рух з випадковою траєкторію.
let d = 50;
let x = d;
let y = d;
let velocity = 2.5;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
circle(x, y, d);
x += random(-velocity, velocity);
y += random(-velocity, velocity);
x = constrain(x, d / 2, width - d / 2);
y = constrain(y, d / 2, height - d / 2);
}
У цьому прикладі для зміни позиції кола на полотні у кожному кадрі використовуються випадкові числа, згенеровані функцією random()
з діапазону (-2.5, 2.5)
, не включаючи значення верхньої межі.
Щоб коло не виходило за межі полотна можна скористатися вказівками розгалуження if
, як це продемонстровано у попередніх прикладах, або ще однією функцією з бібліотеки p5.js
з назвою constrain() , як у цьому прикладі.
Функція constrain()
обмежуватиме значення x
і y
за допомогою діапазонів (d / 2, width - d / 2)
і (d / 2, height - d / 2)
відповідно, що дозволить генерувати x
і y
в межах розмірів полотна.
Переглядаємо Аналізуємо
Щоб використовувати одну і ту ж послідовність випадкових чисел при кожному запуску застосунку, застосовуйте функцію randomSeed() . |
Враховуючи те, що p5.js
завжди відраховує час, що минув із запуску застосунку, можна моделювати рух з прив’язуванням до певної позначки часу за допомогою функції millis() , яка повертає значення лічильника часу у мілісекундах (1 секунда = 1000 мілісекунд).
Наприклад, у наступному застосунку коло буде сповільнювати свій рух на 1-й і 2-й секундах від запуску застосунку. Для кращої візуалізації етапів сповільнення додатково використаємо у коді функцію fill()
.
let d = 50;
let t1 = 1000;
let t2 = 2000;
let x = 0;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
let ms = millis();
if (ms > t2) {
fill(246, 174, 45); // Hunyadi yellow
x += 0.3;
} else if (ms > t1) {
fill(51, 101, 138); // Lapis Lazuli
x += 0.6;
} else {
x += 0.9;
}
circle(x, height / 2, d);
}
Переглядаємо Аналізуємо
Для моделювання руху, що відбувається за криволінійною траєкторією, варто пригадати знання з тригонометрії про функції синус і косинус. У бібліотеці p5.js
ці функції називаються sin() і cos() відповідно і повертають значення в діапазоні від -1
до 1
у радіанах.
Розглянемо застосунок, в якому рух відбувається за законом синуса.
let d = 50;
let angle = 0;
let amplitude = 30;
let velocity = 0.05;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
let y = sin(angle) * amplitude;
circle(width / 2, height / 2 + y, d);
angle += velocity;
}
Завдяки обчисленню у кожному кадрі значення y
-координати кола за допомогою синуса кута, утвореного точкою, яка рухається по колу, створюється ефект вертикального коливального руху. Оскільки значення, що повертає функції sin()
є невеликим, використовуємо множення на величину amplitude
.
Так y
-координата набуває значень орієнтовно в діапазоні від (-30, 30)
, що ми й бачимо на полотні - коло у черговому кадрі малюється все вище, а досягнувши верхньої межі, малювання відбувається з кожним наступним кадром нижче, і досягнувши нижньої межі, все повторюється знову. Значення кута angle
постійно зростає у кожному кадрі на величину velocity
.
Переглядаємо Аналізуємо
Доповнимо код застосунку, щоб коливальний рух здійснювали кілька кіл, розміщених поруч.
let d = 50;
let x = d / 2;
let angle = 0;
let amplitude = 30;
let velocity = 0.05;
function setup() {
createCanvas(400, 200);
}
function draw() {
background(220);
let y1 = sin(angle + 0.0) * amplitude;
let y2 = sin(angle + 0.3) * amplitude;
let y3 = sin(angle + 0.6) * amplitude;
let y4 = sin(angle + 0.9) * amplitude;
let y5 = sin(angle + 1.2) * amplitude;
let y6 = sin(angle + 1.5) * amplitude;
circle(x + d * 1, height / 2 + y1, d);
circle(x + d * 2, height / 2 + y2, d);
circle(x + d * 3, height / 2 + y3, d);
circle(x + d * 4, height / 2 + y4, d);
circle(x + d * 5, height / 2 + y5, d);
circle(x + d * 6, height / 2 + y6, d);
angle += velocity;
}
Для кращого розуміння того, що робить код, використаємо не цикл, а декілька змінних, які визначатимуть y
-координати для окремих шести кіл. Щоб коливання кожного з кіл було із певним зсувом, як аргументи для функції sin()
використовуємо доданки 0.0
, 0.3
, 0.6
, 0.9
, 1.2
і 1.5
.
У результаті отримуємо ефект поширення хвилі у просторі, форма якої відома під назвою синусоїда.
Переглядаємо Аналізуємо
Для моделювання руху по колу визначимо x
-координату, яка буде змінюватися по функції cos()
, і y
-координату - по функції sin()
відповідно. Для регулювання радіуса уявного кола, вздовж якого здійснюватиметься рух, знову використаємо змінну amplitude
.
let d = 50;
let angle = 0;
let amplitude = 30;
let velocity = 0.05;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
let x = cos(angle) * amplitude;
let y = sin(angle) * amplitude;
circle(width / 2 + x, height / 2 + y, d);
angle += velocity;
}
Переглядаємо Аналізуємо
Якщо у кожному кадрі змінювати значення amplitude
, то отримаємо ще один різновид руху - по спіралі.
let d = 50;
let angle = 0;
let amplitude = 0;
let velocity = 0.05;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
let x = cos(angle) * amplitude;
let y = sin(angle) * amplitude;
circle(width / 2 + x, height / 2 + y, d);
angle += velocity;
amplitude += velocity;
}
Переглядаємо Аналізуємо
6.5.2. Трансформації у 2D
За стандартним налаштуванням полотно використовує прямокутну систему координат з початком відліку у точці (0, 0)
в лівому верхньому куті, де вісь X
спрямована вправо, а вісь Y
- вниз.
Змінюючи систему координат полотна за стандартним налаштуванням, можна створювати різні її трансформації: переміщення, обертання та масштабування.
Переміщення
Одним зі способів позиціювання об’єктів на полотні є зміна координат самого полотна. Тобто, замість переміщення об’єкта на полотні на певну кількість пікселів у певному напрямку можна змістити координату (0, 0)
початку відліку системи координат полотна у цьому напрямку на це ж саме значення у пікселях - візуально результат буде однаковим.
Щоб здійснити таке переміщення системи координат, використовується функція translate() , яка переміщує систему координат по горизонталі й вертикалі за допомогою значень двох параметрів.
Візуально роботу функції показано на малюнку нижче. Для кращого розуміння процесу переміщення системи координат на малюнку окреслені межі полотна і фрагменти фігури, що виходять за ці межі при переміщенні.

Щоб продемонструвати процес переміщення координат в реальному застосунку, використаємо дві графічні піксельні сітки: першу - статичну, як зображення тла застосунку, другу - прозору, координати якої будемо змінювати за допомогою функції translate()
.


Отже, під’єднаємо обидва файли із сітками до ескізу, використавши функцію loadImage() , і розглянемо поданий нижче код.
У разі використання середовища Processing IDE , створіть каталог data у каталозі вашого ескізу і скопіюйте у нього файли із зображенням сіток. Шлях до зображень у функції loadImage() повинен бути відносним до HTML -файлу ескізу.
|
let grid; (1)
let gridOver; (2)
function preload() { (3)
grid = loadImage("grid_1000.png");
gridOver = loadImage("grid_1000_over.png");
}
function setup() {
createCanvas(1000, 1000);
noStroke();
}
function draw() {
image(grid, 0, 0); (4)
translate(200, 200); (5)
image(gridOver, 0, 0); (6)
fill(237, 43, 98); // Cerise
rect(300, 200, 300, 500); (7)
}
1 | Оголошуємо змінну grid , яка вказуватиме на завантажений графічний файл першої сітки, що буде статичною і відіграватиме роль тла застосунку. |
2 | Оголошуємо змінну gridOver , яка вказуватиме на завантажений графічний файл другої прозорої сітки. Цю сітку будемо переміщувати. |
3 | Щоб переконатися в тому, що зображення першої й другої графічних сіток повністю завантажені, використаємо функцію preload() . Усередині preload() функція loadImage() завантажує графічні файли та призначає їх змінним з назвами grid і gridOver відповідно. Тут варто бути уважним і правильно прописати шлях до зображень. |
4 | Функція image() розміщує завантажене зображення grid на полотні в точці з координатами (0, 0) (у початку системи координат полотна). |
5 | Переміщення початку координат в точку з координатами (200, 200) за допомогою функції translate() . |
6 | Функція image() розміщує завантажене зображення gridOver на полотні в точці з координатами (200, 200) (нова точка відліку системи координат полотна). |
7 | Малюємо прямокутник на полотні відносно нового початку системи координат. |
У результаті виконання застосунку отримаємо результат як на малюнку.

Повертаючись до візуального представлення роботи функції translate()
, в реальному застосунку він набуває вигляду як на малюнках нижче.

Якщо продовжувати малювати фігури, то на полотні вони будуть відображатися відносно нового початку системи координат.
let grid;
let gridOver;
function preload() {
grid = loadImage("grid_1000.png");
gridOver = loadImage("grid_1000_over.png");
}
function setup() {
createCanvas(1000, 1000);
noStroke();
}
function draw() {
image(grid, 0, 0);
translate(200, 200);
image(gridOver, 0, 0);
fill(237, 43, 98); // Cerise
rect(300, 200, 300, 500);
fill(240, 200, 8); // Jonquil
rect(100, 100, 100, 100);
}

Як бачимо, прямокутник, що створюється викликом функції rect(100, 100, 100, 100)
із шириною і висотою у 100 пікселів (третій і четвертий параметри) малюється відносно початку нової системи координат у точці з координатами (100, 100)
(перший і другий параметри).
Цікавий ефект спостерігається, коли у функції draw()
використовується більше одного виклику функції translate()
.
let grid;
let gridOver;
function preload() {
grid = loadImage("grid_1000.png");
gridOver = loadImage("grid_1000_over.png");
}
function setup() {
createCanvas(1000, 1000);
noStroke();
}
function draw() {
image(grid, 0, 0);
translate(200, 200); (1)
image(gridOver, 0, 0);
fill(237, 43, 98); // Cerise
rect(300, 200, 300, 500);
translate(300, 300); (2)
fill(240, 200, 8); // Jonquil
rect(100, 100, 100, 100);
}
1 | Виклик функції translate(200, 200) встановлює новий початок системи координат в точці з координатами (200, 200) . Перший прямокутник (Cerise ) малюється відносно нового початку системи координат у точці з координатами (200 + 300 = 500, 200 + 200 = 400) . |
2 | Виклик функції translate(300, 300) встановлює знову новий початок системи координат, але вже у точці з координатами (300, 300) відносно точки (200, 200) , яка була початком відліку системи координат до цього. Другий прямокутник (Jonquil ) малюється відносно вже нового початку системи координат у точці з координатами (200 + 300 + 100 = 600, 200 + 300 + 100 = 600) . |

Як бачимо, переміщення координат накопичуються у функції draw()
, але щоразу скидаються, коли draw()
починає свою наступну ітерацію.
Переміщення координат у функції draw() скидаються під час кожного повторного її виконання. Хоча у блоці draw() може бути кілька функцій translate() , результат роботи цих функцій не переноситься у наступний кадр. Для зберігання таких перетворень між кадрами можна використовувати глобальні змінні як аргументи таких перетворень.
|
Вправа 62
Поміркувати, як за допомогою виклику функції circle(0, 0, 100);
із зазначеними аргументами намалювати коло у центрі полотна.
Якщо необхідно намалювати одні фігури у новій системі координат, а інші - у системі координат за стандартним налаштуванням, також використовують кілька викликів функції translate()
. Алгоритм розв’язання цієї задачі за допомогою функції translate()
можна описати так:
-
Перемістити систему координат у точку з координатами, де будуть намальовані фігури.
-
Намалювати фігури.
-
Перемістити систему координат у точку з тими самими координатами, записаними зі знаком мінус.
Запишемо цей алгоритм за допомогою коду.
let grid;
let gridOver;
function preload() {
grid = loadImage("grid_1000.png");
gridOver = loadImage("grid_1000_over.png");
}
function setup() {
createCanvas(1000, 1000);
noStroke();
}
function draw() {
image(grid, 0, 0);
translate(200, 200); (1)
image(gridOver, 0, 0);
fill(237, 43, 98); // Cerise
rect(300, 200, 300, 500);
translate(-200, -200); (2)
fill(240, 200, 8); // Jonquil
rect(100, 100, 100, 100);
}
1 | Виклик функції translate(200, 200) встановлює новий початок системи координат в точці з координатами (200, 200) . У цій системі координат малюється графічна сітка gridOver і маленький прямокутник (Cerise ). |
2 | Виклик функції translate(-200, -200) , встановлює новий початок системи координат в точці з координатами (0, 0) , тобто у системі координат за стандартним налаштуванням. У цій системі координат малюється графічна сітка grid і великий прямокутник (Jonquil ). |

Обертання
Функція rotate() обертає систему координат полотна відносно початку координат - точки (0, 0)
.
Функція приймає один аргумент, зазначений у радіанах за стандартним налаштуванням.
Щоб значення кутів у функціях сприймалось у градусах, а не у радіанах, необхідно встановити (наприклад, у блоці setup() ) відповідний режим angleMode(DEGREES) за допомогою функції angleMode() .
|
У разі додатного значення аргументу обертання здійснюється за годинниковою стрілкою, інакше - проти. Як і для функції translate()
, результат роботи функції rotate()
також накопичується, але скидається щоразу, коли draw()
починає свою наступну ітерацію.
Результат роботи функції rotate()
проілюстровано на малюнку нижче.

Значення PI - це математична константа, яка дорівнює 3.14159265358979323846 і визначається відношенням довжини кола до його діаметра.
|
Застосуємо обертання системи координат в реальному застосунку, використовуючи код нижче.
let grid;
let gridOver;
function preload() {
grid = loadImage("grid_1000.png");
gridOver = loadImage("grid_1000_over.png");
}
function setup() {
createCanvas(1000, 1000);
noStroke();
}
function draw() {
image(grid, 0, 0);
rotate(PI / 10); (1)
// rotate(-PI / 12); (2)
image(gridOver, 0, 0);
fill(237, 43, 98); // Cerise
rect(300, 200, 300, 500);
fill(240, 200, 8); // Jonquil
rect(100, 100, 100, 100);
}
1 | Обертання системи координат відбувається навколо точки (0, 0) , яка позначає початок координат, за годинниковою стрілкою на кут PI / 10 = 0.3141592653589793 радіан = 18° . |
2 | Обертання системи координат відбувається навколо точки (0, 0) , яка позначає початок координат, проти годинникової стрілки на кут -PI / 12 = -0.2617993877991494 радіан = -15° . |

Більше можливостей для трансформацій з’являється, коли функції translate()
та rotate()
застосовуються разом.
Порядок, у якому функції translate() та rotate() виконуються, впливає на результат.
|
Наприклад, щоб запрограмувати обертання фігури навколо її центру у певному місці на полотні, слід дотримуватися такого алгоритму:
-
Застосувати функцію
translate()
для переміщення початку системи координат(0, 0)
в точку, де має бути намальована фігура. -
Викликати функцію
rotate()
з певним значенням кута повороту. -
Намалювати фігуру в точці
(0, 0)
.
Запишемо вищенаведений алгоритм за допомогою коду.
let grid;
let angle = 0; (1)
function preload() {
grid = loadImage("grid_1000.png");
}
function setup() {
createCanvas(1000, 1000);
noStroke();
}
function draw() {
image(grid, 0, 0);
translate(300, 300); (2)
rotate(angle); (3)
fill(240, 200, 8); // Jonquil
rect(0, 0, 100, 100); (4)
angle += 0.05; (5)
}
1 | Ініціалізуємо змінну angle з початковим значенням кута повороту. |
2 | Викликаємо функцію translate() - переміщуємо початок системи координат в точку (300, 300) . |
3 | Викликаємо функцію rotate() - повертаємо систему координат на поточне значення кута angle . |
4 | Малюємо прямокутник в точці (0, 0) - початку системи координат після її переміщення. |
5 | Збільшуємо значення кута angle на величну 0.05 . |
Оскільки за стандартним налаштуванням у функції rect()
перші два параметри встановлюють розташування верхнього лівого кута прямокутника, а третій і четвертий - його ширину і висоту відповідно, то обертання відбувається не відносно центру прямокутника, а навколо точки (0, 0)
системи координат після її переміщення.
Переглядаємо Аналізуємо
Вправа 63
Змінити код застосунку так, щоб прямокутник обертався навколо свого центру.
Щоб спростити малювання фігур відносно їх центру, використовуйте функції rectMode() , ellipseMode() та інші, які змінюють спосіб інтерпретації параметрів. Ще один спосіб - малювання фігури так, щоб точка початку системи координат (0, 0) опинилася в центрі фігури. Наприклад, у разі малювання прямокутника значення координат його центру можна записати як від’ємні значення половини ширини й висоти фігури відповідно: rect(-50, -50, 100, 100) .
|
Масштабування
Функція scale() дозволяє масштабувати координати полотна. Завдяки цьому можна збільшувати або зменшувати розміри об’єктів на полотні.
Якщо функцію scale()
викликають з одним аргументом, то він визначає на скільки відсотків масштабувати об’єкт. Наприклад, виклик scale(1.5)
збільшує усі фігури на полотні у півтора раза (150%
від початкового розміру), а scale(3)
- утричі (300%
від початкового розміру). А от використання scale(1)
не дасть жодного ефекту, тому що у всіх фігур залишиться розмір 100%
від початкового розміру.
Якщо функцію scale()
викликають з двома аргументами, то вони відповідають за масштабування об’єктів по горизонталі й по вертикалі відповідно.
Повторні виклики функції scale()
у блоці draw()
посилюють ефект масштабування, але на початку чергової ітерації у draw()
масштабування скидається.
Для кращого розуміння процесу масштабування системи координат на малюнку нижче окреслені межі полотна і фрагменти фігури, що виходять за ці межі при масштабуванні.

Щоб зменшити розмір фігур удвічі, використовують значення масштабу 0.5
(50%
від початкового розміру).
let grid;
function preload() {
grid = loadImage("grid_1000.png");
}
function setup() {
createCanvas(1000, 1000);
noStroke();
}
function draw() {
image(grid, 0, 0);
scale(0.5);
fill(237, 43, 98); // Cerise
rect(300, 200, 300, 500);
}

Використовуючи scale()
з від’ємними значеннями аргументів можна перевертати/віддзеркалювати систему координат.
Вправа 64
Застосувати функцію scale(-1, 1)
для дзеркального відображення об’єктів на полотні горизонтально.
6.5.3. Трансформації у 3D
WebGL
Бібліотека p5.js
є потужним інструментом для створення у вебпереглядачі 2D
-графіки. Побудова зображень на полотні у двовимірному просторі відбувається у системі координат з двома осями X
та Y
, використовуючи значення координат (x, y)
для позначення конкретного пікселя зображення.
Водночас p5.js
містить засоби для створення 3D
-ескізів. У цьому разі до двох вищезгаданих осей додається третя вісь Z
, яка визначає глибину будь-якої заданої точки, створюючи ілюзію тривимірного реального простору. Для того, щоб вказати тривимірні координати точки, координати задаються в порядку (x, y, z)
.
Прикладом простої ілюзії тривимірного простору може слугувати прямокутник, який повільно збільшується в центрі полотна і, ніби, рухається до глядача.
let d = 0;
function setup() {
createCanvas(200, 200);
rectMode(CENTER);
}
function draw() {
background(255);
stroke(34, 111, 84); // Dark spring green
fill(244, 240, 187); // Lemon chiffon
rect(width / 2, height / 2, d, d);
d += 1;
}
Переглядаємо Аналізуємо
Як відомо, у 2D
-режимі початок системи координат міститься в точці (0, 0)
, яка розташована у верхньому лівому куті полотна.
Для створення ескізів у 3D
-режимі бібліотека p5.js
використовує спеціальний режим WEBGL, в якому початок системи координат міститься в точці (0, 0, 0)
, що розташована в центрі полотна.
Щоб запам’ятати, в яких напрямках спрямовані осі у WEBGL -режимі, використовуйте правило лівої руки: направте вказівний палець праворуч (вісь X ), три пальці вниз (вісь Y ), тоді великий палець автоматично вкаже на вас (вісь Z ).
|

Щоб відобразити ескіз за допомогою WebGL
, все, що нам потрібно зробити, це додати третій аргумент, константу WEBGL
, до виклику функції createCanvas()
. Отож, перетворимо попередній приклад у 3D
-ескіз.
let z = 0;
function setup() {
createCanvas(200, 200, WEBGL); (1)
rectMode(CENTER);
}
function draw() {
background(255);
smooth(); (2)
stroke(34, 111, 84); // Dark spring green
fill(244, 240, 187); // Lemon chiffon
translate(0, 0, z); (3)
rect(0, 0, 20, 20); (4)
z += 1;
}
1 | Попереджуємо p5.js , що необхідно отримати 3D -ескіз. Це досягається додаванням третього аргументу WEBGL до виклику функції createCanvas() . |
2 | За допомогою функції smooth() вмикаємо режим малювання фігур зі згладженими краями, який у WEBGL вимкнений. Це покращить якість зображення, розміри якого змінюються. |
3 | Для того, щоб використовувати тривимірні координати (x, y, z) для прямокутника, використовуємо функцію translate() . Завдяки черговому збільшенню значення z -координати, прямокутник буде рухатися вздовж осі Z , наближаючись до глядача. |
4 | Хоча координати прямокутника мають значення (0, 0) , він розташований у центрі полотна, оскільки увімкнений режим WebGL , в якому початок системи координат міститься у центрі полотна, а не у верхньому лівому куті. |
При русі прямокутника вздовж осі Z
, він візуально збільшується. Однак, у виклику rect(0, 0, 20, 20)
параметри ширини й висоти залишаються незмінними. За стандартним налаштуванням у режимі WEBGL
бібліотека p5.js
використовує перспективну проєкцію, де об’єкти, що знаходяться далеко від глядача, здаються меншими, а ближче - більшими, тобто візуальний розмір фігури залежить від її відстані до глядача. Це створює ефект глибини та просторовості.
Вправа 65
Змінити код застосунку для кола, яке віддаляється від глядача.
Переміщення
Як відомо, функція translate() переміщує початок координат у заданому напрямку. Все, що намальовано після виклику translate()
, буде розташовано відносно цієї точки. У контексті тривимірного простору translate()
приймає аргументи для значень x
, y
та z
.
Розглянемо застосунок із прикладом використання translate()
для розміщення у тривимірному просторі паралелепіпедів, які будуються за допомогою функції box() .
function setup() {
createCanvas(300, 300, WEBGL);
rectMode(CENTER);
}
function draw() {
background(245);
stroke(34, 111, 84); // Dark spring green
fill(244, 240, 187); // Lemon chiffon
// по центру
translate(0, 0, 0);
box();
translate(-100, 0, 0);
box();
translate(200, 0, 0);
box(60);
// вгорі
translate(-200, -100, 0);
box(20, 20);
translate(100, 0, 0);
box(50, 30, 30);
translate(100, 0, 0);
box(45);
// внизу
translate(-200, 200, 0);
box(20, 20, 75);
translate(100, 0, -100);
box(50, 20, 100);
fill(255, 137, 102); // Coral
translate(100, 0, 100);
box();
}

Алгоритм побудови у цьому разі можна описати так:
-
Переносимо за допомогою функції
translate()
початок координат у місце побудови3D
-фігури, враховуючи попереднє значення початку системи координат. -
Будуємо фігуру за допомогою функції
box()
, за потреби викликавши її з аргументами. Для візуалізації конкретного паралелепіпеда використовуємо функціюfill()
. -
Переходимо до першого пункту.
Для кращого розуміння процесу переміщення системи координат у 3D -просторі, скористайтеся інтерактивною демонстрацією за покликанням. Використовуючи повзунки для зміни положення куба у просторі, ви побачите, як він рухається вздовж кожної осі.
|
Переглядаємо Аналізуємо
Обертання
Третій вимір також відкриває можливість обертання навколо різних осей.
Обертання фігури за годинниковою стрілкою чи проти у двовимірному просторі за допомогою функції rotate() , яка переорієнтовує все, що намальовано після її виклику, є насправді обертанням навколо осі Z
(тобто обертання у площині самого полотна).
Вісь Z - вісь, навколо якої за стандартним налаштуванням відбувається обертання у двовимірному просторі.
|
Обертатися можна також навколо осі X
або Y
за допомогою функцій rotateX() та rotateY() , кожна з яких потребує режиму WEBGL
. Функція rotateZ() також існує і є еквівалентом rotate()
.
Кожна із цих функцій приймає один аргумент, який визначає кут повороту. За стандартним налаштуванням p5.js
очікує, що значення кута буде у радіанах. Для визначення кута в радіанах використовуються числа від 0
до TWO_PI
.
Щоб використовувати значення у градусах, необхідно перетворити градуси в радіани за допомогою функції radians() або встановити для застосунку режим angleMode(DEGREES)
.
Розглянемо обертання навколо різних осей паралелепіпедів з попереднього прикладу.
function setup() {
createCanvas(300, 300, WEBGL);
rectMode(CENTER);
}
function draw() {
background(245);
stroke(34, 111, 84); // Dark spring green
fill(244, 240, 187); // Lemon chiffon
// по центру
translate(0, 0, 0);
rotateY(frameCount * 0.01); (1)
box();
translate(-100, 0, 0);
rotateX(frameCount * 0.01); (2)
box();
translate(200, 0, 0);
box(60);
// вгорі
translate(-200, -100, 0);
box(20, 20);
translate(100, 0, 0);
box(50, 30, 30);
translate(100, 0, 0);
box(45);
// внизу
translate(-200, 200, 0);
box(20, 20, 75);
translate(100, 0, -100);
box(50, 20, 100);
fill(255, 137, 102); // Coral
translate(100, 0, 100);
box();
}
1 | Обертання навколо осі Y . Значення кута повороту формується за допомогою frameCount - системної змінної, яка накопичує кількість кадрів, що пройшли з моменту запуску застосунку. |
2 | Обертання навколо осі X . |
Переглядаємо Аналізуємо
Вправа 66
Додати у вищенаведений код обертання навколо осі Z
.
Для кращого розуміння процесу обертання на кожній осі, скористайтеся інтерактивною демонстрацією за покликанням. Використовуючи повзунки для зміни значення кута повороту (у градусах), ви побачите, як тор обертається навколо кожної осі. |
Переглядаємо Аналізуємо
Масштабування
Для зміни розмірів фігур у тривимірному просторі використовується функція scale() , яка змінює розмір того, що намальовано після її виклику. Як і описані вище функції трансформації, вона приймає аргументи для значень x
, y
і z
.
Для кращого розуміння процесу зміни розмірів у просторі, скористайтеся інтерактивною демонстрацією за покликанням. Використовуючи повзунки для зміни значення масштабування, ви побачите, як паралелепіпед масштабується вздовж кожної осі. |
Переглядаємо Аналізуємо
6.5.4. Матриця трансформації
Будь-які елементи, які додаються до ескізу, розташовуються відносно початку системи координат. Кожна нова функція трансформації впливає на положення або орієнтацію початку координат, а на кожну нову трансформацію впливає будь-яка, яка їй передує.
Для того, щоб відстежувати переміщення, обертання та масштабування і відображати фігури відповідно до цих трансформацій, бібліотека p5.js
використовує матрицю трансформації.
Матриця - це таблиця чисел (масив), які розміщені у рядках і у стовпцях. |
У матриці трансформації зберігається інформація, пов’язана із системою координат, а сама матриця трансформації використовується для опису орієнтації полотна.
Коли застосовується переміщення, обертання чи масштабування, матриця трансформації змінюється - в ній оновлюється інформація щодо виконання будь-якої серії перетворень.
Проілюструємо концепцію матриці трансформації на прикладі двох ескізів, в яких у тривимірному просторі навколо відповідних точок початку координат обертаються два прямокутники відповідно.
Отож, змусимо перший прямокутник обертатися навколо осі Z
у верхньому лівому куті полотна.
let a = 0;
function setup() {
createCanvas(200, 200, WEBGL);
rectMode(CENTER);
noStroke();
}
function draw() {
background(255);
translate(-50, -50, 0); (1)
rotateZ(a); (2)
fill(152, 95, 153); // Pomp and Power
rect(0, 0, 50, 50); (3)
a += 0.01; (4)
}
1 | Переміщуємо систему координат з центру полотна (у 3D -просторі початок координат у центрі полотна) у точку (-50, -50, 0) , яка розташована у верхньому лівому куті полотна. |
2 | Повертаємо систему координат на кут a . |
3 | Малюємо прямокутник відповідних розмірів у точці початку координат (0, 0 ). |
4 | Збільшуємо значення кута на величину 0.01 . |
Описані вище кроки виконуються у блоці draw()
в циклі. Завдяки глобальній змінній a
, значення якої в кожній ітерації у draw()
збільшується на величину 0.01
і увімкненому режиму rectMode(CENTER)
прямокутник обертається навколо свого центру.
Переглядаємо Аналізуємо
Другий прямокутник змусимо обертатися навколо осі Y
у нижньому правому куті полотна.
let b = 0;
function setup() {
createCanvas(200, 200, WEBGL);
rectMode(CENTER);
noStroke();
}
function draw() {
background(255);
translate(50, 50); (1)
rotateY(b); (2)
fill(239, 121, 138); // Bright pink (Crayola)
rect(0, 0, 50, 50); (3)
b += 0.02; (4)
}
1 | Переміщуємо систему координат з центру полотна (у 3D -просторі початок координат у центрі полотна) у точку (50, 50, 0) , яка розташована у нижньому правому куті полотна. |
2 | Повертаємо систему координат на кут b . |
3 | Малюємо прямокутник відповідних розмірів у точці початку координат (0, 0 ). |
4 | Збільшуємо значення кута на величину 0.02 . |
Як і у попередньому випадку, описані кроки виконуються у блоці draw()
в циклі. Завдяки глобальній змінній b
, значення якої в кожній ітерації у draw()
збільшується на величину 0.02
та увімкненому режиму rectMode(CENTER)
прямокутник обертається навколо свого центру.
Переглядаємо Аналізуємо
Тепер об’єднаймо два ескізи в один.
let a = 0;
let b = 0;
function setup() {
createCanvas(200, 200, WEBGL);
rectMode(CENTER);
noStroke();
}
function draw() {
background(255);
translate(-50, -50, 0);
rotateZ(a);
fill(152, 95, 153); // Pomp and Power
rect(0, 0, 50, 50);
a += 0.01;
translate(50, 50);
rotateY(b);
fill(239, 121, 138); // Bright pink (Crayola)
rect(0, 0, 50, 50);
b += 0.02;
}
Переглядаємо Аналізуємо
Результат об’єднання двох ескізів в єдиний не є тим, на який ми очікували. Як бачимо, виклик функції rotateZ(a)
впливає на всі фігури, намальовані після виклику, тому обидва прямокутники обертаються навколо центру першого прямокутника.
Поміркуємо, як це можна виправити.
Отож, щоб скасувати обертання другого прямокутника навколо осі Z
, перед переміщенням системи координат для другого прямокутника можна викликати функцію rotateZ(-a)
.
А для розміщення другого прямокутника у правому нижньому куті, необхідно замінити виклик функції translate(50, 50)
на translate(100, 100)
.
Переглядаємо Аналізуємо
На розглянутому вище прикладі ми переконалися, що через ефект накопичування трансформацій можна отримати неочікуваний результат.
За допомогою викликів функцій трансформацій з від’ємними аргументами (як-от rotateZ(-a)
) систему координат можна повернути у попередній стан, а отже, відновити матрицю трансформацій до початкового стану, щоб окремі фігури могли діяти незалежно одна від одної.
Однак, у разі виконання складніших операцій із системою координат, скасування попередніх операцій є не дуже оптимальним рішенням.
Система координат відновлюється до початкового стану (точка початку у верхньому лівому куті полотна для 2D -режиму чи у центрі полотна для 3D -режиму) без ефектів обертання і масштабування щоразу, коли починає виконуватися тіло функції draw() .
|
Для керування перебігом трансформацій бібліотека p5.js
використовує спеціальні функції push() та pop() .
Функція push()
зберігає поточні налаштування стилю малювання та трансформацій (зберігає поточний стан матриці трансформацій), тоді як pop()
відновлює ці налаштування. Тобто, можна змінити стиль й параметри трансформацій, а потім повернутися до того, що було. На практиці це означає, що тепер ми можемо просто переміщувати об’єкт туди, куди хочемо, не запам’ятовуючи, де розміщена наша система координат.
Ці функції завжди використовуються разом, а спільним результатом їхньої роботи є те, що будь-які трансформації або зміни стилю, які відбуваються між push()
і pop()
, ізольовані в тій частині коду, яку вони обмежують.
Якщо не використовувати push()
і pop()
, то необхідно відстежувати будь-які трансформації, які вже відбулися, а це може стати складним завданням. До речі, у цьому ми вже неодноразово переконувалися на прикладі застосунків, в яких виконувалися трансформації для різних елементів.
Цікавимось Додатково
Тепер повернемось до об’єднаного ескізу і перепишемо його код із застосуванням функцій push()
і pop()
.
let a = 0;
let b = 0;
function setup() {
createCanvas(200, 200, WEBGL);
rectMode(CENTER);
noStroke();
}
function draw() {
background(255);
push(); (1)
translate(-50, -50, 0); (2)
rotateZ(a); (3)
fill(152, 95, 153); // Pomp and Power
rect(0, 0, 50, 50); (4)
a += 0.01;
pop(); (5)
push(); (6)
translate(50, 50); (7)
rotateY(b); (8)
fill(239, 121, 138); // Bright pink (Crayola)
rect(0, 0, 50, 50); (9)
b += 0.02;
pop(); (10)
}
Результат виконання застосунку той самий, але використання функцій push()
і pop()
добре структурує фрагменти коду, в яких відбуваються трансформації. Проаналізуємо код в частині застосування в ескізі функцій push()
і pop()
.
1 | Зберігаємо поточну матрицю трансформацій. Тепер матриця трансформацій містить інформацію про те, що початок координат в точці (0, 0) у верхньому лівому куті полотна. |
2 | Переміщуємо початок системи координат (0, 0) у точку (-50, -50, 0) . |
3 | Повертаємо навколо осі Z перший прямокутник на кут a за годинниковою стрілкою відносно точки початку координат. |
4 | Малюємо перший прямокутник відносно початку системи координат. |
5 | Відновлюємо матрицю трансформацій з кроку 1, щоб на другий прямокутник не впливали кроки 2 і 3. |
6 | Зберігаємо поточну матрицю трансформацій. Тепер матриця трансформацій містить інформацію про те, що початок координат в точці (0, 0) у верхньому лівому куті полотна. |
7 | Переміщуємо початок системи координат (0, 0) у точку (50, 50, 0) . |
8 | Повертаємо навколо осі Y другий прямокутник на кут b за годинниковою стрілкою відносно точки початку координат. |
9 | Малюємо другий прямокутник відносно початку системи координат. |
10 | Відновлюємо матрицю трансформацій з кроку 6, щоб на наступні фігури, якщо такі будуть, не впливали кроки 7 і 8. |
Оскільки у нашому застосунку обертаються лише дві фігури, застосовувати функції push()
і pop()
навколо другого прямокутника необов’язково. Проте, використання функцій push()
і pop()
до і після трансформацій для усіх фігур є гарним правилом, оскільки фігури в цьому разі можна розглядати як незалежні сутності.
Як відомо, кількість викликів як push()
, так і pop()
повинна бути однаковою, але вони не завжди повинні йти один за одним.
Якщо взяти з нашого застосунку лише пари викликів функцій push()
і pop()
, то вони будуть записані послідовно одна за одною.
...
push();
// трансформація першого прямокутника
pop();
push();
// трансформація другого прямокутника
pop();
...
Розглянемо код 2D
-ескізу в якому застосування функцій push()
і pop()
мають вкладену структуру. Спочатку матриця трансформації має початковий стан (позначимо стан 1), в якому початок (0, 0)
системи координат розташований в лівому верхньому куті полотна.
function setup() {
createCanvas(200, 200);
rectMode(CENTER);
ellipseMode(CENTER);
}
function draw() {
background(245);
push(); (1)
translate(width / 2, height / 2);
rotate(radians((frameCount * 2) % 360));
fill(19, 70, 17); // Pakistan green
rect(0, 0, width / 2, 5);
push(); (2)
translate(width / 4, 0);
fill(61, 163, 93); // Pigment green
circle(0, 0, 30);
pop(); (3)
fill(232, 252, 207); // Nyanza
circle(0, 0, 10);
pop(); (4)
}
1 | Зберігаємо стан 1 матриці трансформації. Далі переміщуємо систему координат в центр полотна у точку (width / 2, height / 2 ), обертаємо та малюємо прямокутник відносно точки (0, 0 ) початку системи координат. Тепер матриця трансформації має стан 2. |
2 | Зберігаємо стан 2 матриці трансформації. Далі переміщуємо систему координат в точку (width / 4, 0) і малюємо велике коло відносно точки (0, 0 ) початку системи координат. Тепер матриця трансформації має стан 3. |
3 | Відновлюємо стан 2 матриці трансформації. Малюємо мале коло. Мале коло і прямокутник перебувають в одній системі координат. |
4 | Відновлюємо стан 1 (початковий стан) матриці трансформації. |
Переглядаємо Аналізуємо
Вправа 67
Закоментуйте пункти 2 і 3 та внесіть зміни в коді, щоб результат виконання застосунку відповідав представленому у демонстрації.
Годинник
Розглянемо застосування матриці трансформації на прикладі створення годинника з циферблатом.
Спочатку дізнаємось і надрукуємо на екрані поточний час (години, хвилини, секунди). Будемо відштовхуватися від наступного початкового коду:
function setup() {
createCanvas(400, 400);
}
function draw() {
background(255);
let hr = hour(); (1)
let mn = minute();
let sc = second();
fill(0);
noStroke();
textSize(50);
text(correctTime(hr) + ":" + correctTime(mn) + ":" + correctTime(sc), 100, 210); (3)
}
function correctTime(t) { (2)
if (t >= 0 && t < 10) {
t = "0" + t;
}
return t;
}
Переглядаємо Аналізуємо
Отже, трохи пояснень до коду.
1 | Щоб дізнатися поточний час, використаємо три функції для роботи з часом: hour() , minute() , second() . Усі функції отримують значення системного часу на комп’ютері. |
2 | Опишемо функцію correctTime() , яка приймає одне значення (годин або хвилин або секунд) і повертає коректне значення, наприклад: 19:53:4 буде перетворений у 19:53:04 , 16:4:25 у 16:04:25 , 8:11:44 у 08:11:44 тощо. |
3 | Виклики функції correctTime() для поточних значень годин, хвилин і секунд та друк на екрані часу у форматі hh:mm:ss за допомогою функції text() . |
Тепер перейдемо до створення циферблата в центрі полотна.
Використаємо функцію translate() для встановлення центру координат в центрі полотна, де намалюємо точку чорного кольору за допомогою функції point() розміру strokeWeight() і відредагуємо значення координат у функції text()
.
Продемонструємо вказані зміни у тілі функції draw()
.
function draw() {
background(255);
translate(width / 2, height / 2);
let hr = hour();
let mn = minute();
let sc = second();
strokeWeight(8);
stroke(0);
point(0, 0);
fill(0);
noStroke();
textSize(12);
text(correctTime(hr) + ":" + correctTime(mn) + ":" + correctTime(sc), -20, 180);
}
Переглядаємо Аналізуємо
Тепер спробуємо візуалізувати перебіг годин, хвилин і секунд часу.
Відомо, що секундна стрілка проходить повне коло в 360°
. Використаємо малювання дуги відповідно плину секунд.
Щоб значення кутів у функціях сприймалось у градусах, а не у радіанах, у функції setup() встановимо відповідний режим angleMode(DEGREES) за допомогою функції angleMode() .
|
Для цього візьмемо функцію arc() , яка малює дугу, і функцію map() , що перетворить діапазон секунд (0, 60)
у діапазон значень кута в градусах (0, 360)
для використання у функції arc()
.
Фрагмент коду для плину секунд буде наступним:
stroke(231, 29, 54); (1)
noFill(); (2)
let secondsAngle = map(sc, 0, 60, 0, 360); (3)
arc(0, 0, 300, 300, 0, secondsAngle); (4)
1 | Колір лінії дуги. |
2 | Вимикання зафарбовування, щоб малювалась лише лінія дуги, а не сегмент. |
3 | Ініціалізація змінної secondsAngle , яка буде містити значення кута із функції map() . |
4 | Малювання лінії дуги. |
Значення кутів для функції arc() відраховуються за годинниковою стрілкою від позначки годинної стрілки на 3 години. Тому, щоб циферблат був як у справжнього годинника, його необхідно повернути проти годинникової стрілки на 90° (-90° ) за допомогою функції rotate() .
|
Остаточний код на цю мить для плину секунд буде таким:
function setup() {
createCanvas(400, 400);
angleMode(DEGREES);
}
function draw() {
background(255);
translate(width / 2, height / 2);
rotate(-90);
let hr = hour();
let mn = minute();
let sc = second();
strokeWeight(8);
stroke(231, 29, 54);
noFill();
let secondsAngle = map(sc, 0, 60, 0, 360);
arc(0, 0, 300, 300, 0, secondsAngle);
stroke(0);
point(0, 0);
rotate(90);
fill(0);
noStroke();
textSize(12);
text(
correctTime(hr) + ":" + correctTime(mn) + ":" + correctTime(sc),
-20,
180
);
}
function correctTime(t) {
if (t >= 0 && t < 10) {
t = "0" + t;
}
return t;
}
Переглядаємо Аналізуємо
Реалізація плину хвилин буде аналогічною як і для секунд. У визначенні кута для годинного плину часу використаємо 24
-годинний формат.
Фактично, годинна стрілка проходить по колу біля позначки 12 годин два рази на добу (12
година дня і 00
годин - дванадцята година ночі), тому для значення годин обираємо діапазон (0, 12)
і перетворюємо його у діапазон значень кута (0, 360)
для годинного плину часу. Для визначення поточного часу у 24
-годинному форматі від значення, яке повертає функція hour()
, беремо остачу від ділення на 12
за допомогою оператора %
.
Отже, додамо фрагменти коду для плину хвилин і годин.
stroke(200);
let minutesAngle = map(mn, 0, 60, 0, 360);
arc(0, 0, 280, 280, 0, minutesAngle);
stroke(0);
let hoursAngle = map(hr % 12, 0, 12, 0, 360);
arc(0, 0, 260, 260, 0, hoursAngle);
Переглядаємо Аналізуємо
Додамо до нашого годинника стрілки, які будуть рухатися в парі із дугами секунд, хвилин і годин.
Стрілки побудуємо за допомогою функції line() .
Для того, щоб кожна стрілка (окремо від інших стрілок) малювалася відносно центру циферблата, використаємо функції push() і pop() .
Повороти на кут секундної, хвилинної та годинної стрілок буде здійснювати функція rotate() .
Фрагмент коду, який буде реалізовувати обертання стрілок буде
push();
rotate(secondsAngle);
stroke(231, 29, 54);
line(0, 0, 100, 0);
pop();
push();
rotate(minutesAngle);
stroke(200);
line(0, 0, 75, 0);
pop();
push();
rotate(hoursAngle);
stroke(0);
line(0, 0, 50, 0);
pop();
а остаточний код застосунку матиме вигляд:
function setup() {
createCanvas(400, 400);
angleMode(DEGREES);
}
function draw() {
background(255);
translate(width / 2, height / 2);
rotate(-90);
let hr = hour();
let mn = minute();
let sc = second();
strokeWeight(8);
stroke(231, 29, 54);
noFill();
let secondsAngle = map(sc, 0, 60, 0, 360);
arc(0, 0, 300, 300, 0, secondsAngle);
stroke(200);
let minutesAngle = map(mn, 0, 60, 0, 360);
arc(0, 0, 280, 280, 0, minutesAngle);
stroke(0);
let hoursAngle = map(hr % 12, 0, 12, 0, 360);
arc(0, 0, 260, 260, 0, hoursAngle);
push();
rotate(secondsAngle);
stroke(231, 29, 54);
line(0, 0, 100, 0);
pop();
push();
rotate(minutesAngle);
stroke(200);
line(0, 0, 75, 0);
pop();
push();
rotate(hoursAngle);
stroke(0);
line(0, 0, 50, 0);
pop();
stroke(0);
point(0, 0);
rotate(90);
fill(0);
noStroke();
textSize(12);
text(
correctTime(hr) + ":" + correctTime(mn) + ":" + correctTime(sc),
-20,
180
);
}
function correctTime(t) {
if (t >= 0 && t < 10) {
t = "0" + t;
}
return t;
}
Переглядаємо Аналізуємо
Цікавимось Додатково
Дерево
Розглянемо застосування матриці трансформації на прикладі побудови стохастичного фракталу - дерева.
Переглядаємо Аналізуємо
Алгоритм побудови дерева такий:
-
Намалювати лінію.
-
Наприкінці лінії повернути на певний кут ліворуч й намалювати коротку лінію і водночас повернути праворуч на певний кут і намалювати коротку лінію.
-
Повторити крок 2 для нових ліній.

Як бачимо, кожна нова гілка дерева повертається відносно попередньої гілки, яка повертається відносно усіх її попередніх гілок. Такі повороти реалізуємо за допомогою функції rotate()
.
Функція rotate() , за стандартним налаштуванням виконує повороти відносно точки початку координат. Тому точку початку координат необхідно щоразу переносити у кінець поточної гілки-лінії.
|
Розпочнемо малювання першої гілки - стовбура дерева. Використаємо функцію translate()
для зміщення початку координат в нижню частину полотна, звідки буде «рости» корінь дерева, і проведемо лінію догори за допомогою line()
.
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
translate(width / 2, height); (1)
line(0, 0, 0, -100); (2)
}
1 | Перенесення точки початку системи координат у (width / 2, height) . |
2 | Малювання лінії від нового початку координат вгору на 100 пікселів. |
Після того, як корінь буде намальований, потрібно перемістити початок координат у кінець лінії та виконати поворот, щоб намалювати наступну гілку.

Вищенаведену ілюстрацію запишемо у вигляді коду.
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
translate(width / 2, height);
line(0, 0, 0, -100);
translate(0, -100); (1)
rotate(PI / 6); (2)
line(0, 0, 0, -100); (3)
}
1 | Перенесення точки початку системи координат у (0, -100) . |
2 | Поворот праворуч на кут beta заданий в радіанах PI / 6 (30° ). |
3 | Малювання лінії від нового початку координат вгору на 100 пікселів. |
Тепер, побудуємо гілку, яка «росте» ліворуч. Для цього використаємо функції:
-
push()
- зберігає стан матриці трансформації перед поворотом; -
pop()
- відновлює стан матриці трансформації, щоб намалювати гілку ліворуч.
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
translate(width / 2, height); // корінь
line(0, 0, 0, -100);
translate(0, -100);
push();
rotate(PI / 6); // гілка праворуч
line(0, 0, 0, -100);
pop();
rotate(-PI / 6); // гілка ліворуч
line(0, 0, 0, -100);
}
Виклик функції line()
тут можна розглядати як «окрему гілку» дерева і додавати щоразу все більше «гілок». Але в цьому разі код стане громіздким і неймовірно складним, тому створимо рекурсивну функцію branch()
, яка замінить прямі виклики line()
.
function branch() {
line(0, 0, 0, -100); (1)
translate(0, -100); (2)
push();
rotate(PI / 6); (3)
branch();
pop();
push();
rotate(-PI / 6); (4)
branch();
pop();
}
1 | Намалювати гілку. |
2 | Перемістити початок координат. |
3 | Повернути праворуч і викликати рекурсивну функцію branch() . |
4 | Повернути ліворуч і викликати рекурсивну функцію branch() . |
Для роботи рекурсії необхідно встановити умову виходу, інакше функція буде нескінченно рекурсивно викликати себе. Цю умову виходу з рекурсії пов’яжемо зі зменшенням довжини гілок дерева - коли лінії стануть занадто короткими, розгалуження дерева потрібно припинити.
function branch(len) { (1)
line(0, 0, 0, -len);
translate(0, -len);
len = len * 0.66; (2)
if (len > 2) {
push();
rotate(PI / 6);
branch(len); (3)
pop();
push();
rotate(-PI / 6);
branch(len);
pop();
}
}
1 | Кожна гілка тепер отримує свою довжину як аргумент. |
2 | Довжина кожної гілки зменшується на дві третини. |
3 | Наступні виклики рекурсивної функції branch() містять аргумент len . |
Запишемо остаточний код застосунку для малювання дерева.
function setup() {
createCanvas(200, 200);
}
function draw() {
background(39, 38, 64);
stroke(0, 181, 184);
translate(width / 2, height);
branch(60);
}
function branch(len) {
line(0, 0, 0, -len);
translate(0, -len);
len = len * 0.66;
if (len > 2) {
push();
rotate(PI / 6);
branch(len);
pop();
push();
rotate(-PI / 6);
branch(len);
pop();
}
}

Рекурсивний фрактал дерева - чудовий приклад сценарію, в якому додавання трохи випадковості може зробити дерево більш природним.
Якщо поглянути на справжні дерева, можна помітити, що довжина і кути розгалужень варіюються від гілки до гілки, не кажучи вже про те, що не всі гілки мають однакову кількість менших гілок.
Отже, змінимо кут розгалуження гілок випадковим чином.
let beta;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(39, 38, 64);
stroke(0, 181, 184);
translate(width / 2, height);
branch(60);
}
function branch(len) {
beta = random(0, PI / 3);
line(0, 0, 0, -len);
translate(0, -len);
len = len * 0.66;
if (len > 2) {
push();
rotate(beta);
branch(len);
pop();
push();
rotate(-beta);
branch(len);
pop();
}
noLoop();
}

А це варіант для випадкових кута розгалуження і довжини гілок дерева.
let beta;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(39, 38, 64);
stroke(0, 181, 184);
translate(width / 2, height);
branch(60);
}
function branch(len) {
line(0, 0, 0, -len);
translate(0, -len);
len = len * 0.66;
if (len > 2) {
let n = int(random(1, 4));
for (let i = 0; i < n; i++) {
beta = random(-PI / 2, PI / 2);
push();
rotate(beta);
branch(len);
pop();
}
}
noLoop();
}

6.5.5. 3D-примітиви
Однією з важливих відмінностей між створенням примітивів у 2D
і у 3D
є те, що функції бібліотеки p5.js
для побудови 3D
-примітивів як аргументи приймають значення розмірів, а не положення. Для зміни положення 3D
-примітивів необхідно викликати функції translate()
і rotate()
.
У бібліотеці p5.js
є попередньо визначені базові функції для створення 3D
-примітивів:
Функції для малювання 3D -фігур можна викликати без аргументів. У цьому разі будуть застосовуватися значення розміру за стандартним налаштуванням для кожної з фігур.
|
Розглянемо код застосунку, в якому за натискання миші на полотні відображаються по черзі усі 3D
-примітиви.
let i = 0;
function setup() {
createCanvas(200, 200, WEBGL);
}
function draw() {
background(245);
translate(mouseX - width / 2, mouseY - height / 2, 0);
rotateY(frameCount * 0.02);
if (i == 0) {
plane(100, 100); (1)
} else if (i == 1) {
box(100, 100, 100); (2)
} else if (i == 2) {
sphere(75); (3)
} else if (i == 3) {
cylinder(50, 100); (4)
} else if (i == 4) {
cone(70, 100); (5)
} else if (i == 5) {
ellipsoid(60, 70, 70); (6)
} else if (i == 6) {
torus(50, 25); (7)
}
}
function mousePressed() {
i = (i + 1) % 7;
}
1 | Площина із заданими шириною та висотою. |
2 | Паралелепіпед із заданою шириною, висотою та глибиною. |
3 | Сфера заданого радіуса. |
4 | Циліндр із заданими радіусом і висотою. |
5 | Конус із заданими радіусом і висотою. |
6 | Еліпсоїд із заданим радіусом. |
7 | Тор із заданими радіусом і радіусом трубки. |
Використовуючи функцію mousePressed()
, яка відстежує подію натискання миші у вікні застосунку, змінюється значення i
. Відповідно значення i
виконується одна з гілок if
для малювання 3D
-фігури у місці вказівника миші.
Переглядаємо Аналізуємо
Якщо необхідно намалювати одночасно кілька 3D
-фігур у різних положеннях на полотні, тут знову приходять на допомогу функції push()
і pop()
, які викликають щоразу, коли малюють іншу фігуру.
function setup() {
createCanvas(400, 200, WEBGL);
}
function draw() {
background(245);
push();
translate(-width / 4, 0, 0);
rotateZ(frameCount * 0.02);
cone();
pop();
push();
rotateY(frameCount * 0.02);
box();
pop();
push();
translate(width / 4, 0, 0);
rotateX(frameCount * 0.02);
torus();
pop();
}
Переглядаємо Аналізуємо
Для створення інших тривимірних фігур використовують функції beginShape() , vertex() та endShape() .
У WEBGL
-режимі перелічені функції працюють аналогічно, як і у 2D
-режимі, за винятком того, що у WEBGL
вершини отримують значення x
, y
і z
як координати їх розташування.
Розглянемо код застосунку, який будує тривимірну піраміду, що обертається відносно центру полотна.
let s = 50; (1)
let angle = 0;
function setup() {
createCanvas(200, 200, WEBGL);
noStroke();
}
function draw() {
background(245);
rotateY(-angle);
rotateZ(angle);
fill(100, 87, 166); // Ultra Violet
sphere(25); (2)
createPyramid(s); (3)
angle += 0.01;
}
function createPyramid(s) { (4)
beginShape(TRIANGLES);
fill(255, 227, 71); // Mustard
vertex(0, 0, s);
vertex(-s, -s, -s);
vertex(s, -s, -s);
fill(239, 118, 122); // Light coral
vertex(0, 0, s);
vertex(s, -s, -s);
vertex(0, s, -s);
fill(0, 196, 154); // Mint
vertex(0, 0, s);
vertex(0, s, -s);
vertex(-s, -s, -s);
fill(114, 76, 249); // Majorelle Blue
vertex(-s, -s, -s);
vertex(s, -s, -s);
vertex(0, s, -s);
endShape(CLOSE);
}
1 | Ініціалізуємо змінну s , яка визначатиме розміри піраміди. |
2 | Малюємо сферу для позначення центра системи координат тривимірного простору. |
3 | Викликаємо користувацьку функцію createPyramid() з аргументом s для побудови піраміди. |
4 | Описуємо користувацьку функцію createPyramid() , яка прийматиме аргумент s , значення якого буде використовуватися для формування координат x , y і z у побудові вершин піраміди. |
Переглядаємо Аналізуємо
Вправа 68
Змінити код застосунку для малювання прозорих граней 3D
-піраміди.
6.5.6. Використання 3D-моделей
Усі 3D -моделі, які використані у підручнику, завантажені із сайту rigmodels.com .
|
Бібліотека p5.js
, окрім низки 3D
-примітивів, також здатна відтворювати 3D-моделі з OBJ-файлів або STL-файлів, створених в інших застосунках, як-от Blender .
Для цього спочатку потрібно завантажити модель у тіло функції preload()
за допомогою функції loadModel() , а потім намалювати її, використовуючи функцію model() .
Щоб нормалізувати імпортовану із файлу в ескіз 3D -модель до розміру для перегляду, у виклику loadModel() другим аргументом необхідно вказати значення true .
|
Розглянемо застосунок, який завантажує 3D
-модель з OBJ
-файлу і відображає її на полотні.
Завантажити файл 3D -моделі можна за покликанням.
|
let dog; (1)
function preload() {
dog = loadModel("frankie.obj", true); (2)
}
function setup() {
createCanvas(200, 200, WEBGL);
}
function draw() {
background(245);
translate(mouseX - width / 2, mouseY - height / 2, 0); (3)
rotateX(PI); (4)
rotateY(frameCount * 0.01); (5)
scale(0.8); (6)
model(dog); (7)
}
1 | Оголошуємо змінну dog , яка буде покликатися на об’єкт завантаженої із файлу 3D -моделі. |
2 | Імпортуємо 3D -модель із файлу frankie.obj за допомогою функції loadModel() , у виклику якої другим аргументом зазначаємо true для нормалізації вигляду моделі. Зберігаємо імпортовану модель під ім’ям dog . |
3 | Переміщуємо систему координат в точку, в якій перебуває вказівник миші. |
4 | Перевертаємо модель вертикально. |
5 | Обертаємо модель навколо осі Y . |
6 | Зменшуємо розміри моделі. |
7 | Відображаємо модель на полотні за допомогою виклику функції model() , аргументом для якої буде об’єкт під ім’ям dog завантаженої із файлу моделі. |
Переглядаємо Аналізуємо
Цікавимось Додатково
6.5.7. 3D-сцена та її складові
Коли бібліотека p5.js
виконує рендеринг 3D-сцени, вона абстрагує багато складнощів, пов’язаних із перетворенням 3D
-фігури у 2D
-зображення, яке ми бачимо на екрані комп’ютера.
Ці складнощі пов’язані з процесом створення 3D
-сцени, в якому важливу роль відіграють камера, світло та матеріали.
Камера
Перегляд 3D
-сцени завжди відбувається з певної точки простору, яку називають камерою. Розташування та напрямок огляду камери визначають, що видно у тривимірному просторі та наскільки близько чи далеко розташовані об’єкти в ньому.
Камера є важливою складовою 3D
-сцени, оскільки вона впливає на реалістичність та сприйняття тривимірної графіки.
Камера - точка огляду 3D -сцени. Звичайно, мова йде не про фізичну камеру, а про імітацію камери, яка визначає, як об’єкти у тривимірному просторі відображаються на двовимірному екрані.
|
Керувати положенням і напрямком огляду камери у p5.js
можна за допомогою функції camera() .
Перші три аргументи для camera()
- це положення камери (x, y, z)
у 3D
-просторі.
Переглядаємо Аналізуємо
Наступні три аргументи - це координати (x, y, z)
точки на полотні, куди спрямована камера і за стандартним налаштуванням дорівнюють (0, 0, 0)
. Останні три аргументи - це напрямок «вгору» від камери (орієнтація камери), зазвичай (0, 1, 0)
.
Функція camera() імітує рухи камери, дозволяючи переглядати об’єкти під різними кутами. Об’єкти при цьому не переміщуються.
|
Розглянемо застосунок, який використовує камеру для огляду завантаженої із файлу 3D
-моделі.
За допомогою вкладених циклів і функції map()
, яка змінює числове значення з одного діапазону в інший, обчислимо координати x
та z
для малювання імпортованої в ескіз 3D
-моделі за допомогою виклику translate(x, 0, z)
.
Завантажити файл 3D -моделі можна за покликанням.
|
let bird;
function preload() {
bird = loadModel("chuck.obj", true);
}
function setup() {
createCanvas(200, 200, WEBGL);
camera(0, -120, 300, 0, 0, 0, 0, 1, 0);
}
function draw() {
background(245);
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 2; j++) {
let x = map(i, 0, 1, -90, 90);
let z = map(j, 0, 1, -100, 100);
push();
translate(x, 0, z);
rotateX(PI);
rotateY(frameCount * 0.01);
model(bird);
pop();
}
}
}
Переглядаємо Аналізуємо
Камера, яку створює за стандартним налаштуванням функція camera()
, є перспективною, оскільки вона створює ілюзію глибини, відтворюючи 3D
-об’єкти поблизу камери в їх фактичному розмірі та зменшуючи об’єкти, які розташовані далі.
Змінити стандартні параметри камери можна за допомогою викликів функцій:
-
perspective() - встановлює перспективну проєкцію для поточної камери в
3D
-ескізі; -
ortho() - встановлює ортогональну проєкцію для поточної камери в
3D
-ескізі.
Перспективна проєкція:
-
створює видимість глибини, завдяки чому об’єкти на відстані здаються меншими, а ті, що розташовані ближче до глядача в
z
-площині, виглядають більшими.
Ортогональна (прямокутна) проєкція:
-
об’єкти однакових розмірів виглядають однаковими, навіть якщо вони розташовані далі на
z
-площині, що створює враження двовимірності.
Одним із параметрів, які можна змінити за допомогою функції perspective()
- це поле зору (англ. field of view
, FoV
- широта спостережуваного світу, яка спостерігається в будь-який момент) або інакше кут огляду, який впливає на те, як форми змінюють розмір на відстані.
Розглянемо застосунок, в якому кут огляду камеру для завантаженої із файлу 3D
-моделі змінюється за допомогою переміщення вказівника миші.
Завантажити файл 3D -моделі можна за покликанням.
|
let bird;
function preload() {
bird = loadModel("red.obj", true);
}
function setup() {
createCanvas(200, 200, WEBGL);
camera(0, 0, 200, 0, 0, 0, 0, 1, 0);
}
function draw() {
background(245);
let fieldOfView = radians(map(mouseY, 0, height, 60, 30));
perspective(fieldOfView);
translate(0, 0, 0);
rotateX(PI);
rotateY(frameCount * 0.01);
model(bird);
}
Переглядаємо Аналізуємо
На відміну від перспективної проєкції камери, для ортогональної проєкції камери розміри залишаються незмінними, коли об’єкти віддаляються чи наближаються.
let bird;
function preload() {
bird = loadModel("chuck.obj", true);
}
function setup() {
createCanvas(200, 200, WEBGL);
ortho(-width / 2, width / 2, height / 2, -height / 2, 0, 500);
}
function draw() {
background(245);
rotateX(0.2);
rotateY(-0.2);
push();
translate(-50, 0, sin(frameCount / 30) * 100);
model(bird);
pop();
push();
translate(50, 0, sin(frameCount / 30 + PI) * 100);
model(bird);
pop();
}
Переглядаємо Аналізуємо
Для кращого розуміння того, як працює камера, скористайтеся інтерактивною демонстрацією за покликанням. |
Переглядаємо Аналізуємо
Під час роботи в 3D
-режимі постійне переміщення та налаштування камери в коді застосунку може бути виснажливим. Для таких цілей бібліотека p5.js
містить функцію orbitControl() , яка дозволяє легко обертати, масштабувати та переміщувати камеру за допомогою миші або через дотик, а саме:
-
щоб змінити масштаб камери, прокручуйте колесо миші;
-
щоб повернути камеру, натисніть ліву кнопку миші та перетягніть полотно;
-
щоб перемістити камеру, натисніть праву кнопку миші та перетягніть полотно.
Ще однією корисною функцією для роботи з камерою є функція debugMode() , яка вміє малювати сітку та індикатор осей на полотні.
У наступному застосунку намалюємо сітку та осі координат.
let bird;
function preload() {
bird = loadModel("red.obj", true);
}
function setup() {
createCanvas(200, 200, WEBGL);
camera(0, 0, 200, 0, 0, 0, 0, 1, 0);
debugMode(300, 10, 0, 0, 0, 150, 0, -100, 0); (1)
}
function draw() {
background(245);
orbitControl(1, 1); (2)
translate(0, 0, 0);
rotateX(PI);
rotateY(frameCount * 0.01);
model(bird);
}
1 | Перший аргумент (300 ) у виклику функції - розмір однієї сторони сітки, другий (10 ) - кількість поділок одної сторони сітки, наступні три (0, 0, 0 ) - зміщення сітки від точки (0, 0, 0) , шостий (150 ) - розмір індикатора осей, останні три (0, -100, 0 ) - зміщення осей відносно точки (0, 0, 0) . |
2 | Аргументи у виклику функції визначають чутливість до руху миші/дотику вздовж осей X та Y . Виклик функції orbitControl() без аргументів еквівалентний виклику orbitControl(1, 1) . Щоб змінити напрямок руху будь-якої осі, необхідно ввести від’ємне число для чутливості. |
Переглядаємо Аналізуємо
Вправа 69
Змінити параметри застосунку, щоб індикатор осей розташувався в центрі об’єкта, а сам об’єкт - на поверхні сітки.
Освітлення
Освітлення, як ще одна складова 3D
-сцени, - простий, але потужний спосіб забезпечити глибину та реалістичність ескізів p5.js
.
Подібно до того, як тривимірне малювання є ілюзією, додавання освітлення до ескізу є моделюванням ідеї освітлення реального світу з метою створення різноманітних ефектів.
Для увімкнення освітлення за стандартним налаштуванням можна скористатися функцією lights() .
Освітлення потрібно «вмикати» у блоці draw() .
|
Наприклад, використаємо функцію lights()
для освітлення сфери (зауважте, сфера не виглядає тривимірною, доки її не освітлять) за умови, коли будь-яка з кнопок миші натиснута.
Для цього перевірятимемо значення змінної lightIsOn
, яка набуватиме по черзі значень true
або false
при натисканні кнопки миші. За натисканням кнопок миші слідкуватиме функція mousePressed() , яка у момент натискання кнопки миші змінюватиме значення lightIsOn
на протилежне.
let lightIsOn = false; // перемикач
function setup() {
createCanvas(200, 200, WEBGL);
noStroke();
}
function draw() {
background(45, 132, 138); // Teal
if (lightIsOn) {
lights();
} else {
fill(237, 255, 122); // Mindaro
}
translate(0, 0, 0);
sphere(50);
}
function mousePressed() {
lightIsOn = !lightIsOn;
}
Переглядаємо Аналізуємо
Для налаштування в ескізі спеціального освітлення бібліотека p5.js
має низку функцій, які визначають різні типи джерел світла:
-
ambientLight() - створює навколишнє освітлення заданим кольором, предмети рівномірно освітлені з усіх боків;
-
directionalLight() - створює спрямоване світло із заданим кольором, яке надходить з одного напрямку та є сильнішим, якщо воно потрапляє на поверхню прямо, і слабшим, якщо воно потрапляє під кутом;
-
spotLight() - створює прожектор подібний до спрямованого світла, але має інший ефект, коли він розташований далі чи ближче до об’єкта, із заданим кольором, положенням, напрямком світла, кутом і концентрацією;
-
pointLight() - створює точкове джерело світла із заданим кольором і положенням, яке випромінює з однієї точки в усіх напрямках та має різний ефект, коли джерело світла розташоване далі та ближче до об’єкта;
-
noLights() - видаляє всі джерела світла в ескізі.
Проаналізуємо код застосунку, який наведений нижче, закоментувавши та розкоментувавши виклик кожного джерела світла.
let lightIsOn = false; // перемикач (1)
function setup() {
createCanvas(200, 200, WEBGL);
noStroke();
}
function draw() {
background(45, 132, 138); // Teal
if (lightIsOn) { (2)
lights();
// ambientLight(255, 0, 0); (3)
/*
directionalLight( (4)
255,
0,
0,
-(mouseX / width - 0.5) * 2,
-(mouseY / height - 0.5) * 2,
-1
);
*/
/*
spotLight( (5)
255,
0,
0,
mouseX - width / 2,
mouseY - height / 2,
100,
0,
0,
-1,
PI / 6,
1
);
*/
// pointLight(255, 0, 0, mouseX - width / 2, mouseY - height / 2, 50); (6)
} else {
noLights();
fill(237, 255, 122); // Mindaro
}
translate(0, 0, 0);
sphere(50);
}
function mousePressed() {
lightIsOn = !lightIsOn;
}
1 | Ініціалізуємо змінну lightIsOn зі значенням false , яка позначатиме увімкнення ефектів освітлення. |
2 | Якщо lightIsOn має значення false , спрацьовує виклик функції noLights() - усі джерела світла видаляються і сфера зафарбовується в колір Mindaro , інакше - відбувається виклик функції lights() джерела світла або іншої з розкоментованих пунктів 3-7. |
3 | Рівномірне освітлення сфери з усіх боків червоним кольором 255, 0, 0 . |
4 | Спрямоване освітлення червоним кольором 255, 0, 0 і напрямком освітлення -(mouseX / width - 0.5) * 2, -(mouseY / height - 0.5) * 2, -1 по осях у діапазоні від -1 до 1 включно. |
5 | Освітлення прожектором червоного кольору 255, 0, 0 з точки із координатами mouseX - width / 2, mouseY - height / 2, 100 , напрямком світла 0, 0, -1 вздовж конічної форми, яка визначається кутом PI / 6 (за стандартним налаштуванням PI / 3 ) і концентрацією 1 (за стандартним налаштуванням 100 ). |
6 | Освітлення від точкового джерела світла із червоним кольором 255, 0, 0 і положенням джерела світла у точці mouseX - width / 2, mouseY - height / 2, 50 . |
Переглядаємо Аналізуємо
Для кращого розуміння того, як поводять себе різні типи освітлення, скористайтеся інтерактивною демонстрацією за покликанням. |
Переглядаємо Аналізуємо
Матеріали
Матеріали дозволяють надати 3D
-об’єктам реалістичний вигляд, залежно від того, як вони взаємодіють зі світлом. Наприклад, матеріал може визначати, чи об’єкт відбиває світло, якого кольору має бути об’єкт, чи має він блискучість, чи об’єкт випромінює світло.
Матеріал визначає зовнішній вигляд 3D -об’єктів і є набором властивостей, які впливають на те, як об’єкт взаємодіє з освітленням у 3D -сцені.
|
Бібліотека p5.js
має низку вбудованих функцій для встановлення різних типів матеріалів, таких як:
-
normalMaterial() - встановлює поточний матеріал як звичайний, який не реагує на окремі джерела світла, тому його часто використовують як матеріал-заповнювач в RGB-кольорах під час налагодження;
-
ambientMaterial() - встановлює для матеріалу колір зі складовими (компонентами) кольору функції освітлення
ambientLight()
, які відбиває об’єкт; -
emissiveMaterial() - встановлює колір для випромінювання світла з поверхні об’єкта (ефект світіння), який не змінюється в залежності від освітлення
3D
-сцени; -
specularMaterial() - встановлює колір, зі складовими кольору функції освітлення
ambientLight()
, які об’єкт відбиває. Однак, на відміну відambientMaterial()
, для всіх інших типів світла (directionalLight()
,pointLight()
,spotLight()
), дзеркальний матеріал відображатиме колір джерела світла і саме це надає йому «блискучого» вигляду (блиск можна контролювати за допомогою функції shininess() ).
Поміркуємо над утворенням кольору (матеріалу) об’єкта, який реагує на амбієнтне освітлення в 3D
-сцені. Для цього розглянемо функцію ambientMaterial()
із жовтим кольором (255, 255, 0
) і функцію ambientLight()
з різними значеннями кольору освітлення.
Колір ambientMaterial()
утворюється із компонентів кольору ambientLight()
, які відбиває об’єкт. Якщо ambientLight()
випромінює світло білого кольору (255, 255, 255
), тоді об’єкт виглядатиме жовтим, оскільки для глядача він відбиває червоний і зелений компоненти світла, а синій - «поглинає».
Якщо ambientLight()
випромінює світло червоного кольору (255, 0, 0
), тоді об’єкт для глядача виглядатиме червоним, оскільки він відбиватиме червоний компонент світла, а зелений і синій - «поглинатиме».
Якщо ambientLight()
випромінює світло синього кольору (0, 0, 255
), то об’єкт для глядача буде виглядати чорним, оскільки немає компонентів світла, які він може відбити.
Колір об’єктів виникає внаслідок того, що певні кольори світла відбиваються від них. |
У підсумку розглянемо код застосунку, в якому створимо об’єкти різних матеріалів.
function setup() {
createCanvas(200, 200, WEBGL);
}
function draw() {
background(245);
// сфера
push();
translate(0, 0, 0);
ambientLight(255, 0, 255); (1)
ambientMaterial(255, 255, 255); (2)
noStroke();
sphere(30);
pop();
stroke(255);
// куб у лівому верхньому куті
push();
translate(-50, -50, 35);
ambientLight(255, 255, 255); (3)
ambientMaterial(70, 130, 230); // Azure (4)
box(30);
pop();
// куб у правому верхньому куті
push();
translate(50, -50, 35);
ambientLight(255, 255, 0); (5)
ambientMaterial(70, 130, 230); (6)
box(30);
pop();
// куб у правому нижньому куті
push();
translate(50, 50, 35);
ambientLight(0, 255, 0); (7)
ambientMaterial(255, 0, 255); (8)
box(30);
pop();
// куб у лівому нижньому куті
push();
translate(-50, 50, 35);
ambientLight(255, 200, 87); // Sunglow (9)
ambientMaterial(192, 253, 251); // Celeste (10)
box(30);
pop();
}
1 | Сфера освітлюється світлом, яке має лише компоненти червоного і синього кольорів. |
2 | Для сфери встановлений білий колір матеріалу. Сфера відбиває лише червону та синю компоненти світла, що падає, і тому колір її матеріалу є 255, 0, 255 (Fuchsia ). |
3 | Куб, який розміщений у лівому верхньому куті, освітлюється білим світлом. |
4 | Для куба, який розміщений у лівому верхньому куті, встановлений колір матеріалу 70, 130, 230 (Azure ). Куб відбиває червоне, зелене та синє світло і має колір матеріалу 70, 130, 230 (Azure ). |
5 | Куб, який розміщений у правому верхньому куті, освітлюється світлом, яке має лише компоненти червоного і зеленого кольорів. |
6 | Для куба, який розміщений у правому верхньому куті, встановлений колір матеріалу 70, 130, 230 (Azure ). Куб відбиває червоне та зелене світло і має колір матеріалу 70, 130, 0 (Avocado ). |
7 | Куб, який розміщений у правому нижньому куті, освітлюється світлом, яке має лише компонент зеленого кольору. |
8 | Для куба, який розміщений у правому нижньому куті, встановлений колір матеріалу 255, 0, 255 (Fuchsia ). Оскільки куб не містить зеленого кольору, він не відбиває світло і має колір матеріалу чорний. |
9 | Куб, який розміщений у лівому нижньому куті, освітлюється світлом, яке має усі компоненти кольору і відповідно значення 255, 200, 87 (Sunglow ). |
10 | Для куба, який розміщений у лівому нижньому куті, встановлений колір матеріалу 192, 253, 251 (Celeste ). Куб відбиває червоне, зелене та синє світло і має після обчислень колір матеріалу 192, 198, 86 (Citron ). |

Для перегляду результату відбивання світла для різних типів матеріалів скористайтеся інтерактивною демонстрацією за покликанням. |
Переглядаємо Аналізуємо
Текстури
Текстури - це зображення, які можуть бути прикріплені до 2D - або 3D -об’єктів, щоб надати об’єктам додатковий вигляд і візуальні деталі.
|
Використовуючи текстури, можна створювати складніші та реалістичніші зображення, а також задавати деталі на поверхнях об’єктів, які було б важко або неможливо досягнути інакше. Будь-яке зображення, відео чи трансляцію вебкамери можна використовувати як текстуру.
У p5.js для застосування текстур до об’єктів використовується функція texture() , яка дозволяє прикріпити зображення до геометричного об’єкта.
Розглянемо код застосунку, який використовує текстури для створення планети Юпітер у міжзоряному просторі.
Завантажити файли текстур можна за покликанням. |
let jupiter, stars; (1)
function preload() { (2)
jupiter = loadImage("2k_jupiter.jpg");
stars = loadImage("2k_stars.jpg");
}
function setup() {
createCanvas(200, 200, WEBGL);
noStroke();
textureMode(NORMAL);
}
function draw() {
texture(stars); (3)
plane(width, height);
rotateY(frameCount * 0.0035);
scale(0.6);
texture(jupiter); (4)
sphere(75);
}
1 | Оголошуємо змінні jupiter і stars , які будуть покликатися на об’єкти текстур. |
2 | За допомогою функції preload() завантажуємо файли із зображеннями текстур та зберігаємо з іменами jupiter і stars відповідно. |
3 | Викликаємо функцію texture() для додавання текстури stars міжзоряного простору до 3D -об’єкта (площина), створеного за допомогою plane() . |
4 | Викликаємо функцію texture() для додавання текстури jupiter планети до 3D -об’єкта (сфера), створеного за допомогою sphere() . |
Переглядаємо Аналізуємо
Функцію texture()
можна використовувати для роботи з текстом у режимі WEBGL
.
Розглянемо код застосунку, який малює текст як текстуру. Для наших цілей приєднаємо до ескізу шрифт та використаємо функції push()
і pop()
, які будуть зберігати й відновлювати матрицю трансформацій, щоб не впливати на решту 3D
-сцени.
Також, змінюючи значення координати z
у translate()
, будемо контролювати взаємне розташування об’єктів з різними текстурами у тривимірному просторі.
Завантажити файл шрифту можна за покликанням. |
let jupiter, stars, myFont, textTexture;
function preload() {
jupiter = loadImage("2k_jupiter.jpg");
stars = loadImage("2k_stars.jpg");
myFont = loadFont("CooperHewitt-Medium.otf"); (1)
}
function setup() {
createCanvas(200, 200, WEBGL);
noStroke();
textureMode(NORMAL);
textTexture = createGraphics(100, 100); (2)
textTexture.textFont(myFont);
textTexture.textSize(30);
textTexture.fill(214, 153, 207); // Plum (web)
textTexture.textAlign(CENTER, CENTER);
textTexture.text("Jupiter", textTexture.width / 2, textTexture.height / 2);
}
function draw() {
background(245);
// міжзоряна площина
push();
translate(0, 0, -50); (3)
texture(stars);
plane(width + 50, height + 50);
pop();
// планета Юпітер
push();
translate(0, 0, 0); (4)
rotateY(frameCount * 0.009);
scale(0.6);
texture(jupiter);
sphere(75);
pop();
// текстовий напис
push();
translate(90, 100, -40); (5)
texture(textTexture); (6)
plane();
pop();
}
1 | Завантажуємо шрифт у тіло функції preload() за допомогою loadFont() . |
2 | Створюємо текстуру з ім’ям textTexture для текстового напису. За допомогою функції createGraphics() малюємо полотно в закадровому графічному буфері вказаних розмірів і встановлюємо параметри тексту для цього полотна. |
3 | Розташовуємо площину зоряного неба якнайдалі. |
4 | Розташовуємо планету у центрі системи координат. |
5 | Розташовуємо текстовий напис ближче до глядача, порівняно з площиною зір. |
6 | Застосовуємо текстуру textTexture , яка містить зрендерований текст із вказаними налаштуваннями шрифту, за допомогою функції texture() на площині, яку будуємо за допомогою функції plane() . |
Переглядаємо Аналізуємо
6.5.8. Ресурси
Корисні джерела
6.5.9. Контрольні запитання
Міркуємо Обговорюємо
-
Що таке «матриця трансформації»?
-
Як працюють трансформації переміщення, обертання і масштабування?
-
Які налаштування необхідно зробити для створення застосунків у
3D
-режимі? -
Навести приклади
3D
-примітивів. -
Як імпортувати
3D
-модель із файлу в ескіз? -
Що таке «3D-сцена»? Схарактеризувати складові
3D
-сцени.
6.5.10. Практичні завдання
Початковий
-
Створити застосунок, в якому квадрат малюється у стандартній системі координат, а коло у системі координат, відлік якої динамічно встановлюється у місці вказівника миші. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок, в якому у центрі полотна обертається текстовий рядок. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок, в якому вертикально рухається платформа з вантажем. Коли платформа досягає верхньої межі полотна, то вона починає рух у нижній частині полотна. Орієнтовний зразок роботи застосунку представлений в демонстрації.
-
Створити
3D
-застосунок, в якому за допомогою миші можна обертати дві площини, одна з яких є меншою за розмірами та розташована за іншою. Орієнтовний зразок роботи застосунку представлений в демонстрації.
Середній
-
Створити застосунок з кубом, що обертається, і на гранях якого необхідно розмістити зображення. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Завантажити файл зображення можна за покликанням. |
-
Створити застосунок, в якому
Tony
вчиться обертатися у польоті. Орієнтовний взірець роботи застосунку представлений в демонстрації.

-
У бібліотеці
p5.js
функціяbox()
використовується для створення куба. Створити застосунок, який будує тривимірний простір за допомогою функційbeginShape()
,vertex()
іendShape()
. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Високий
-
Створити
3D
-застосунок, в якому у тривимірному просторі обертаються з різними швидкостями навколо свого центру площини різних розмірів. За потреби, можна вказувати, навколо яких осей відбувається обертання. Орієнтовний взірець роботи застосунку, в якому обертання відбувається навколо осіZ
, представлений в демонстрації.
-
Створити застосунок, в якому фігура рухається і змінює свій напрямок руху, коли торкається меж полотна. У момент відбивання від межі по периметру полотна вмикається кольоровий індикатор. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити
3D
-застосунок, в якому у тривимірному просторі хаотично обертаються піраміди. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Екстремальний
-
Створити застосунок-годинник. Орієнтовні зразки годинників представлені у демонстраціях.
-
Створити двовимірну модель Сонячної системи із Землею, Місяцем і рухом навколо Місяця космічного корабля місії Артеміда . Орієнтовний взірець роботи застосунку представлений в демонстрації.
6.6. Бібліотеки для роботи з мультимедійними даними
Розглянемо використання декількох бібліотек, представлених на сторінці офіційної документації бібліотеки p5.js
, для роботи з мультимедійними даними та розширення функціонала p5.js
.
6.6.1. ASCII Art
p5.asciiart - це простий у використанні графічний конвертер зображень в ASCII -символи для p5.js . Розробником бібліотеки p5.asciiart є Павел Яніцкі (Pawel Janicki) .
|
Цікавимось
Щоб використати бібліотеку p5.asciiart
, її необхідно завантажити зі сторінки tetoki.eu/asciiart і розпакувати завантажений архів.
У разі, якщо бібліотека Також, для роботи з бібліотекою |
Щоб додати власне зображення для конвертації в ASCII
, необхідно «покласти» зображення у каталог examples
, а у файлі asciiart_stillimage_example.js
у функції preload()
записати шлях до зображення згідно із наведеним зразком.
...
function preload() {
// "Young man reading by candlelight", Matthias Stom, 1600-1650
images[0] = loadImage('example_image_young_man_reading.jpg');
// "Le Penseur", Auguste Rodin, 1880
images[1] =loadImage('example_image_Thinking-Man.jpg');
// "American Gothic", Grant DeVolson Wood, 1930
images[2] = loadImage('example_image_American_Gothic.jpg');
// "La Liseuse", Jean-Honoré Fragonard, 1770
images[3] = loadImage('example_image_young_girl_reading.jpg');
}
...
Переглядаємо Аналізуємо
Вправа 70
Використати бібліотеку p5.asciiart.js
для конвертації власних світлин в ASCII
-символи.
6.6.2. Shape5
Познайомимось ще з однією бібліотекою - Shape5 , яка призначена для побудови двовимірних примітивів і орієнтована на початківців, які роблять перші кроки у кодуванні.
Розробником бібліотеки Shape5.js є Патрік Естер (Patrick Ester) . Ідея проєкту Shape5.js полягає у зниженні порогу входження у створенні статичного мистецтва за допомогою p5.js .
|
Встановимо і приєднаємо бібліотеку локально, виконавши наступні кроки.
-
Завантажити архів з файлом бібліотеки та іншими файлами (зелена кнопка з написом
Code
) із GitHub -сховища. -
Розпакувати архів і відкрити каталог
Shape5 Template
. Файлshape5.js
- це і є файл бібліотекиShape5
, який вже приєднаний до проєкту (у цьому можна переконатися, відкривши файлindex.html
у редакторі коду). -
Записати свій код у файл ескізу
sketch.js
. -
Відкрити файл
index.html
у вебпереглядачі, щоб переглянути результати виконання коду.
Звернемось до принципів об’єктоорієнтованого програмування, щоб коротко пояснити роботу з бібліотекою.
Поняття об’єктоорієнтованого програмування більш детально розглядаються у розділі Об’єкти та класи. |
Файл бібліотеки shape5.js містить класи об’єктів (об’єктами є коло, прямокутник, овал тощо) та їхні атрибути (розмір, колір тощо). Кожен клас має метод .show()
, який використовується для малювання фігури на полотні.
На основі певного класу створюється конкретний об’єкт класу (екземпляр класу) - змінна, яка має деяке ім’я. Також, екземпляр класу отримує набір атрибутів цього класу.
Загалом алгоритм створення фігури за допомогою бібліотеки Shape5.js
такий:
-
Оголошення змінної на основі класу фігури.
-
Модифікації фігури - зміна значень її атрибутів.
-
Малювання фігури.
Спосіб модифікації фігури залежить від атрибутів, пов’язаних із кожною фігурою. Усі фігури мають обмежений набір атрибутів, які можна переглянути на сторінці бібліотеки.
Модифікації фігур повинні бути зроблені до того, як їх буде намальовано на полотні. Нижче наведений шаблон інструкції зміни атрибута у конкретного об’єкта класу
variableName.attribute = value;
де
-
variableName
- ім’я змінної, що позначає конкретний об’єкт; -
.
- символ крапки, який використовується для доступу до атрибутів об’єкта; -
символ
=
- операція присвоєння; -
value
- значення, яке присвоюють атрибуту об’єкта; -
;
- символ завершення інструкції.
За стандартним налаштуванням Shape5.js
використовує Hex CSS
-кольори. Також можна використовувати традиційну функцію color() із бібліотеки p5.js
.
Отже, напишемо код для створення статичного малюнка за допомогою бібліотеки Shape5.js
.
let b; (1)
function setup() {
createCanvas(200, 200);
b = new Square(); (2)
}
function draw() {
background("#4C1E4F"); // Palatinate (3)
b.x = 100;
b.y = 100;
b.size = 50;
b.color = color(255, 239, 213); // Papaya whip
b.spin = 45; (4)
b.show(); (5)
}
1 | Оголошення глобальної змінної з ім’ям b . |
2 | Створення екземпляра класу Square . Тобто, на основі класу Square створюється конкретний об’єкт - квадрат з ім’ям b і набором атрибутів за стандартним налаштуванням (переглянути атрибути за стандартним налаштуванням можна у файлі shape5.js для класу Square ). |
3 | Встановлення кольору полотна за допомогою синтаксису Hex CSS . |
4 | Модифікація значення фігури - встановлення для квадрата b кута повороту 45° за допомогою звернення до атрибута spin . |
5 | Використання методу show() для малювання квадрата b на полотні. |
В результаті виконання коду отримуємо фігуру - ромб.

До речі, бібліотека Shape5.js має клас Rhombus для створення ромбів 😉 .
|
Вправа 71
Створити застосунок, використовуючи фігури із бібліотеки Shape5.js
.
Для миттєвого застосування Shape5.js відкрийте зразок ескізу , до якого вже приєднана бібліотека.
|
6.6.3. p5.sound.js
Бібліотека p5.sound.js , яка використовується в тандемі з p5.js
, розширює останню за допомогою функцій Web Audio
- API, який надає інструменти для керування відтворенням аудіо на вебсторінці, вибору аудіоджерела, додавання ефектів до аудіо, записування і зберігання звуку та інші.
Розробником бібліотеки p5.sound.js є Джейсон Сігал (Jason Sigal) .
|
Архівна адреса вебсторінки бібліотеки розташована за адресою p5.sound.js . Покликання на описи функцій за архівною адресою позначаються міткою Архів . |
Для роботи зі звуком бібліотека має широкий набір інструментів. Обмежимось знайомством лише із базовими можливостями бібліотеки та приєднаємо її стиснену версію p5.sound.min.js
у файлі index.html
.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css" />
<meta charset="utf-8" />
</head>
<body>
<main></main>
<script src="sketch.js"></script>
</body>
</html>
Зразки аудіофайлів (семпли) для власних експериментів, зокрема й ті, що використовуються у прикладах ескізів цього розділу, можна безплатно завантажити з бібліотеки звуків Sample Focus - онлайн-платформи для обміну та пошуку звуків, яку підтримує Інтернет-спільнота. Покликання є запрошувальним, тобто, якщо ви зареєструєтесь за ним на сайті Sample Focus , ви отримаєте 5 (+ 20) кредитів для безплатного завантаження звуків (1 кредит - 1 завантаження).
|
Цікавимось
Завантаження та відтворення звуку
Розглянемо код застосунку, за допомогою якого в ескіз завантажується аудіофайл і створюється кілька HTML
-елементів у DOM для керування відтворенням звуку у вебпереглядачі.
Щоб використовувати звукові файли в ескізі, дотримуйтесь того ж алгоритму, що й у разі використання бібліотек для роботи із зображеннями. А саме, спочатку потрібно відшукати потрібний аудіофайл, скопіювати його у каталог ескізу, а потім завантажити дані звукового файлу в ескіз. |
Звуковий файл, що використовується у застосунку, можна завантажити за покликанням. |
let sliderSetVolume, sliderRate, checkboxLoop, btn; (1)
let soundFile, bgColor; (2)
function preload() {
soundFile = loadSound("space-synth-melody_138bpm.wav"); (3)
}
function setup() {
createCanvas(200, 200);
sliderRate = createSlider(0.5, 1.5, 1, 0.1); (4)
sliderSetVolume = createSlider(0.0, 1.0, 0.5, 0.1); (5)
checkboxLoop = createCheckbox("циклічно", false); (6)
checkboxLoop.changed(сheckedLooping); (7)
btn = createButton("відтворити"); (8)
btn.mousePressed(switchPlaying); (9)
soundFile.addCue(2, changeBgColor, color(97, 83, 204)); // Iris (10)
soundFile.addCue(5, changeBgColor, color(4, 114, 77)); // Dark spring green
soundFile.addCue(9, changeBgColor, color(166, 0, 103)); // Murrey
bgColor = color(22, 27, 51); // Oxford Blue (11)
textAlign(CENTER, CENTER);
}
function draw() {
background(bgColor); (12)
soundFile.setVolume(sliderSetVolume.value()); (13)
soundFile.rate(sliderRate.value()); (14)
let t = soundFile.duration() - soundFile.currentTime(); (15)
fill(255);
text(t, width / 2, height / 6); (16)
}
function changeBgColor(c) { (17)
bgColor = c;
}
function switchPlaying() { (18)
if (!soundFile.isPlaying()) {
soundFile.play();
btn.html("пауза");
} else {
soundFile.pause();
btn.html("продовжити");
}
}
function сheckedLooping() { (19)
if (checkboxLoop.checked()) {
soundFile.setLoop(true);
} else {
soundFile.setLoop(false);
}
}
1 | Оголошуємо змінні, які позначатимуть елементи інтерфейсу: sliderSetVolume (регулювання звуку відтворення), sliderRate (регулювання швидкості відтворення звукового файлу й зміни тону відповідно), checkboxLoop (чи відтворення звуку буде циклічним) і btn (кнопка відтворення/паузи). |
2 | Оголошуємо змінні: soundFile - назва покликання на об’єкт завантаженого звукового файлу, bgColor - значення кольору полотна. |
3 | Завантажуємо у функції preload() звуковий файл за допомогою функції loadSound() і зазначаємо, що покликанням на звуковий файл буде ім’я soundFile . |
4 | Створюємо слайдер під ім’ям sliderRate для регулювання швидкості відтворення звукового файлу й зміни тону відповідно. |
5 | Створюємо слайдер під ім’ям sliderSetVolume для встановлення максимальної гучності звуку відтворення. |
6 | Створюємо чекбокс checkboxLoop для увімкнення/вимкнення циклічного відтворення звукового файлу. За стандартним налаштуванням циклічне відтворення звукового файлу вимкнено. |
7 | Прив’язуємо до checkboxLoop функцію changed() - слухача події зміни значення чекбокса. Аргумент сheckedLooping() - функція-обробник цієї події. |
8 | Створюємо кнопку btn з написом Відтворити. |
9 | Прив’язуємо до btn функцію mousePressed() - слухача події натискання кнопки. Аргумент switchPlaying() - функція-обробник цієї події. |
10 | Використовуємо функцію addCue() , за допомогою якої плануємо виклики користувацької функції changeBgColor() зі значенням кольору color(97, 83, 204) (color(4, 114, 77) , color(166, 0, 103) ) щоразу коли відтворення звукового файлу досягне 2 секунди (5 секунд, 9 секунд) від початку відтворення. |
11 | Встановлюємо початкове значення кольору bgColor для полотна. |
12 | У блоці draw() для функції background() використовується поточне значення кольору bgColor . |
13 | Для soundFile за допомогою функції setVolume() Архів встановлюємо максимальну гучність, використовуючи поточне значення слайдера sliderSetVolume , де гучність (амплітуда звукової хвилі) відтворення звукового файлу вимірюється у діапазоні між 0.0 (тиша) і 1.0 (повна гучність). |
14 | Для soundFile за допомогою функції rate() встановлюємо швидкість відтворення звукового файлу і тон відповідно, використовуючи поточне значення слайдера sliderRate . |
15 | Обчислюємо час t , який залишився до кінця одного відтворення звукового файлу. Для цього обчислюємо різницю результатів викликів двох функції: duration() (повертає тривалість звукового файлу в секундах) і currentTime() Архів (повертає поточне положення позиції відтворення від початку у секундах). |
16 | Розміщуємо значення t з пункту 15 у центрі полотна. |
17 | Оголошення користувацької функції-обробника changeBgColor() , яка що виклику буде змінювати значення кольору для полотна. |
18 | Оголошення користувацької функції-обробника switchPlaying() , яка що виклику буде змінювати стан кнопки btn . Якщо значення soundFile.isPlaying() , де функція isPlaying() повертає true , якщо звук відтворюється, інакше - false (тобто відтворення призупинено або зупинено), набуває значення false , файл soundFile буде відтворюватися за допомогою play() і на кнопці за допомогою функції html() буде встановлений напис пауза. Інакше - файл soundFile буде призупинений за допомогою pause() і на кнопці за допомогою функції html() буде встановлений напис продовжити. |
19 | Оголошення користувацької функції-обробника сheckedLooping() , яка для soundFile за допомогою функції setLoop() буде вмикати відтворення звуку у циклі (true ) чи ні (false ) в залежності від того, чи відзначений чекбокс (checkboxLoop.checked() ). Якщо звук вже відтворюється, ця зміна почне діяти після завершення поточного відтворення. |
Переглядаємо Аналізуємо
У результаті отримуємо прототип плеєра з базовими можливостями, як-от встановлення максимальної гучності звуку, швидкості та циклічності відтворення звукового файлу, водночас із простою візуалізацією - зміною кольору полотна, коли відтворення звукового файлу досягає певних часових позначок.
Ці часові мітки на відрізку часу відтворення звукового файлу жодним чином не прив’язані до властивостей звукового файлу - вони обрані нами випадково. Тому зміна кольору полотна хоча й додає певний візуальний ефект, але він є трохи відмежованим від процесу відтворення звукового файлу.
Отож, змінимо візуалізацію при відтворенні звукового файлу так, щоб ескіз реагував на зміну характеристик звукового файлу під час його відтворення.
Для цього на полотні намалюємо коло, яке буде змінювати свої розміри залежно від поточного значення гучності відтворення звукового файлу. Для цих цілей використаємо клас p5.Amplitude , щоб створити об’єкт, наприклад з ім’ям amplitude
, який буде містити низку характеристик, які пов’язані з амплітудою звукових коливань при відтворенні звукового файлу.
Щоб отримати поточне значення гучності (амплітуди), застосуємо для об’єкта amplitude
метод getLevel() , який повертає одне значення амплітуди в момент свого виклику.
Оскільки повернені поточні значення амплітуди є малими у порівнянні з величинами, які використовуються для малювання на полотні, використаємо функцію map() , яка для певної величини змінює діапазон значень, які вона може набувати.
Доповнимо код нашого плеєра згідно із нашими міркуваннями.
let sliderSetVolume, sliderRate, checkboxLoop, btn;
let soundFile, bgColor;
let amplitude; (1)
function preload() {
soundFile = loadSound("space-synth-melody_138bpm.wav");
}
function setup() {
createCanvas(200, 200);
sliderRate = createSlider(0.5, 1.5, 1, 0.1);
sliderSetVolume = createSlider(0.0, 1.0, 0.5, 0.1);
checkboxLoop = createCheckbox("циклічно", false);
checkboxLoop.changed(сheckedLooping);
btn = createButton("відтворити");
btn.mousePressed(switchPlaying);
soundFile.addCue(2, changeBgColor, color(97, 83, 204)); // Iris
soundFile.addCue(5, changeBgColor, color(4, 114, 77)); // Dark spring green
soundFile.addCue(9, changeBgColor, color(166, 0, 103)); // Murrey
bgColor = color(22, 27, 51); // Oxford Blue
textAlign(CENTER, CENTER);
amplitude = new p5.Amplitude(); (2)
}
function draw() {
background(bgColor);
soundFile.setVolume(sliderSetVolume.value());
soundFile.rate(sliderRate.value());
let t = soundFile.duration() - soundFile.currentTime();
fill(255);
text(t, width / 2, height / 6);
let currentAmplitude = amplitude.getLevel(); (3)
let d = map(currentAmplitude, 0, sliderSetVolume.value(), 1, width); (4)
noStroke();
fill(218, 98, 125); // Blush
ellipse(width / 2, height / 2, d); (5)
}
function changeBgColor(c) {
bgColor = c;
}
function switchPlaying() {
if (!soundFile.isPlaying()) {
soundFile.play();
btn.html("пауза");
} else {
soundFile.pause();
btn.html("продовжити");
}
}
function сheckedLooping() {
if (checkboxLoop.checked()) {
soundFile.setLoop(true);
} else {
soundFile.setLoop(false);
}
}
1 | Оголошуємо змінну з ім’ям amplitude , яке буде покликанням на об’єкт, створений класом p5.Amplitude . |
2 | Створюємо сам об’єкт поточного значення амплітуди за допомогою класу p5.Amplitude і надаємо йому ім’я amplitude . |
3 | За допомогою виклику методу getLevel() на об’єкті amplitude отримуємо щоразу поточне значення амплітуди currentAmplitude . |
4 | Перетворюємо значення currentAmplitude з діапазону від 0 до sliderSetVolume.value() (максимальне значення гучності, встановлене слайдером, може бути 1.0 ) у діапазон від 1 до значення ширини полотна width і зберігаємо під ім’ям d . |
5 | Малюємо коло в центрі полотна розміром d . |
Переглядаємо Аналізуємо
Використаємо інший спосіб візуалізації звуку - намалюємо статичний графік значень амплітуди звукового файлу.
Для цього на об’єкті звукового файлу застосуємо метод getPeaks() Архів, який повертає масив піків амплітуди звукового файлу. А далі використаємо значення цього масиву для малювання графіка.
Отож, код застосунку з попереднього прикладу зазнає таких змін.
let btn, checkboxLoop, soundFile;
let peaks; (1)
function preload() {
soundFile = loadSound("space-synth-melody_138bpm.wav");
}
function setup() {
createCanvas(700, 200);
checkboxLoop = createCheckbox("циклічно", false);
checkboxLoop.changed(сheckedLooping);
btn = createButton("відтворити");
btn.mousePressed(switchPlaying);
peaks = soundFile.getPeaks(width); (2)
}
function draw() {
background(245);
stroke(106, 76, 147); // Ultra Violet
for (let i = 0; i < peaks.length; i++) { (3)
line(
i,
height / 2 + peaks[i] * height / 2,
i,
height / 2 - peaks[i] * height / 2
);
}
let moment = map(soundFile.currentTime(), 0, soundFile.duration(), 0, width); (4)
stroke(242, 73, 163); // Rose Bonbon
line(moment, 0, moment, height); (5)
}
function switchPlaying() {
if (!soundFile.isPlaying()) {
soundFile.play();
btn.html("пауза");
} else {
soundFile.pause();
btn.html("продовжити");
}
}
function сheckedLooping() {
if (checkboxLoop.checked()) {
soundFile.setLoop(true);
} else {
soundFile.setLoop(false);
}
}
1 | Оголошуємо змінну з ім’ям peaks , яке буде покликанням на масив піків амплітуди звукового файлу soundFile . |
2 | Застосовуємо на об’єкті soundFile метод getPeaks() . Метод приймає один параметр - розмір масиву. Більші масиви забезпечують більш точну візуалізацію форми звукового сигналу (за стандартним налаштуванням, значення параметра - 5 * ширина вікна вебпереглядача). Цього разу використовується розмір масиву, що дорівнює ширині полотна width , тому у масиві 700 елементів. За ім’ям peaks можна отримати доступ до цього масиву відповідно. |
3 | У блоці draw() проходимо по масиву peaks , отримуємо значення peaks[i] конкретного піку амплітуди та використовуємо це значення для формування y -координат початку і кінця окремої вертикальної лінії. x -координати початку і кінця лінії збільшується завдяки зростанню лічильника i у циклі for . Оскільки значення масиву peaks є доволі маленькими, то для формування y -координат початку і кінця лінії використовуємо добуток peaks[i] * height / 2 . Половина конкретної лінії малюється вниз, а половина - вгору від центру полотна. |
4 | За допомогою функції map() змінюємо для значень soundFile.currentTime() (поточна позиція відтворення звукового файлу у реальному часі) діапазон значень від 0 до soundFile.duration() (тривалість звукового файлу в секундах) на діапазон від 0 до ширини полотна width і зберігаємо під ім’ям moment . Іншими словами, тривалість відтворення звукового файлу тепер дорівнює ширині полотна. |
5 | Малюємо вертикальну лінію одномоментно під час відтворення звукового файлу. |
Переглядаємо Аналізуємо
Вигляд графіка піків амплітуди для звукового файлу можна отримати, наприклад, відкривши аудіофайл в застосунку Audacity . |
Тепер змусимо графік динамічно рухатися в міру відтворення звукового файлу.
Для наших цілей створимо окремий масив amplitudes
, який буде містити поточні значення амплітуди, отримані у процесі відтворення аудіофайлу. А сам графік буде побудований за допомогою функцій beginShape()
, vertex()
і endShape()
.
let btn, sliderSetVolume, checkboxLoop, soundFile, amplitude;
let amplitudes = []; (1)
function preload() {
soundFile = loadSound("space-synth-melody_138bpm.wav");
}
function setup() {
createCanvas(700, 200);
sliderSetVolume = createSlider(0.0, 1.0, 1.0, 0.1);
checkboxLoop = createCheckbox("циклічно", false);
checkboxLoop.changed(сheckedLooping);
btn = createButton("відтворити");
btn.mousePressed(switchPlaying);
amplitude = new p5.Amplitude();
noFill();
}
function draw() {
background(245);
soundFile.setVolume(sliderSetVolume.value());
let currentAmplitude = amplitude.getLevel();
amplitudes.push(currentAmplitude); (2)
stroke(106, 76, 147); // Ultra Violet
push(); (3)
translate(0, -height / 2); (4)
beginShape(); (5)
for (let i = 0; i < amplitudes.length; i++) { (6)
let x = i;
let y = map(amplitudes[i], 0, 1, height, 0);
vertex(x, y); (7)
}
endShape(); (8)
pop(); (9)
if (amplitudes.length >= width / 2) { (10)
amplitudes.splice(0, 1);
}
stroke(242, 73, 163); // Rose Bonbon
line(amplitudes.length, 0, amplitudes.length, height); (11)
}
function switchPlaying() {
if (!soundFile.isPlaying()) {
soundFile.play();
btn.html("пауза");
} else {
soundFile.pause();
btn.html("продовжити");
}
}
function сheckedLooping() {
if (checkboxLoop.checked()) {
soundFile.setLoop(true);
} else {
soundFile.setLoop(false);
}
}
1 | Оголошуємо змінну з ім’ям amplitudes , яке буде покликанням на масив значень амплітуди. |
2 | Заповнюємо масив amplitudes поточними значеннями амплітуди під час відтворення звуку. Якщо звук не відтворюється, то масив заповнюється нулями (горизонтальна лінія у центрі полотна). |
3 | Зберігаємо поточний стан матриці трансформації, в якому початок системи координат у лівому верхньому куті полотна з координатами (0, 0) . |
4 | Переміщуємо початок системи координат у точку (0, -height / 2) - центр лівої вертикальної межі полотна. |
5 | Початок побудови фігури. |
6 | Проходячи у циклі по масиву amplitudes , значення лічильника i буде визначати x -координату, а y -координата буде формуватися з елементів amplitudes[x] масиву, діапазон значень яких від 0 до 1 буде перетворено у діапазон від height до 0 . |
7 | Побудова вершини з координатами (x, y) , обчисленими з пункту 6. |
8 | Завершення побудови фігури. |
9 | Відновлюємо стан матриці трансформації. |
10 | Умова, за виконання якої значення по одному будуть видалятися з масиву amplitudes від його початку. Це зроблено для імітації руху графіка горизонтально, справа наліво. Доки ця умова не виконується - графік рухається горизонтально, зліва направо. |
11 | Малюємо вертикальну лінію, x -координати побудови початку і кінця якої визначаються розміром масиву amplitudes . Коли масив amplitudes заповнюється (пункт 2), x -координата збільшується, а отже лінія рухається горизонтально зліва направо. Як тільки умова з пункту 10 виконується, розмір масиву стає сталим - x -координата не змінюється і лінія малюється увесь час у центрі полотна. |
Переглядаємо Аналізуємо
Побудуємо графік значень амплітуди (гучності) відтворення аудіофайлу у формі радіальної діаграми, яка використовується для зображення явищ, що періодично змінюються в часі, і водночас пригадаємо як використовувати полярні координати.
Код застосунку з попереднього прикладу знову зазнав змін, тому обов’язково ці зміни проаналізуємо.
let btn, sliderSetVolume, checkboxLoop, soundFile, amplitude;
let amplitudes = [];
function preload() {
soundFile = loadSound("space-synth-melody_138bpm.wav");
}
function setup() {
createCanvas(200, 200);
sliderSetVolume = createSlider(0.0, 1.0, 1.0, 0.1);
checkboxLoop = createCheckbox("циклічно", false);
checkboxLoop.changed(сheckedLooping);
btn = createButton("відтворити");
btn.mousePressed(switchPlaying);
amplitude = new p5.Amplitude();
angleMode(DEGREES); (1)
}
function draw() {
background(245);
soundFile.setVolume(sliderSetVolume.value());
let currentAmplitude = amplitude.getLevel();
amplitudes.push(currentAmplitude); (2)
stroke(106, 76, 147); // Ultra Violet
translate(width / 2, height / 2); (3)
fill(65, 66, 136); // Marian blue
beginShape();
for (let i = 0; i < 360; i++) { (4)
if (amplitudes[i]) {
let r = map(amplitudes[i], 0, 1, 1, height * 1.2); (5)
let x = r * cos(i); (6)
let y = r * sin(i);
vertex(x, y);
}
}
endShape();
if (amplitudes.length > 360) { (7)
amplitudes.splice(0, 1);
}
}
function switchPlaying() {
if (!soundFile.isPlaying()) {
soundFile.play();
btn.html("пауза");
} else {
soundFile.pause();
btn.html("продовжити");
}
}
function сheckedLooping() {
if (checkboxLoop.checked()) {
soundFile.setLoop(true);
} else {
soundFile.setLoop(false);
}
}
1 | Вмикаємо за допомогою функції angleMode() режим інтерпретації DEGREES (градуси) значень для функцій sin() і cos() . |
2 | Заповнюємо масив amplitudes поточними значеннями амплітуди під час відтворення звуку. Якщо звук не відтворюється, то масив заповнюється нулями. |
3 | Переміщуємо систему координат у центр полотна. |
4 | Проходимо у циклі до значення 360, де i - значення кута у градусах (повне коло 360 градусів) і формуємо координати вершин, які будуть побудовані за допомогою функції vertex() . Утворення координат вершин відбувається за умови існування елемента amplitudes[i] зі значенням, відмінним від 0 . Як вже було зазначено у пункті 2, якщо звук не відтворюється, а застосунок вже запущений, то масив буде заповнюватися нулями. Щоб функція map() коректно спрацювала, для цього і використовується умова існування елемента amplitudes[i] зі значенням, відмінним від 0 . |
5 | В ході циклу змінюємо діапазон від 0 до 1 у діапазон від 1 до height * 1.2 (за потреби обрати власні межі) для значення елемента amplitudes[i] масиву і зберігаємо це значення під ім’ям r . Саме r (радіус кола) та i (кут, під яким радіус проведений з центру кола до точки на колі) - полярні координати, які визначають координати точки - у цьому разі поточне значення амплітуди. |
6 | Оскільки вершина будується у декартових координатах (x, y) , перетворюємо полярні координати у декартові використовуючи тригонометричні функції. |
7 | Як тільки розмір масиву amplitudes стає більшим за 360 , значення з нього по одному будуть видалятися від його початку. Це зроблено для імітації обертання діаграми амплітуд. Доки ця умова не виконується - діаграма не обертається. |
Переглядаємо Аналізуємо
Використання мікрофона
Створимо візуальний ефект вуста, що говорять, використавши цього разу як джерело не завантажений в ескіз аудіофайл, а звук, який надходить на вхід системного чи зовнішнього мікрофонів.
let microphone; (1)
function setup() {
createCanvas(200, 200);
microphone = new p5.AudioIn(); (2)
microphone.start(); (3)
}
function draw() {
background(245);
let a = microphone.getLevel(); (4)
noStroke();
fill(252, 161, 125); // Atomic tangerine
ellipse(width / 2, height / 2, width, a * height); (5)
}
1 | Оголошуємо змінну з ім’ям microphone , яке буде покликанням на об’єкт мікрофона. |
2 | Створюємо на основі класу p5.AudioIn об’єкт мікрофона і надаємо йому ім’я microphone . |
3 | Застосовуємо метод start() на об’єкті microphone , щоб розпочати обробку вхідного аудіосигналу. |
4 | Зчитуємо амплітуду (рівень гучності) аудіовходу за допомогою методу getLevel() Архів (клас p5.AudioIn містить власний екземпляр класу p5.Amplitude , який допомагає легко отримати рівень гучності мікрофона) і зберігаємо під ім’ям a . |
5 | Малюємо вуста, що говорять у формі еліпса, використовуючи поточне значення амплітуди a , отримане у пункті 4. |
Переглядаємо Аналізуємо
Для коректної роботи застосунків із застосуванням мікрофона необхідно використовувати вебсервер. |
Цікавимось Додатково
Аналіз звуку
Якщо необхідно змусити ескіз реагувати на звуковий сигнал, найпростіше, що можна зробити, це змусити його реагувати на гучність сигналу - його амплітуду. Це дозволить анімувати ескіз, але лише на основі одного параметра. Що, якщо необхідно створити різні анімації для низьких і високих частот або використати діапазон частот для анімації різних частин ескізу?
У цьому разі швидке перетворення Фур’є (абревіатура FFT
від англ. Fast Fourier Transform
) може надати такі можливості.
FFT
- це математичний алгоритм, який аналізує форму сигналу та надає дані про його різні частоти. Після виконання аналізу вхідного аудіо FFT
надає детальний звіт про повний спектр частот і амплітуду кожного частотного діапазону. Використовуючи ці різні діапазони, можна зробити так, щоб ескіз по-різному реагував на низькі, середні або високі частоти сигналу.
Для цих цілей бібліотека p5.sound.js
містить клас p5.FFT , який є реалізацією алгоритму швидкого перетворення Фур’є і часто використовується для цифрової обробки сигналів для перетворення дискретних даних з часового у частотний діапазон.
Розглянемо код застосунку, в якому візуалізуємо обчислені значення амплітуди звукового файлу в частотному діапазоні.
let btn, sliderSetVolume, checkboxLoop, soundFile;
let fft, spectrum; (1)
function preload() {
soundFile = loadSound("space-synth-melody_138bpm.wav");
}
function setup() {
createCanvas(600, 200);
sliderSetVolume = createSlider(0.0, 1.0, 1.0, 0.1);
checkboxLoop = createCheckbox("циклічно", false);
checkboxLoop.changed(сheckedLooping);
btn = createButton("відтворити");
btn.mousePressed(switchPlaying);
fft = new p5.FFT(0.9); (2)
}
function draw() {
background(245);
soundFile.setVolume(sliderSetVolume.value());
spectrum = fft.analyze(); (3)
let w = width / 128; (4)
stroke(106, 76, 147); // Ultra Violet
fill(119, 118, 188); // Glaucous
for (let i = 0; i < spectrum.length; i++) { (5)
let s = spectrum[i];
let y = map(s, 0, 255, height, 0);
rect(i * w, y, w, height - y);
}
}
function switchPlaying() {
if (!soundFile.isPlaying()) {
soundFile.play();
btn.html("пауза");
} else {
soundFile.pause();
btn.html("продовжити");
}
}
function сheckedLooping() {
if (checkboxLoop.checked()) {
soundFile.setLoop(true);
} else {
soundFile.setLoop(false);
}
}
1 | Оголошуємо дві змінні з іменами: fft - покликання на об’єкт, створений на основі класу p5.FFT , spectrum - покликання на масив значень амплітуди (значення від 0 до 255 ) у частотному діапазоні. |
2 | Створюємо об’єкт fft . Значення 0.9 - це необов’язковий параметр, що визначає рівень згладжування у діапазоні від 0.0 до 1.0 (за стандартним налаштуванням 0.8 ). Другим необов’язковим параметром є довжина кінцевого масиву (16 , 32 , 64 , 128 і т. д. до 1024 так званих bins - бункерів, за стандартним налаштуванням їх 1024 ). |
3 | Використовуємо метод аналізу analyze() , який повертає spectrum - масив значень амплітуди (значення від 0 до 255 ) у частотному діапазоні довжиною 1024 за стандартним налаштуванням. Індекси масиву відповідають частотам (тобто висоті звуку), від найнижчої до найвищої, які може почути людина. Кожне значення представляє амплітуду в цій частині частотного діапазону. |
4 | Значення w визначає ширину стовпчика при візуалізації кожного значення масиву spectrum . |
5 | Проходимо через масив spectrum і малюємо прямокутники шириною w і висотою height - y , де y - вертикальна координата, яка формується за допомогою перетворення діапазону від 0 до 255 на діапазон від height до 0 для значень амплітуди spectrum[i] у частотному діапазоні з масиву spectrum . |
Переглядаємо Аналізуємо
Окрім того, для об’єкта, який створений на основі класу p5.FFT
, можна застосувати метод getEnergy() Архів для обчислення кількості енергії (амплітуди) на певній частоті або в діапазоні частот.
При виклику, цей метод як аргументи отримує числові значення частоти у герцах або рядок, що відповідає попередньо визначеним діапазонам частот: "bass"
- низькі, "lowMid"
- низькі середні, "mid"
- середні, "highMid"
- високі середні, "treble"
- високі.
Результат виклику getEnergy()
- діапазон від 0
(немає енергії на цій частоті) до 255
(максимальна енергія).
Оскільки метод analyze() змушує FFT аналізувати частотні дані, а getEnergy() використовує результати для визначення значення на певній частоті або діапазоні частот, метод analyze() потрібно викликати перед getEnergy() .
|
Створимо власну версію візуалізатора аудіо, реалізації якого можна зустріти у різних медіаплеєрах та у застосунках для візуалізації музики, які генерують анімовані зображення в реальному часі та певним чином синхронізують демонстрацію цих зображень з музикою під час її відтворення. Зміни в гучності та частотному спектрі музики належать до властивостей, які використовуються як вхідні дані для візуалізації.
Алгоритм виконання нашого завдання може складатися із таких кроків:
-
Створити малюнок на полотні.
-
Проаналізувати звук приєднаного аудіофайлу.
-
Додати анімацію до фрагментів малюнка на основі різних частотних діапазонів.
Отже, намалюємо на полотні кілька фігур, наприклад, як на малюнку.

Розглянемо код застосунку для створення фігур.
let bins = 1; (1)
function setup() {
createCanvas(200, 200);
noFill();
}
function draw() {
background(41, 39, 76); // Space cadet
let d = height / 4; (2)
translate(width / 2, height / 2); (3)
stroke(22, 152, 115); // Shamrock green
circle(0, 0, d);
for (i = 0; i < bins; i++) {
rotate(TWO_PI / bins); (4)
// лінія 1
stroke(244, 159, 188); // Amaranth pink
line(10, d / 2, 0, d);
// лінія 2
stroke(128, 93, 147); // Pomp and Power
line(-10, d / 2, 0, d);
// коло
stroke(255, 211, 186); // Apricot
circle(20, d * 1.2, d / 6);
// квадрат
stroke(158, 189, 110); // Olivine
square(0, d * 1.4, 10);
// трикутник
stroke(48, 242, 242); // Fluorescent cyan
triangle(d * 1.3, d * 1.1, d * 1.2, d * 1.1, d * 1.2, d * 1.3);
}
}
1 | Ініціалізуємо змінну bins зі значенням 1 , яка визначатиме кількість фігур одного типу. |
2 | Ініціалізумо змінну d зі значенням height / 4 (або на свій задум), яка буде використовувати у побудові фігур на полотні. |
3 | Переміщуємо систему координат в центр полотна. |
4 | Повертаємо систему координат на кут TWO_PI / bins і малюємо фігури із зазначеними властивостями. |
Тепер розглянемо код застосунку для аналізу звукового файлу.
Звуковий файл, що використовується у застосунку, можна завантажити за покликанням. |
let btn, sliderSetVolume, checkboxLoop, soundFile;
let fft, spectrum;
let bins = 32; (1)
function preload() {
soundFile = loadSound("space_125bpm_C_minor.wav");
}
function setup() {
createCanvas(200, 200);
sliderSetVolume = createSlider(0.0, 1.0, 1.0, 0.1);
checkboxLoop = createCheckbox("циклічно", false);
checkboxLoop.changed(сheckedLooping);
btn = createButton("відтворити");
btn.mousePressed(switchPlaying);
noFill();
fft = new p5.FFT(0.9, bins); (2)
}
function draw() {
background(41, 39, 76); // Space cadet
soundFile.setVolume(sliderSetVolume.value());
spectrum = fft.analyze(); (3)
let bass = fft.getEnergy("bass"); (4)
let lowMid = fft.getEnergy("lowMid");
let mid = fft.getEnergy("mid");
let highMid = fft.getEnergy("highMid");
let treble = fft.getEnergy("treble");
let custom = fft.getEnergy(100, 200);
let mapBass = map(bass, 0, 255, -100, 100); (5)
let mapLowMid = map(lowMid, 0, 255, -150, 150);
let mapMid = map(mid, 0, 255, -100, 100);
let mapHighMid = map(highMid, 0, 255, -50, 50);
let mapTreble = map(treble, 0, 255, -200, 200);
}
function switchPlaying() {
if (!soundFile.isPlaying()) {
soundFile.play();
btn.html("пауза");
} else {
soundFile.pause();
btn.html("продовжити");
}
}
function сheckedLooping() {
if (checkboxLoop.checked()) {
soundFile.setLoop(true);
} else {
soundFile.setLoop(false);
}
}
1 | Ініціалізуємо змінну bins зі значенням 32 , яке буде використовуватися для встановлення розміру масиву значень амплітуди, що повертає об’єкт FFT . |
2 | Створюємо новий об’єкт fft за допомогою класу p5.FFT з двома необов’язковими аргументами: 0.9 - визначає рівень згладжування у діапазоні від 0.0 до 1.0 (за стандартним налаштуванням 0.8 ) і bins - довжина кінцевого масиву значень амплітуди в частотній області (16 , 32 , 64 , 128 і т. д. до 1024 так званих bins - бункерів, за стандартним налаштуванням їх 1024 ), який отримується викликом методу analyze() на об’єкті fft . |
3 | Виклик методу analyze() на об’єкті fft . Доступ до масиву значень амплітуди в частотній області можна отримати за ім’ям spectrum . |
4 | Отримуємо амплітуди різних частотних діапазонів за допомогою виклику методу getEnergy() на об’єкті fft . |
5 | Створюємо діапазони значень на свій задум для кожного частотного діапазону. |
При виконанні застосунку файл буде лише відтворюватися. Щоб продемонструвати візуалізацію звукових даних, об’єднаємо обидва застосунки в один.
let btn, sliderSetVolume, checkboxLoop, soundFile;
let fft, spectrum;
let bins = 32;
function preload() {
soundFile = loadSound("space_125bpm_C_minor.wav");
}
function setup() {
createCanvas(200, 200);
sliderSetVolume = createSlider(0.0, 1.0, 1.0, 0.1);
checkboxLoop = createCheckbox("циклічно", false);
checkboxLoop.changed(сheckedLooping);
btn = createButton("відтворити");
btn.mousePressed(switchPlaying);
noFill();
fft = new p5.FFT(0.9, bins);
}
function draw() {
background(41, 39, 76); // Space cadet
soundFile.setVolume(sliderSetVolume.value());
spectrum = fft.analyze();
let bass = fft.getEnergy("bass");
let lowMid = fft.getEnergy("lowMid");
let mid = fft.getEnergy("mid");
let highMid = fft.getEnergy("highMid");
let treble = fft.getEnergy("treble");
let custom = fft.getEnergy(100, 200);
let mapBass = map(bass, 0, 255, -100, 100);
let mapLowMid = map(lowMid, 0, 255, -150, 150);
let mapMid = map(mid, 0, 255, -100, 100);
let mapHighMid = map(highMid, 0, 255, -50, 50);
let mapTreble = map(treble, 0, 255, -200, 200);
let d = height / 4;
translate(width / 2, height / 2);
stroke(22, 152, 115); // Shamrock green
circle(0, 0, d);
for (i = 0; i < spectrum.length; i++) {
rotate(TWO_PI / bins);
// лінія 1
stroke(244, 159, 188); // Amaranth pink
line(mapBass, d / 2, 0, d);
// лінія 2
stroke(128, 93, 147); // Pomp and Power
line(mapMid, d / 2, 0, d);
// коло
stroke(255, 211, 186); // Apricot
circle(mapTreble, d * 1.2, d / 6);
// квадрат
stroke(158, 189, 110); // Olivine
square(mapHighMid, d * 1.4, 10);
// трикутник
stroke(48, 242, 242); // Fluorescent cyan
triangle(
mapLowMid * 1.3,
mapLowMid * 1.1,
mapLowMid * 1.2,
mapLowMid * 1.1,
mapLowMid * 1.2,
mapLowMid * 1.3
);
}
}
function switchPlaying() {
if (!soundFile.isPlaying()) {
soundFile.play();
btn.html("пауза");
} else {
soundFile.pause();
btn.html("продовжити");
}
}
function сheckedLooping() {
if (checkboxLoop.checked()) {
soundFile.setLoop(true);
} else {
soundFile.setLoop(false);
}
}
У кінцевому коді візуалізатора аудіо на місці значень координат у функціях побудови фігур були використані змінні mapBass
, mapLowMid
, mapMid
, mapHighMid
, mapTreble
, що містять значення попередньо перетворених за допомогою функції map()
амплітуд різних частотних діапазонів.
Переглядаємо Аналізуємо
Ще один метод аналізу, який надає клас p5.FFT
, - це waveform() . Цей метод використовується для обчислення значення амплітуди в часовому діапазоні.
Застосування методу waveform()
на об’єкті, створеному на основі класу p5.FFT
, повертає масив значень амплітуди (від -1.0
до 1.0
) довжиною за стандартним налаштуванням 1024
. Індекси масиву відповідають коротким відрізкам часу, а кожне значення масиву представляє амплітуду хвилі за цей відрізок часу.
Отож, використаємо метод waveform()
для малювання звукової хвилі.
let btn, sliderSetVolume, checkboxLoop, soundFile, fft;
let wf; (1)
function preload() {
soundFile = loadSound("space-synth-melody_138bpm.wav");
}
function setup() {
createCanvas(200, 200);
sliderSetVolume = createSlider(0.0, 1.0, 1.0, 0.1);
checkboxLoop = createCheckbox("циклічно", false);
checkboxLoop.changed(сheckedLooping);
btn = createButton("відтворити");
btn.mousePressed(switchPlaying);
fft = new p5.FFT(0.9);
}
function draw() {
background(245);
soundFile.setVolume(sliderSetVolume.value());
wf = fft.waveform(); (2)
noFill();
beginShape();
stroke(20);
for (let i = 0; i < wf.length; i++) { (3)
let x = map(i, 0, wf.length, 0, width);
let y = map(wf[i], -1, 1, 0, height);
stroke(131, 50, 172); // Grape
vertex(x, y);
}
endShape();
}
function switchPlaying() {
if (!soundFile.isPlaying()) {
soundFile.play();
btn.html("пауза");
} else {
soundFile.pause();
btn.html("продовжити");
}
}
function сheckedLooping() {
if (checkboxLoop.checked()) {
soundFile.setLoop(true);
} else {
soundFile.setLoop(false);
}
}
1 | Оголошуємо змінну з ім’ям wf , яке буде покликанням на масив значень амплітуди. |
2 | Застосовуємо на об’єкті fft , створеному на основі класу p5.FFT , метод waveform() . Метод поверне масив значень амплітуди (від -1.0 до 1.0 ) довжиною за стандартним налаштуванням 1024 , доступ до якого можна буде отримати за допомогою імені wf . |
3 | Проходимо у циклі по масиву wf і за допомогою функцій beginShape() , vertex() і endShape() малюємо вершини, які утворюють форму хвилі. Координати кожної з вершин формуються за допомогою функції map() , а саме: для x -координати використовуємо значення лічильника i (відрізки часу) циклу for , яке перетворюємо з діапазону від 0 до wf.length у діапазон від 0 до width , а y -координату отримуємо зі значень елементів wf[i] масиву wf , яку аналогічно перетворюємо з діапазону від -1 до 1 у діапазон від 0 до height . |
Переглядаємо Аналізуємо
Синтез звуку
Окрім перелічених вище можливостей, за допомогою бібліотеки p5.sound.js
можна створювати (синтезувати) звук.
У цьому разі джерелом звуку є осцилятор, який генерує синтезовану версію звукової хвилі певної форми і частоти.
Осцилятор (від лат. oscillo - гойдаюся) - система, яка здійснює коливання, тобто показники якої періодично повторюються в часі.
|
Щоб переглянути різні форми осциляторів-генераторів, завітайте до музичної лабораторії Chrome - вебсайту, який робить навчання музики доступнішим за допомогою веселих практичних експериментів.
|
Розглянемо код застосунку, в якому створюється осцилятор і за допомогою вказівника миші змінюється його частота (висота звуку) і амплітуда (гучність звуку) коливань.
let osc; (1)
function setup() {
createCanvas(200, 200);
osc = new p5.Oscillator(); (2)
osc.setType("triangle"); (3)
}
function draw() {
background(225);
let fr = map(mouseX, 0, width, 0, 1000); (4)
let vo = map(mouseY, 0, height, 0.2, 0); (5)
osc.freq(fr); (6)
osc.amp(vo); (7)
}
function mousePressed() { (8)
osc.start();
}
function mouseReleased() { (9)
osc.stop();
}
1 | Оголошуємо змінну з ім’ям osc , яке буде покликанням на об’єкт осцилятора. |
2 | Створюємо об’єкт осцилятора за допомогою класу p5.Oscillator . |
3 | Зазначаємо за допомогою методу setType() тип для об’єкта осцилятора osc . За стандартним налаштуванням коливання має форму синусоїди ("sine" ). Додатковими типами осциляторів є: трикутник ("triangle" ), пилкоподібний ("sawtooth" ) і квадрат ("square" ). |
4 | За допомогою функції map() змінюємо діапазон значень для координати mouseX вказівника миші, яка формуватиме частоту коливань осцилятора fr , з діапазону від 0 до width на діапазон від 0 до 1000 . Частота за стандартним налаштуванням для створеного об’єкта осцилятора становить 440 Гц, що дорівнює висоті звуку ноти ля. |
5 | За допомогою функції map() змінюємо діапазон значень для координати mouseY вказівника миші, яка формуватиме амплітуду коливань (гучність звуку) осцилятора vo , з діапазону від 0 до height на діапазон від 0.2 до 0 . |
6 | Встановлюємо значення частоти fr у герцах для осцилятора osc за допомогою методу freq() . |
7 | Встановлюємо значення амплітуди vo для осцилятора osc за допомогою методу amp() . |
8 | Використання функції mousePressed() , яка викликається натисканням будь-якої кнопки миші будь-де. Настання цієї події запускає осцилятор за допомогою методу start() . |
9 | Використання функції mouseReleased() , яка викликається після того, як буде скасовано натискання будь-якої кнопки миші. Настання цієї події зупиняє осцилятор за допомогою методу stop() . |
Переглядаємо Аналізуємо
У результаті, коли на полотні відбувається рух вказівника миші вгору або вниз при затиснутій будь-якій кнопці миші - змінюється амплітуда коливань (гучність), а при горизонтальному русі - частота коливань осцилятора.
Цікавимось Додатково
Вправа 72
Доповнити застосунок з осцилятором візуалізацією - заливка полотна кольором, значення якого довільно змінюється за рухом вказівника миші.
Коли звучить реальний музичний інструмент, його гучність змінюється із часом. Якщо клавішу піаніно натиснути й утримувати, вона створює майже миттєвий початковий звук, гучність якого поступово зменшується до нуля. Гітара відтворює звук максимально голосно тільки в момент удару по струні, після чого він плавно загасає.
Для синтезу звуків більш наближених до природних та імітації звуків музичних інструментів у бібліотеці p5.sound.js
використовується клас p5.Envelope .
Об’єкти, створені на основі цього класу, називають конвертами. Вони описують, як звук змінюється з часом.
Наприклад, процес зміну амплітуди (гучності звуку) в часі можна представити графічно, де лінія графіка (ADSR
-обвідна) дозволяє симулювати вищезгадані особливості відтворення звуку у синтезаторах.

ADSR -обвідна (англ. ADSR envelope ) - спеціальної форми лінія, що використовується в синтезаторах (як апаратних, так і програмних) для контролю зміни якого-небудь параметра (частіше гучності - у цьому разі йдеться про амплітудну обвідну) у часі.
|
Абревіатура ADSR
складається з перших букв у їх назвах англійською мовою чотирьох параметрів, за допомогою яких вона задається:
-
Attack
(Атака) - визначає час, потрібний для того, щоб гучність ноти досягла свого максимального рівня. -
Decay
(Спадання) - визначає час, протягом якого відбувається перехід від максимального рівня до рівня Підтримки (Sustain
). -
Sustain
(Підтримка) - описує рівень звуку, що грає під час утримання клавіші (після того, як інші складові Атака й Спадання уже відіграли). -
Release
(Згасання) - визначає час, потрібний для остаточного згасання звучання ноти до нуля, після того, як клавішу відпустили.
Розглянемо код застосунку, в якому об’єкт конверта використовується для створення більш реалістичного звуку.
let osc, env; (1)
function setup() {
createCanvas(200, 200);
env = new p5.Envelope(); (2)
env.setADSR(0.01, 0.1, 0.5, 1); (3)
osc = new p5.Oscillator();
osc.setType("triangle");
osc.start();
osc.amp(env); (4)
}
function draw() {
background(245);
}
function mousePressed() { (5)
env.play();
}
1 | Оголошуємо змінну з ім’ям env , яке буде покликанням на об’єкт конверта. |
2 | За допомогою класу p5.Envelope створюємо об’єкт конверта з ім’ям env . |
3 | Застосовуємо до об’єкта env метод setADSR() з аргументами A , D , S і R . |
4 | Встановлюємо значення амплітуди для об’єкта осцилятора osc , використовуючи env - об’єкт, який визначає розподіл амплітуди в часі. |
5 | За допомогою методу play() конверт починає керувати гучністю. |
Переглядаємо Аналізуємо
Генератори обвідної, які дозволяють керувати відтворенням звуку, є загальними функціями синтезаторів, семплерів та інших електронних музичних інструментів.
Як відомо, для встановлення частоти осцилятора використовується метод freq() , який викликається на об’єкті осцилятора зі значенням частоти у герцах.
Значення частоти за стандартним налаштуванням для створеного об’єкта осцилятора становить 440 Гц, що дорівнює висоті звуку ноти ля.
Розглянемо код застосунку, який використовує частоти з музичного діапазону (звуки нот) стандарту MIDI .
У класичному буквеному позначенню звуків нот застосовується латинська абетка: А (ля), В (сі-бемоль), Н (сі), С (до), D (ре), Е (мі), F (фа), G (соль). Назви, номери MIDI та частоти нот можна переглянути за покликанням .
|
let osc, env;
function setup() {
createCanvas(100, 100);
env = new p5.Envelope();
env.setADSR(0.01, 0.1, 0.5, 0.25);
osc = new p5.Oscillator("triangle");
osc.start();
osc.amp(env);
}
function draw() {
if (mouseX < width / 2 && mouseIsPressed) { (1)
playNote(osc, 69, color(217, 114, 255), 0, 0, width / 2, height); // Heliotrope
} else if (mouseX > width / 2 && mouseIsPressed) { (2)
playNote(osc, 74, color(140, 255, 218), width / 2, 0, width, height); // Aquamarine
} else { (3)
background(245);
stroke(132, 71, 255); // Veronica
line(width / 2, 0, width / 2, height);
}
}
function mousePressed() {
env.play();
}
function playNote(o, midiNumber, c, x1, y1, x2, y2) { (4)
fill(c);
rect(x1, y1, x2, y2);
o.freq(midiToFreq(midiNumber));
}
1 | Якщо вказівник миші перебуває ліворуч від лінії, яка розділяє полотно вертикально, і будь-яка кнопка миші натиснута, то відбувається виклик користувацької функції playNote() з такими аргументами як: osc - об’єкт осцилятора (корисна опція у разі використання кількох осциляторів), 69 - MIDI -номер ноти, колір для зафарбовування частини полотна у формі клавіші і координати малювання лівої клавіші. |
2 | Якщо вказівник миші перебуває праворуч від лінії, яка розділяє полотно вертикально, і будь-яка кнопка миші натиснута, то відбувається виклик користувацької функції playNote() для малювання правої клавіші. |
3 | Інакше - полотно зафарбовується суцільним кольором і малюється вертикальна лінія. |
4 | Опис користувацької функції playNote() , у тілі якої використовується функція midiToFreq() Архів, яка повертає значення частоти для midiNumber - цілочисельного значення MIDI -ноти. Отримане значення частоти у герцах встановлюється для переданого у функцію playNote осцилятора o . |
Протилежно midiToFreq() працює функція freqToMidi() Архів - вона повертає найближче значення MIDI -ноти для зазначеної частоти.
|
Переглядаємо Аналізуємо
6.6.4. Ресурси
Корисні джерела
6.6.5. Контрольні запитання
Міркуємо Обговорюємо
-
Що таке «мультимедійні дані»?
-
Опишіть алгоритм приєднання зовнішньої бібліотеки до ескізу.
-
Які можливості надає бібліотека
p5.sound.js
для створення та обробки звуку?
6.6.6. Практичні завдання
Початковий
-
Перетворити зображення, яке утворюється будь-яким із ваших застосунків, в
ASCII art
за допомогою бібліотекиp5.asciiart.js
.
-
Створити застосунок Ударна установка, в якому окремі клавіші клавіатури відтворюють звуки ударних інструментів. До ударної установки входять тарілки креш, райд, хай-хети, навісні та підлоговий том-томи, малий барабан та бас-барабан, а також іноді інші ударні інструменти. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Звукові файли, що використовуються у застосунку, можна завантажити за покликанням. |
Середній
-
Використати у своєму проєкті бібліотеку з колекції на вибір.
-
Створити застосунок, в якому за допомогою сили голосу, який подається на вхід мікрофона, відбувається підняття кульки на певну висоту. Орієнтовний взірець роботи застосунку представлений в демонстрації (без звуку).
-
Використовуючи поданий нижче код за основу, додати звукове оформлення у застосунок, щоб відбивання кульки від меж полотна супроводжувалося звуком.
let d = 50;
let x = d;
let y = d;
let velocityX = 0.5;
let velocityY = 0.8;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(245);
noStroke();
fill(0, 159, 183); // Moonstone
circle(x, y, d);
if (x > width - d / 2 || x < d / 2) {
velocityX = velocityX * -1;
}
if (y > height - d / 2 || y < d / 2) {
velocityY = velocityY * -1;
}
x += velocityX;
y += velocityY;
}
Високий
-
Створити застосунок, у якому за допомогою натискання кнопки миші на полотні малюються випадковим чином фігури довільного кольору. Розміри фігур визначаються частотою коливань синтезованого звуку. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок -
MIDI
-клавіатуру. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок, який візуалізує звук. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Звуковий файл, що використовується у застосунку, можна завантажити за покликанням. |
Екстремальний
-
Створити застосунок - візуалізатор аудіо, в якому окремі елементи ескізу змінюють свої властивості в залежності від характеристик звуку (амплітуда, частота). Наприклад, в демонстрації частинки, що розлітаються від центру полотна, прискорюються для певного частотного діапазону звукоряду. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Звуковий файл і зображення тла, що використовуються у застосунку, можна завантажити за покликанням. |
-
Додати звуки у гру Змійка із практичного завдання 4 Розділу 5.5.3.
Звукові файли, що використовуються в застосунку, можна завантажити за покликанням. |
7. Інтерфейс програмного продукту
У цьому розділі ви навчитеся створювати інтерфейси користувача, отримувати дані від зовнішніх джерел та їх візуалізувати. |
Більшість із нас щодня взаємодіє з різними типами програмних продуктів (англ. software product
, software as a product
- SaaP
) - програмним забезпеченням, що розроблене для розв’язування різноманітних задач як у навчанні, так і у дозвіллі.
Прикладами таких програмних продуктів є пакунки офісних застосунків (наприклад, LibreOffice
) для роботи з різними типами файлів документів, засоби для проведення відеоконференцій та онлайн-зустрічей (наприклад, Google Meet
, Zoom
), хмарні сховища даних (Google Диск
, OneDrive
), середовища розробки (наприклад, Processing
, Replit
) та інші.
Зазвичай програмне забезпечення встановлюється на комп’ютерний пристрій, але й надається як сервіс (англ. Software as a service
, SaaS
).
Користувач взаємодіє із програмним продуктом завдяки інтерфейсу користувача, який повинен забезпечувати зручність використання, бути інтуїтивно зрозумілим і максимально дружнім до користувача. Тому, одним із важливих моментів у розробці програмного забезпечення є проєктування орієнтованого на людину інтерфейсу.
Бібліотека p5.js
- це програмний продукт, призначений для створення графічних та інтерактивних вебзастосунків. Для створення ефективного та зручного інтерфейсу p5.js
надає набір інструментів, таких як кнопки, слайдери, поля для введення та інші елементи керування, які полегшують взаємодію користувача із застосунком.
Застосунки, які створюються за допомогою бібліотеки p5.js , можна також віднести певною мірою до програмних продуктів.
|
7.1. Програмний код, графічний інтерфейс користувача та джерела даних
7.1.1. Типи інтерфейсів
Якщо для взаємодії з користувачем програмний продукт використовує елементи дизайну, такі як кнопки, меню, поля для введення, графіки, візуалізації даних тощо, то у цьому разі мова йде про графічний інтерфейс (англ. Graphical user interface
, GUI
).

Загалом графічний інтерфейс може мати такі складові:
-
меню та навігацію - допомагають користувачу переходити між різними функціями чи сторінками застосунку;
-
елементи керування - елементи, за допомогою яких користувач може інтерактивно взаємодіяти із застосунком (кнопки, чекбокси, радіокнопки, перемикачі, списки тощо);
-
вікна - відкриваються для введення або відображення додаткової інформації, а також діалогові вікна для попереджень, підтверджень, сповіщень про результати дій, помилки тощо;
-
адаптивність - можливість інтерфейсу пристосовуватися до різних типів пристроїв і розмірів екранів, щоб забезпечити комфортну роботу застосунку на різних пристроях.
Графічний інтерфейс грає важливу роль у взаємодії користувача із програмним продуктом, забезпечуючи йому зручність, доступність та ефективність його використання. Він має бути добре спроєктований і відповідати потребам цільової аудиторії.
Інший тип інтерфейсу - інтерфейс командного рядка (англ. Command-Line Interface
, CLI
), особливостями якого є:
-
взаємодія відбувається шляхом введення текстових інструкцій, де кожна інструкція виконує певну дію;
-
інструкції можуть містити додаткові параметри, які вказують додаткові опції або аргументи для виконання інструкції;
-
окрім інструкцій,
CLI
дозволяє виконувати скрипти - набори інструкцій, які автоматизують рутинні завдання; -
CLI
може бути дуже продуктивним, хоча й має обмежені візуальні можливості порівняно з графічним інтерфейсом, але це може зробити його менш вимогливим до ресурсів і швидшим у використанні.
У CLI
користувач вводить команди в текстовому форматі у спеціальному вікні, яке називають вікном командного рядка (Windows
) чи терміналом (Linux
), або в консолі (наприклад, середовище виконання інструкцій мови програмування JavaScript
у вебпереглядачі) та отримує результати їх виконання.



Інтерфейс командного рядка використовується в операційних системах, серверних застосунках тощо і надає користувачу прямий контроль над застосунком, і в багатьох випадках він є незамінним інструментом для адміністрування та керування.
7.1.2. Графічний інтерфейс
Окрім безпосередньої взаємодії із застосунком за допомогою миші чи клавіатури, на вебсторінку ескізу можна додавати різні елементи графічного інтерфейсу, які роблять таку інтерактивну взаємодію більш зручною.
Розглянемо приклади створення складових графічного інтерфейсу на вебсторінці ескізу за допомогою створення HTML
-елементів у DOM, які визначатимуть кнопки, слайдери (повзунки), текстові поля, чекбокси (множинний вибір з декількох варіантів), радіокнопки (перемикачі), списки із вибором, палітру кольорів.
Кнопка
Кнопки є популярними елементами у будь-якому графічному інтерфейсі. Вони дозволяють викликати певні функції або події при їхньому натисканні.
Напишемо код застосунку, в якому використовується кнопка для зміни кольору зафарбовування кола на полотні.
let g, btn; (1)
function setup() {
createCanvas(200, 200);
btn = createButton("Встановити колір"); (2)
btn.mousePressed(changeColor); (3)
g = color(42, 157, 143); // Persian green (4)
noStroke();
}
function draw() {
background(220);
fill(g); (6)
circle(width / 2, height / 2, width / 4);
}
function changeColor() { (5)
g = color(random(0, 255), random(0, 255), random(0, 255));
}
1 | Оголошуємо глобальні змінні: btn - для об’єкта кнопки та g - для кольору тла полотна. |
2 | Створюємо HTML -елемент кнопки (<button> ) за допомогою функції createButton() з написом на ній. |
3 | Для кнопки btn застосовуємо метод mousePressed() , який викликається один раз після кожного натискання кнопки миші на елементі кнопки. Цей метод є слухачем події натискання кнопки миші на елементі кнопки. Він отримує як аргумент функцію changeColor() , яка є обробником цієї події. |
4 | Ініціалізуємо змінну g початковим значенням кольору для зафарбовування кола. |
5 | Описуємо обробник події натиснення кнопки btn - функцію changeColor() , у тілі якої встановлюємо випадкове нове значення кольору і зберігаємо його з ім’ям g . |
6 | При натисканні будь-якої кнопки миші вбудована функція fill() зафарбовує коло значенням g . |
Метод mousePressed() відстежує лише подію натиснення на кнопці btn , тому зміна кольору відбувається лише у разі натискання кнопки btn , а не у будь-якому місці вікна перегляду.
|
Переглядаємо Аналізуємо
Елемент кнопки створюється поза полотном за стандартним налаштуванням. |
Напишемо код ще одного застосунку, в якому використовуватиметься кнопка, натискання на яку викличе зміну кольору тла полотна ескізу.
let btn, colorBg = 120; (1)
function setup() {
createCanvas(200, 200);
btn = createButton("Змінити колір"); (2)
btn.position(10, 10); (3)
btn.mousePressed(changeColor); (4)
}
function draw() {
background(colorBg); (5)
}
function changeColor() { (6)
colorBg = color(random(255), random(255), random(255));
}
1 | Оголошуємо глобальну змінну btn для об’єкта кнопки та ініціалізуємо глобальну змінну colorBg з початковим значенням 120 кольору тла полотна. |
2 | Створюємо HTML -елемент кнопки (<button> ) за допомогою функції createButton() з написом на ній. |
3 | Використовуємо метод position() для встановлення координат положення кнопки на полотні відносно точки (0, 0) - верхнього лівого кута полотна. |
4 | Як і в попередньому прикладі для кнопки btn застосовуємо метод mousePressed() , який викликається один раз після кожного натискання кнопки миші на елементі кнопки. Цей метод є слухачем події натискання кнопки миші на елементі кнопки. Він отримує як аргумент функцію changeColor() , яка є обробником цієї події. |
5 | Встановлюємо колір полотна відповідно до значення colorBg . |
6 | Оголошуємо функцію з назвою changeColor() , яка буде обробником події у пункті 4. У тілі цієї функції змінна colorBg щоразу при натисканні кнопки набуватиме випадкового значення кольору. |
Коли запустити застосунок, щоразове натискання вказівника миші на кнопці змінюватиме колір тла полотна.
Метод mousePressed() відстежує лише подію натиснення на кнопці btn , тому зміна кольору відбувається лише у разі натискання кнопки btn , а не у будь-якому місці вікна перегляду.
|
Перед тим, як переглянути результати роботи застосунку, додамо оформлення для самої кнопки.
Зробимо це за допомогою файлу стилів style.css
html,
body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
button {
padding: 4px;
background-color: transparent;
color: white;
border: 1px solid #efefef;
border-radius: 3px;
box-shadow: 3px 3px 2px 1px rgba(0, 0, 0, 0.1);
}
який приєднаємо у файлі index.html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css" />
<meta charset="utf-8" />
</head>
<body>
<main></main>
<script src="sketch.js"></script>
</body>
</html>
Стилі, які будуть застосовані до кнопки:
-
padding: 4px;
- внутрішні відступи (відступи зазначають у такому порядку: вгорі-справа-унизу-зліва; оскільки є єдине значення4px
, воно використовується для усіх напрямків); -
background-color: transparent;
- прозоре тло; -
color: white;
- колір тексту на кнопці - білий; -
border: 1px solid #efefef;
- суцільна межа завтовшки в1px
кольору#efefef;
; -
border-radius: 3px;
- округлені кути зовнішньої рамки елемента радіусом3px
; -
box-shadow: 3px 3px 2px 1px rgba(0, 0, 0, 0.1);
- тінь з параметрами у зазначеному порядку:3px
- зсув по горизонталі (додатне значення, тому вправо),3px
- зсув по вертикалі (додатне значення, тому униз),2px
- радіус розмиття,1px
- розтягування тіні,rgba(0, 0, 0, 0.1)
- колір тіні.
Переглядаємо Аналізуємо
Розглянемо ще один застосунок, в якому кнопка буде використовуватися для зміни розмірів полотна з певними кроками для ширини й висоти відповідно.
Для цих цілей скористаємось функцією resizeCanvas() , яка змінює розміри полотна до заданих ширини та висоти. Також використаємо для стилізації кнопки файл зі стилями із попереднього прикладу.
У коді застосунку ініціалізуємо дві змінні x
та y
значеннями кроків зміни полотна по горизонталі й вертикалі відповідно.
let btn;
let x = 2;
let y = -4;
function setup() {
createCanvas(200, 200);
btn = createButton("Змінити розміри");
btn.position(10, 10);
btn.mousePressed(changeSize);
}
function draw() {
background(9, 188, 138); // Mint
}
function changeSize() {
resizeCanvas(width + x, height + y);
}
Обробник події натискання кнопки, функція changeSize()
, у своєму тілі використовує функцію зміни розмірів полотна resizeCanvas()
. Початкові значення (200
і 200
) розмірів полотна зберігаються у системних змінних width
і height
відповідно.
Розміри полотна після чергового натискання на кнопку будуть такими (у рядку перше число - розміри по горизонталі, друге число - по вертикалі):
202 196
204 192
206 188
...
Переглядаємо Аналізуємо
Як бачимо, у демонстрації по ширині полотно збільшується на 2 пікселі, а по висоті зменшується на 4 пікселі. При цьому полотно очищається після кожного натискання на кнопку, а ескіз повторно відображається на полотні, яке має вже нові розміри.
Вправа 73
Створити застосунок із двома кнопками, у якому при натисканні на першу кнопку - полотно збільшується в розмірах, а на другу - зменшується. Зміна розмірів відбувається одночасно по горизонталі й по вертикалі.
Слайдер
Слайдери (англ. slider; укр. повзунок) дозволяють користувачам вибирати значення в заданому діапазоні даних. Даними у цьому разі можуть бути числові значення складових кольору, координат тощо.
Розглянемо код застосунку, в якому при переміщенні повзунка слайдера змінюється діаметр кола у діапазоні від 0 до ширини квадратного полотна.
let slider; (1)
function setup() {
createCanvas(200, 200);
slider = createSlider(0, width, width / 2, 2); (2)
slider.position(10, 10); (3)
slider.style("width", "80px"); (4)
noStroke();
}
function draw() {
background(220);
fill(96, 225, 224); // Tiffany Blue
let d = slider.value(); (5)
circle(width / 2, height / 2, d); (6)
}
1 | Оголошуємо глобальну змінну slider , яка зберігатиме покликання на об’єкт слайдера. |
2 | Створюємо слайдер (HTML -елемент <input> ) за допомогою функції createSlider() . Аргументами, які передаються у функцію, є: початкове значення (0 ) діапазону даних, кінцеве значення (width ) діапазону даних, значення за стандартним налаштуванням (width / 2 ), необов’язковий крок зміни значень (2 ). |
3 | Використовуємо метод position() для встановлення координат слайдера на полотні відносно точки (0, 0) - верхнього лівого кута полотна. |
4 | Застосовуємо метод style() , який встановлює для слайдера ширину - додає CSS -властивість width зі значенням 80px . Аналогічно це можна зробити за допомогою методу size() , який встановлює ширину і висоту елемента. У цьому разі використовується лише значення ширини: slider.size(80) . |
5 | Ініціалізуємо локальну змінну d із поточним значенням слайдера (значення, яке відповідає розташуванню повзунка), використовуючи метод value() . |
6 | Використовуємо значення d як значення діаметра кола у функції circle() . |
У функції draw()
змінна d
ініціалізується на кожній ітерації поточним значенням слайдера. Завдяки цьому ми отримуємо актуальне значення даних зі слайдера при переміщенні повзунка, яке відразу встановлюється для кола.
Переглядаємо Аналізуємо
Вправа 74
Створити застосунок із слайдером для зміни кольору тла полотна ескізу. Для кольору використовується одне ціле значення - колір встановлюється в градаціях сірого (між білим і чорним).
Текстове поле
Важливим елементом будь-якого інтерфейсу є текстове поле, що використовується для введення текстових даних.
У бібліотеці p5.js
для створення текстового поля використовують функцію createInput() , яка створює у DOM
елемент <input>
для введення тексту.
Розглянемо код застосунку, в якому текст, який вводиться у текстове поле, відображається в консолі вебпереглядача.
let inp; (1)
function setup() {
createCanvas(200, 200);
inp = createInput("тут ввести текст"); (2)
inp.input(printInput); (3)
noStroke();
}
function draw() {
background(220);
text(inp.value(), 10, 20); (5)
}
function printInput() {
console.log("ви друкуєте:", inp.value()); (4)
}
-
Оголошуємо глобальну змінну
inp
, яка буде містити покликання на текстове поле. -
Створюємо текстове поле за допомогою функції
createInput()
зі значенням за стандартним налаштуванням (атрибутvalue
для елемента<input>
). -
Використовуємо функцію input() - слухач події введення у текстове поле
inp
, який отримує обробник цієї події - користувацьку функціюprintInput()
. -
Оголошуємо користувацьку функцію
printInput()
- обробник події введення у текстове полеinp
. Функція друкує в консолі значення введеного тексту у текстове поле за допомогою методаvalue()
, який застосовується дляinp
. -
За допомогою функції
text()
на полотні у визначених координатах виводимо значення введеного тексту за допомогою методуvalue()
, який застосовується дляinp
.
Результати введення тексту в текстове поле після його очищення від тексту за стандартним налаштуванням:
ви друкуєте: ""
ви друкуєте: J
ви друкуєте: Ja
ви друкуєте: Jav
ви друкуєте: Java
ви друкуєте: JavaS
ви друкуєте: JavaSc
ви друкуєте: JavaScr
ви друкуєте: JavaScri
ви друкуєте: JavaScrip
ви друкуєте: JavaScript
Використаємо у нашому прикладі з текстовим полем як слухача функцію changed() , яка відстежує випадок, коли значення елемента змінюється.
Для текстового поля зміна значення - введення тексту і підтвердження клавішею Enter чи натискання поза межами текстового поля. Замінивши у коді слухача події
...
inp.changed(printInput);
...
отримаємо результат, коли введений текст у текстове поле буде друкуватися в консолі лише після підтвердження його введення.
Розглянемо застосунок в якому колір тексту на полотні змінюється відповідно введеної у текстове поле назви кольору англійською. Наприклад, якщо у текстове поле введено слово red
, текст стає червоного кольору і т. д.
let inp, textColor = 0;
function setup() {
createCanvas(200, 200);
inp = createInput("Введіть назву кольору");
inp.position(10, 10);
inp.changed(changeTextColor);
textSize(40);
textAlign(CENTER, CENTER);
}
function draw() {
background(220);
fill(textColor);
text("p5.js", width / 2, width / 2);
}
function changeTextColor() {
textColor = inp.value();
}
Переглядаємо Аналізуємо
Вправа 75
Використати код попереднього застосунку для зміни розміру тексту на полотні за допомогою введеного у текстове поле цілочисельного значення розміру літер у пікселях.
Чекбокс, радіокнопка, список із вибором
Розглянемо код застосунку, у якому використовуються вже розглянуті елементи графічного інтерфейсу та нові, серед яких:
-
чекбокси (множинний вибір з декількох варіантів);
-
радіокнопки (перемикачі);
-
списки із вибором.
У коді застосунку використовується функція createP() , яка створює у DOM елемент абзацу <p> .
|
let inp, btn, radio, sel, checkbox; (1)
function setup() {
createCanvas(200, 200);
createP("Оцініть застосунок:");
radio = createRadio(); (2)
radio.option("чудово");
radio.option("добре");
radio.option("так собі");
radio.selected("чудово");
radio.style("width", "80px");
createP("Що змінити в застосунку насамперед?");
sel = createSelect(); (3)
sel.option("жодних змін не потрібно");
sel.option("стильове оформлення");
sel.option("додати більше об'єктів");
sel.option("збільшити розмір полотна");
sel.selected("жодних змін не потрібно");
createP("Додатковий коментар:");
inp = createInput("відсутній"); (4)
createP("Опублікувати відгук для усіх?");
checkbox = createCheckbox("так", false); (5)
createP("Перевірте введені дані та натисніть кнопку");
btn = createButton("Надіслати"); (6)
btn.mousePressed(sendFeedback); (7)
noStroke();
}
function draw() {
background(220);
fill(42, 157, 143); // Persian green
circle(width / 2, height / 2, width / 4);
}
function sendFeedback() { (8)
console.log("Ваша оцінка:", radio.value());
console.log("Ваші зауваги:", sel.value());
console.log("Ваш коментар:", inp.value());
if (checkbox.checked()) {
console.log("Вашу оцінку будуть бачити усі.");
}
console.log("Дякуємо за відгук!\n");
}
1 | Описуємо глобальні змінні для зберігання елементів інтерфейсу. |
2 | За допомогою функції createRadio() створюємо елемент перемикача у DOM і зберігаємо з ім’ям radio . За допомогою властивості option для radio визначаємо значення усіх перемикачів. Метод selected() робить обраний перемикач позначеним, а властивість style додає вбудований стиль для елемента <div> , який є батьківським для перемикачів, - ширину 80 пікселів. |
3 | За допомогою функції createSelect() створюємо список із вибором у DOM і зберігаємо з ім’ям sel . За допомогою властивості option визначаємо елементи списку, а метод selected() встановлює обраний елемент списку за стандартним налаштуванням. |
4 | Використовуємо функцію createInput() для створення текстового поля з ім’ям inp . |
5 | Використовуємо функцію createCheckbox() для створення елемента прапорця у DOM з ім’ям checkbox . |
6 | Використовуючи функцію createButton() створюємо кнопку з ім’ям btn . |
7 | До кнопки приєднуємо функцію mousePressed() - слухач події натиснення на кнопку btn і передаємо їй як аргумент обробник цієї події - функцію sendFeedback() . |
8 | Оголошуємо функцію sendFeedback() - обробник події натиснення на кнопку btn . У тілі функції-обробника звертаємось до значень створених елементів графічного інтерфейсу за допомогою метода value() . Виклик метода checked() , який повертає істину, якщо прапорець позначений, інакше - хибність, для прапорця checkbox використовуємо у розгалуженні. Для зручності читання результатів символ нового рядка \n розділяє виведення повідомлень в консолі вебпереглядача. |
Якщо запустити застосунок і відразу натиснути кнопку надсилання, у консолі вебпереглядача отримаємо значення, які встановлені для елементів за стандартним налаштуванням:
Ваша оцінка: чудово
Ваші зауваги: жодних змін не потрібно
Ваш коментар: відсутній
Дякуємо за відгук!
Вправа 76
Поміркуйте, значення яких елементів інтерфейсу зазнали змін, якщо в консолі отримали наступні результати:
Ваша оцінка: добре
Ваші зауваги: додати більше об'єктів
Ваш коментар: цікавий застосунок
Вашу оцінку будуть бачити усі.
Дякуємо за відгук!
Палітра кольорів
У комп’ютерній графіці термін «палітра» вказує на набір доступних кольорів, які можуть бути використані для створення зображення.
Бібліотека p5.js
має спеціальну функцію createColorPicker() для створення палітри кольорів. Ця функція створює у DOM
елемент colorPicker
для вибору кольору.
Розглянемо використання палітри кольорів для зміни кольору тла полотна на прикладі наступного застосунку.
let colorPicker, bgColor = 220; (1)
function setup() {
createCanvas(200, 200);
colorPicker = createColorPicker("#ff0000"); (2)
colorPicker.position(10, 10); (3)
colorPicker.input(changeBgColor); (4)
}
function draw() {
background(bgColor); (6)
}
function changeBgColor() {
bgColor = this.color(); (5)
}
1 | Оголошуємо глобальну змінну colorPicker , ім’я якої буде посиланням на об’єкт палітри, та ініціалізуємо bgColor зі значенням 220 , що визначає початковий колір (сірий) тла полотна. |
2 | Створюємо об’єкт палітри за допомогою функції createColorPicker() зі значенням рядка, який описує колір у шістнадцятковому вигляді, що буде встановлений на палітрі за стандартним налаштуванням. У цьому разі це червоний колір. |
3 | Використовуємо метод position() для розташування палітри кольорів на полотні відносно точки (0, 0) - верхнього лівого кута полотна. |
4 | Використовуємо функцію input() - слухач події вибір кольору, який отримує обробник цієї події - користувацьку функцію changeBgColor() . |
5 | Користувацька функція changeBgColor() у своєму тілі за допомогою методу color() повертає об’єкт p5.Color із поточним вибраним кольором. Значення кольору присвоюємо змінній bgColor . |
6 | Використовуємо значення bgColor для встановлення кольору тла полотна. |
Переглядаємо Аналізуємо
Розглянуті елементи графічного інтерфейсу, які можна легко реалізувати за допомогою p5.js
, відкривають безліч можливостей для створення динамічних та інтерактивних ескізів, а комбінування їх дозволяє легко взаємодіяти з користувачем та розробляти захопливі вебзастосунки.
7.1.3. Ресурси
Корисні джерела
7.1.4. Контрольні запитання
Міркуємо Обговорюємо
-
Які елементи можуть входити до графічного інтерфейсу?
-
Що таке «слухач події»?
-
Для чого використовується «обробник події»?
7.1.5. Практичні завдання
Початковий
-
Створити застосунок, який дозволяє користувачеві вибирати колір з палітри для зафарбовування полотна. На полотні також друкується значення кольору. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок з двома кнопками, які призначені для генерації випадкових значень кольору для тла полотна і фігур відповідно. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок із чотирма слайдерами. Перші три - керують значеннями трьох складових кольору, а четвертий - прозорістю. Застосувати слайдери для зміни кольору і прозорості будь-якої фігури на полотні. Орієнтовний зразок роботи застосунку представлений в демонстрації.
Середній
-
Створити застосунок, в якому за допомогою кнопки можна запускати та зупиняти почергово обертання фігури. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок, в якому за допомогою елементів інтерфейсу можна змінювати властивості геометричної фігури як представлено у демонстрації.
Високий
-
Розробити гру «Вгадай число» з графічним інтерфейсом. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок-таймер, який веде зворотний відлік часу від введеного значення у секундах. Для запуску таймера використовується кнопка. Коли час спливає, виводиться повідомлення про те, що час минув. Орієнтовний зразок роботи застосунку представлений в демонстрації.
-
Створити простий графічний редактор з інструментами малювання (пензель, гумка, товщина пензля/гумки, зберігання у файл), використовуючи елементи інтерфейсу. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Екстремальний
-
Створити застосунок, який використовує кнопки для додавання на полотно випадкових геометричних фігур із певними властивостями. За допомогою вказівника миші зайві фігури можна видалити. Орієнтовний зразок роботи застосунку представлений в демонстрації.
-
Створити прототип музичного плеєра, який використовує елементи інтерфейсу для відтворення музичних треків. Передбачити можливість паузи та продовження відтворення, регулювання гучністю, зациклення, вибір треків і за потреби візуалізацію. Орієнтовний зразок роботи застосунку представлений в демонстрації.
Завантажити файли музичних треків, які використовуються у демонстрації, можна за покликанням. |
-
Створити емуляцію модального вікна як у вебпереглядачі з можливістю налаштування його за допомогою слайдерів. Орієнтовний взірець роботи застосунку представлений в демонстрації.
7.2. Зовнішні джерела даних
Дотепер дані, які використовувались у застосунках, створювалися за допомогою змінних та структур даних (масиви, об’єкти) мови програмування JavaScript
безпосередньо у коді ескізу під час його виконання.
Дані можна отримати також з різних зовнішніх джерел, серед яких бази даних, сторонні API тощо. Дані зазвичай організовані у файли різних форматів. Щоб прочитати дані із файлу, файл завантажують локально або отримують доступ до нього віддалено.
Розглянемо можливості бібліотеки p5.js
для роботи з даними, отриманими із зовнішніх джерел, як-от CSV
- і JSON
-файли.
7.2.1. CSV
CSV-файли - звичайні текстові файли з розширенням .csv
, в яких дані розміщені у рядках і стовпцях та відокремлені один від одного символом коми (,
).
У першому рядку, який називається рядком заголовків і може бути відсутнім, CSV
-файлу розміщуються імена стовпців, розділені комами. Кожен рядок даних знаходиться в наступних рядках CSV
-файлу і містить значення, також розділені комами.
Усі рядки CSV -файлу повинні мати однакову кількість розділювачів, щоб структура даних була коректною.
|
Наприклад, CSV
-файл з даними про комп’ютерні ігри може мати наступну структуру:
Назва гри,Рік випуску,Жанр
The Witcher 3: Wild Hunt,2015,Action RPG
Grand Theft Auto V,2013,Open World
Minecraft,2011,Sandbox
The Legend of Zelda: Breath of the Wild,2017,Action-Adventure
Fortnite,2017,Battle Royale
Civilization VI,2016,Turn-Based Strategy
Age of Empires II: Definitive Edition,2019,Real-Time Strategy
Heroes of Might and Magic III,1999,Turn-Based Strategy
Disciples II: Dark Prophecy,2002,Turn-Based Strategy
Така організація даних подібна до традиційної електронної таблиці.

Назва формату CSV
(Comma-Separated Values
або значення, розділені комами) вказує на використання коми як розділювача даних.
Попри це, термін CSV
широко використовується для позначення великого сімейства форматів, які відрізняються багатьма параметрами. Наприклад, деякі реалізації CSV
-формату включають обробку інших символів як розділювачів, як-от крапки з комою (;
), коли кома вже використовується як розділювач цілої та дробової частин числових даних.
Файли, які використовують символ табуляції (засіб відступу, який дорівнює по ширині кільком пропускам) замість коми, є альтернативою CSV
-формату і має назву TSV
- Tab Separated Values
або значення, розділені табуляцією.
Для опрацювання даних, організованих у рядки та стовпці, бібліотека p5.js
надає клас p5.Table , що спрощує роботу з табличними даними у разі створення з нуля, динамічно в коді або використовуючи дані з наявного файлу.
Отже, напишемо код застосунку, за допомогою якого отримаємо вміст локального CSV
-файлу, який спочатку потрібно зберегти в каталозі ескізу.
Для належної роботи застосунку, який завантажує дані з локального файлу, необхідно використовувати локальний вебсервер . |
let table; (1)
function preload() {
table = loadTable("games.csv", "csv", "header"); (2)
}
function setup() {
print(table.getRowCount() + " рядків у таблиці"); (3)
print(table.getColumnCount() + " стовпців у таблиці"); (4)
print(table.getColumn("Назва гри")); (5)
print(table.getColumn(0)); (6)
}
1 | Оголошуємо змінну table , ім’я якої буде покликанням на об’єкт із даними CSV -файлу games.csv . |
2 | Викликаємо функцію loadTable() , яка дозволяє прочитати вміст CSV -файлу. Аргументами для loadTable() є: games.csv - шлях до локального CSV -файлу, csv - формат файлу, header - враховувати рядок заголовків. |
3 | На об’єкті table застосовуємо метод getRowCount() , який повертає загальну кількість рядків у таблиці. |
4 | На об’єкті table застосовуємо метод getColumnCount() , який повертає загальну кількість стовпців у таблиці. |
5 | Використовуючи на об’єкті table метод getColumn() , отримуємо всі значення у вказаному стовпці (Назва гри) у вигляді масиву. До стовпця звертаємось за допомогою його назви. |
6 | Використовуючи на об’єкті table метод getColumn() , отримуємо всі значення у вказаному стовпці (Назва гри) у вигляді масиву. До стовпця звертаємось за допомогою його індексу. Результат виконання ідентичний пункту 5. |
Виклик loadTable() є асинхронним, тобто він може не завершитися до виконання наступної інструкції ескізу. Тому, щоб вміст файлу games.csv був повністю завантажений і лише після цього використаний для опрацювання даних, записуємо виклик функції loadTable() у тілі функції preload() .
|
Якщо аргумент header
передано у виклик функції loadTable()
, то кількість рядків у файлі буде обчислюватися, не враховуючи рядка заголовка (пункт 3), а доступ до елементів стовпця можна буде отримати як за назвою стовпця (пункт 5), так і за індексом (пункт 6). При цьому, в обидвох випадках назви стовпця не буде серед отриманих елементів.
Інакше, виклик функції loadTable()
без header
буде враховувати усі рядки файлу, включно із рядком заголовків, і серед елементів стовпця буде й значення його заголовка.
У результаті виконання застосунку в консолі вебпереглядача за допомогою функції print()
отримаємо без врахування рядка заголовка значення кількості рядків і стовпців у файлі та два однакових масиви (показаний лише один) зі значеннями першого стовпця.
9 рядків у таблиці
3 стовпців у таблиці
(9) ["The Witcher 3: Wild Hunt", "Grand Theft Auto V", "Minecraft", "The Legend of Zelda: Breath of the Wild", "Fortnite", "Civilization VI", "Age of Empires II: Definitive Edition", "Heroes of Might and Magic III", "Disciples II: Dark Prophecy"]
Оскільки результати друкуються лише в консолі вебпереглядача, у коді ескізу блок draw() не використовуємо.
|
Щоб отримати доступ до конкретної комірки даних - значення на перетині рядка і стовпця, можна скористатися двома циклами for
, як і у разі з двовимірними масивами.
let table;
function preload() {
table = loadTable("games.csv", "csv", "header");
}
function setup() {
for (let r = 0; r < table.getRowCount(); r++) { (1)
print(table.getRow(r).arr); (2)
print(table.getRow(r).obj); (3)
for (let c = 0; c < table.getColumnCount(); c++) { (4)
print(table.getString(r, c)); (5)
}
print(" "); (6)
}
}
1 | Виконуємо зовнішній цикл for по рядках CSV -файлу (без врахування рядка заголовків). |
2 | Друкуємо у консолі вебпереглядача значення поточного рядка даних у форматі масиву рядків. |
3 | Друкуємо у консолі вебпереглядача значення поточного рядка даних у форматі об’єкта, в якому ключами є назви заголовків, а значеннями - відповідні дані із поточного рядка. |
4 | Виконуємо вкладений цикл for по стовпцях CSV -файлу. |
5 | За допомогою методу getString() , який застосовується до об’єкта table , отримуємо рядкове значення із рядка з індексом r та стовпця з індексом c таблиці. Номер рядка визначається його індексом, тоді як стовпець може бути визначений або своїм індексом, або заголовком. |
6 | Застосовуємо функцію print(" ") , яка цього разу використовується для створення порожніх рядків між надрукованими в консолі вебпереглядача рядками CSV -файлу. |
У результаті отримаємо дані кожної комірки файлу games.csv
порядково.
(3) ["The Witcher 3: Wild Hunt", "2015", "Action RPG"]
{Назва гри: "The Witcher 3: Wild Hunt", Рік випуску: "2015", Жанр: "Action RPG"}
The Witcher 3: Wild Hunt
2015
Action RPG
(3) ["Grand Theft Auto V", "2013", "Open World"]
{Назва гри: "Grand Theft Auto V", Рік випуску: "2013", Жанр: "Open World"}
Grand Theft Auto V
2013
Open World
(3) ["Minecraft", "2011", "Sandbox"]
{Назва гри: "Minecraft", Рік випуску: "2011", Жанр: "Sandbox"}
Minecraft
2011
Sandbox
(3) ["The Legend of Zelda: Breath of the Wild", "2017", "Action-Adventure"]
{Назва гри: "The Legend of Zelda: Breath of the Wild", Рік випуску: "2017", Жанр: "Action-Adventure"}
The Legend of Zelda: Breath of the Wild
2017
Action-Adventure
(3) ["Fortnite", "2017", "Battle Royale"]
{Назва гри: "Fortnite", Рік випуску: "2017", Жанр: "Battle Royale"}
Fortnite
2017
Battle Royale
(3) ["Civilization VI", "2016", "Turn-Based Strategy"]
{Назва гри: "Civilization VI", Рік випуску: "2016", Жанр: "Turn-Based Strategy"}
Civilization VI
2016
Turn-Based Strategy
(3) ["Age of Empires II: Definitive Edition", "2019", "Real-Time Strategy"]
{Назва гри: "Age of Empires II: Definitive Edition", Рік випуску: "2019", Жанр: "Real-Time Strategy"}
Age of Empires II: Definitive Edition
2019
Real-Time Strategy
(3) ["Heroes of Might and Magic III", "1999", "Turn-Based Strategy"]
{Назва гри: "Heroes of Might and Magic III", Рік випуску: "1999", Жанр: "Turn-Based Strategy"}
Heroes of Might and Magic III
1999
Turn-Based Strategy
(3) ["Disciples II: Dark Prophecy", "2002", "Turn-Based Strategy"]
{Назва гри: "Disciples II: Dark Prophecy", Рік випуску: "2002", Жанр: "Turn-Based Strategy"}
Disciples II: Dark Prophecy
2002
Turn-Based Strategy
Вправа 77
Створити CSV
-файл з даними довільної тематики.
7.2.2. JSON
JSON або JavaScript Object Notation
- це текстовий формат даних, що використовується для передачі структурованих даних в Інтернеті. Він був створений як складова мови JavaScript
, але нині підтримується та активно використовується у більшості мов програмування як формат обміну даними.
JSON
побудований з використанням двох структур даних: об’єкти та масиви.
Об’єкти в JSON - це невпорядковані набори пар ключ: значення, де ключ - це рядок, а значення може бути рядком, числом, булевим значенням, об’єктом, масивом або null . Об’єкти в JSON записуються у фігурних дужках {} .
|
Щоб зрозуміти, як дані організовуються в JSON
-об’єкти, варто пригадати, як створюються і використовуються об’єкти у JavaScript
.
Для цього розглянемо код застосунку, який використовує властивості об’єкта cnv
для встановлення параметрів полотна застосунку.
let cnv = { (1)
w: 300,
h: 200,
c: "rgb(157, 117, 203)" // Amethyst
};
function setup() {
createCanvas(cnv.w, cnv.h); (2)
}
function draw() {
background(cnv.c); (3)
}
1 | Ініціалізуємо створення об’єкта cnv , використовуючи літеральний синтаксис (за допомогою фігурних дужок {} ), із трьома властивостями, які визначатимуть розміри полотна по ширині w та висоті h зі значеннями 300 і 200 відповідно, а також, встановлять для полотна колір c зі значенням "rgb(157, 117, 203)" . |
2 | Отримуємо значення властивостей cnv.w і cnv.h об’єкта cnv , використовуючи крапкову нотацію (вказується назва об’єкта і через крапку записується ім’я його властивості, значення якої необхідно отримати) і застосовуємо їх як аргументи у виклику функції createCanvas() для встановлення розмірів полотна. |
3 | Отримуємо значення властивості cnv.с об’єкта cnv , використовуючи крапкову нотацію і застосовуємо його як аргумент у виклику функції background() для встановлення кольору полотна. |
Для належної роботи застосунків, які запускаються локально, необхідно використовувати локальний вебсервер . Вебсервер запускається із каталогу ескізу. У цьому разі, щоб переглянути свої ескізи, необхідно перейти у вебпереглядачі за адресою http://localhost:port/index.html , де port - номер порту.
|
У результаті виконання застосунку полотно змінить свій колір та розміри, використовуючи значення властивостей об’єкта cnv
.
Тепер, використовуючи текстовий редактор, створимо новий файл і збережемо його з назвою cnv.json
поруч з файлом sketch.js
.
А далі, з файлу sketch.js
перенесемо літерал об’єкта cnv
у файл cnv.json
і додамо подвійні лапки навколо ключів і значень властивостей
{
"w": "300",
"h": "200",
"c": "rgb(157, 117, 203)"
}
залишивши у sketch.js
лише оголошення змінної cnv
.
Отже, ми вручну створили JSON
-файл з даними у вигляді об’єкта.
Дані у JSON -форматі можна генерувати автоматично, використовуючи численні онлайн-ресурси, призначені для цього. Одним із таких сайтів є jsondataai.com , який генерує структуру і дані у форматі JSON , використовуючи штучний інтелект.
|
Цікавимось
Тепер прочитаємо дані зі створеного JSON
-файлу.
Зробимо це за допомогою виклику функції loadJSON() , яка, як аргумент, отримує шлях до JSON
-файлу і повертає об’єкт, що містить дані JSON
-файлу.
Оскільки виклик loadJSON() є асинхронним, виконаємо його у тілі функції preload() , що гарантуватиме завершення завантаження даних із JSON -файлу до виклику функцій setup() і draw() .
|
У підсумку, код файлу sketch.js
матиме наступний вигляд:
let cnv;
function preload() {
cnv = loadJSON("cnv.json");
}
function setup() {
createCanvas(cnv.w, cnv.h);
}
function draw() {
background(cnv.c);
}
Для доступу до даних, як і у разі з об’єктом JavaScript
, використовується крапкова нотація (cnv.c
), а якщо є потреба, можна використовувати нотацію квадратних дужок (cnv["c"]
).
Результат виконання застосунку залишиться тим самим, оскільки значення властивостей, які завантажуються із файлу cnv.json
, є однаковими як і для прикладу з об’єктом JavaScript
. Проте, помістивши дані у JSON
-файлі, код застосунку, який опрацьовує ці дані, тепер став краще структурованим і легшим для читання.
Тепер розглянемо приклад використання масиву у JSON
-об’єкті.
Масиви в JSON - це упорядковані набори значень. Масиви в JSON записуються за допомогою квадратних дужок [] і можуть містити значення будь-якого типу.
|
Створимо JSON
-файл, який буде містити масив об’єктів. Кожен об’єкт зберігатиме дані про відомих творців ІТ-технологій.
{
"creators":[
{
"firstName":"Дуглас",
"lastName":"Крокфорд",
"photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/Douglas_Crockford.jpg/800px-Douglas_Crockford.jpg"
},
{
"firstName":"Брендан",
"lastName":"Айк",
"photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Brendan_Eich_Mozilla_Foundation_official_photo.jpg/1024px-Brendan_Eich_Mozilla_Foundation_official_photo.jpg"
},
{
"firstName":"Тім",
"lastName":"Бернерс-Лі",
"photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/Tim_Berners-Lee_April_2009.jpg/800px-Tim_Berners-Lee_April_2009.jpg"
},
{
"firstName":"Хокон",
"lastName":"Віум Лі",
"photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/H%C3%A5kon-Wium-Lie-2009-03.jpg/800px-H%C3%A5kon-Wium-Lie-2009-03.jpg"
}
]
}
Розглянемо код застосунку, який використовує дані із файлу creators.json
для створення елементів у DOM
вебсторінки, на якій відображатиметься зображення відомого творця, а при наведенні вказівника на зображення - його ім’я.
let data; (1)
function preload() {
data = loadJSON("creators.json"); (2)
}
function setup() {
createCanvas(200, 200);
let firstName = data.creators[0].firstName; (3)
let lastName = data.creators[0].lastName;
let photo = data.creators[0].photo;
let img = createImg(photo, `${firstName} ${lastName}`); (4)
img.position(0, 0); (5)
img.style("width", `${width}px`); (6)
img.attribute("title", `${firstName} ${lastName}`); (7)
}
function draw() {} (8)
1 | Оголошуємо змінну з ім’ям data , яке буде покликатися на об’єкт JSON , що міститиме дані з файлу creators.json . |
2 | Завантажуємо у застосунок дані з файлу creators.json . |
3 | Ініціалізуємо змінну з ім’ям firstName і значенням data.creators[0].firstName . Використовуючи крапкову нотацію звертаємось до JSON -об’єкта data , далі до масиву з ім’ям creators , а саме до першого його елемента з індексом 0 , який є об’єктом ({"firstName":"Дуглас", "lastName":"Крокфорд", "photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/Douglas_Crockford.jpg/800px-Douglas_Crockford.jpg"} ), а потім отримуємо з цього об’єкта значення його властивості firstName (Дуглас ). Для lastName і photo застосовується той самий алгоритм. |
4 | Використовуємо функцію createImg() для створення у DOM елемента зображення <img> з атрибутом src , який набуде значення photo (шлях до файлу зображення) і атрибутом alt зі значенням назви творця (тут використовується шаблонний рядок). |
5 | Застосовуємо до елемента img функцію position() , яка встановлює положення елемента відносно точки з координатами (0, 0) - лівий верхній кут полотна (початок системи координат полотна). |
6 | Застосовуємо до елемента img стиль за допомогою функції style() . Встановлюємо для властивості зображення width значення ширини полотна у пікселях (px ). Висота зображення буде встановлена автоматично. |
7 | Додаємо до елемента img атрибут title зі значенням назви творця (шаблонний рядок), використовуючи функцію attribute() , яка додає атрибути до елементів. |
8 | Функція draw() у цьому разі не використовується, тому її тіло залишаємо порожнім. |

Вправа 78
Використати цикл у коді застосунку для відображення на вебсторінці інформації про усіх творців.
Як засіб перевірки та форматування даних JSON використовуйте JSON Formatter & Validator .
|
Вправа 79
З’ясувати, чи містить файл volcanoes.json помилки. Якщо так, то виправити їх.
JSON
-файли можуть мати складнішу ієрархічну структуру. Розглянемо JSON
-файл, який містить дані про певний університет.
{
"університет":"Назва університету",
"ректор":{
"ім'я":"Ім'я Ректора",
"вік":45,
"освіта":"Доктор наук"
},
"факультети":[
{
"назва":"Факультет інформаційних технологій",
"декан":{
"ім'я":"Ім'я Декана",
"вік":40,
"освіта":"Кандидат наук"
},
"спеціальності":[
{
"назва":"Інформатика",
"кількість_студентів":300
},
{
"назва":"Комп'ютерні науки",
"кількість_студентів":250
}
]
},
{
"назва":"Факультет економіки",
"декан":{
"ім'я":"Ім'я Декана",
"вік":50,
"освіта":"Доктор наук"
},
"спеціальності":[
{
"назва":"Економіка підприємства",
"кількість_студентів":200
},
{
"назва":"Міжнародна економіка",
"кількість_студентів":150
}
]
}
],
"студенти":[
{
"ім'я":"Ім'я Студента 1",
"факультет":"Факультет інформаційних технологій",
"курс":2
},
{
"ім'я":"Ім'я Студента 2",
"факультет":"Факультет економіки",
"курс":3
}
]
}
Розглянемо код застосунку, який змінює DOM
вебсторінки ескізу, додаючи на неї інформацією про вищий навчальний заклад із файлу university.json
.
let data; (1)
function preload() {
data = loadJSON("university.json"); (2)
}
function setup() {
noCanvas(); (3)
createElement("h1", `${data.університет}`); (4)
createElement("h2", `Ректор: ${data.ректор["ім'я"]}`); (5)
createElement("h3", `Вік: ${data.ректор.вік}`);
createElement("h3", `Освіта: ${data.ректор.освіта}`);
for (let факультет of data.факультети) { (6)
createElement("h2", `Факультет: ${факультет.назва}`);
createElement("h2", `Декан: ${факультет.декан["ім'я"]}`);
createElement("h3", `Вік: ${факультет.декан.вік}`);
createElement("h3", `Освіта: ${факультет.декан.освіта}`);
for (let спеціальність of факультет.спеціальності) { (7)
createElement("p", `Спеціальність: ${спеціальність.назва}`);
createElement(
"p",
`Кількість студентів: ${спеціальність.кількість_студентів}`
);
}
}
}
function draw() {} (8)
1 | Оголошуємо змінну з ім’ям data , яке буде покликатися на об’єкт JSON , що міститиме дані з файлу university.json . |
2 | Завантажуємо у застосунок дані з файлу university.json . |
3 | Використовуємо функцію noCanvas() для видалення стандартного полотна, оскільки для цього ескізу воно не використовується. |
4 | Використовуємо функцію createElement() , яка створює об’єкт на основі класу p5.Element для опису HTML -елементів. Перший аргумент функції - це назва елемента, який необхідно створити, а другий аргумент - вміст елемента. Для доступу до даних використовуємо крапкову нотацію і шаблонний рядок. |
5 | Оскільки назви властивостей у JSON -файлі записані українською, щоб отримати значення властивості ім’я, при зверненні до цієї властивості використовуємо нотацію квадратних дужок, а не крапкову нотацію. |
6 | Зовнішнім циклом for проходимо по факультетах (назва; ім’я, вік і освіта декана). |
7 | Внутрішнім циклом for проходимо по спеціальностях на кожному факультеті (назва спеціальності; кількість студентів). |
8 | Функція draw() для цього ескізу не використовується, тому її тіло залишаємо порожнім. |
У результаті виконання застосунку в коді вебсторінки ескізу з’явиться розмітка (заголовки різних рівнів, абзаци), а на самій вебсторінці буде відображена узагальнена інформація про факультети і спеціальності.
Вправа 80
Файл university.json
містить дані про студентів. Вивести ці дані в консоль вебпереглядача.
7.2.3. Серіалізація та десеріалізація JSON
JavaScript
надає спеціальні методи JSON.stringify()
та JSON.parse()
для роботи з даними у форматі JSON
, які використовуються для серіалізації та десеріалізації JSON
відповідно.
JSON.stringify()
використовується для перетворення JavaScript
-об’єкта в рядок JSON
. Цей процес називається серіалізація об’єкта.
Проілюструємо як це працює на прикладі.
Створимо об’єкт country
, використовуючи літерал об’єкта {}
і додамо у створений об’єкт властивості name
, continent
, capital
, facts
, cuisine
зі своїми значеннями.
let country = {};
country.name = "Україна";
country.continent = "Європа";
country.capital = "Київ";
country.facts = [];
country.facts.push({"валюта": "гривня"});
country.facts.push({"домен": "ua"});
country.facts.push({"телефонний код": 380});
country.cuisine = ["борщ", "вареники"];
let countrySerialize = JSON.stringify(country);
console.log(countrySerialize);
За допомогою JSON.stringify()
об’єкт country
перетворюється (запаковується) в JSON
-рядок з ім’ям countrySerialize
і результат друкується в консолі вебпереглядача.
{"name":"Україна","continent":"Європа","capital":"Київ","facts":[{"валюта":"гривня"},{"домен":"ua"},{"телефонний код":380}],"cuisine":["борщ","вареники"]}
До речі, серіалізацію можна застосувати до окремих властивостей об’єкта. Наприклад, серіалізуємо з об’єкта country
лише властивості name
і cuisine
, надавши їх у вигляді масиву як другий аргумент для JSON.stringify
.
...
let countrySerialize = JSON.stringify(country, ["name", "cuisine"], 4);
console.log(countrySerialize);
Третім аргументом для JSON.stringify
є число 4
- це значення кількості пропусків у відступах при форматуванні структури отриманого JSON
-рядка.
{
"name": "Україна",
"cuisine": [
"борщ",
"вареники"
]
}
А тепер використаємо JSON.parse()
для перетворення JSON
-рядка з ім’ям countrySerialize
в протилежному напрямку - у JavaScript
-об’єкт. Цей процес називається десеріалізація.
let countrySerialize = '{"name":"Україна","continent":"Європа","capital":"Київ","facts":[{"валюта":"гривня"},{"домен":"ua"},{"телефонний код":380}],"cuisine":["борщ","вареники"]}';
let country = JSON.parse(countrySerialize);
console.log(country);
У цьому прикладі відбувається перетворення (розпакування, парсинг) JSON
-рядка у JavaScript
-об’єкт.
{name: "Україна", continent: "Європа", capital: "Київ", facts: Array(3), cuisine: Array(2)}
7.2.4. Ресурси
Корисні джерела
7.2.5. Контрольні запитання
Міркуємо Обговорюємо
-
Що спільного і відмінного у форматах зберігання даних
CSV
іTSV
? -
У чому популярність
JSON
-формату для зберігання структурованих даних? -
Як виконується обробка даних, які зберігаються у
JSON
-форматі?
7.2.6. Практичні завдання
Файли з даними для виконання практичних завдань можна завантажити за покликанням. |
Початковий
-
Зберегти подані дані у
CSV
-файл і створити застосунок для зчитування даних з файлу у консоль вебпереглядача порядково у вигляді списків.
книга,автор,жанр,рік
1984,Джордж Орвелл,Наукова фантастика,1949
Великий Гетсбі,Ф. Скотт Фіцджеральд,Класика,1925
Хоббіт,Дж.Р.Р. Толкін,Фентезі,1937
Гаррі Поттер і філософський камінь,Дж.К. Роулінг,Фентезі,1997
-
Виконати cеріалізацію об’єкта
vacanciesIT
з даними про вакансії популярних IT-компаній в Україні та деcеріалізацію отриманого результату.
const vacanciesIT = {
посада: ["Frontend developer", "Data Scientist"],
компанія: ["EPAM Systems", "SoftServe"],
місце: ["Київ, Україна", "Сан-Франциско, США"],
навички: [
["JavaScript", "React", "CSS", "HTML"],
["Python", "Машинне навчання", "Аналіз даних"],
],
досвід: ["2+ роки", "3+ роки"],
типЗайнятості: ["Повна зайнятість", "Віддалено"],
};
Середній
-
Створити
JSON
-файли з даними на довільну тематику і різним ступенем вкладеності.
-
Створити застосунок для читання даних із файлу
countries.json
і друку в консолі вебпереглядача назв країн, які розташовані в Європі.
Високий
-
Використовуючи дані із файлу
countries.tsv
, визначити, які із країн мають кількість населення більше мільярда людей.
Екстремальний
-
Надрукувати в консолі вебпереглядача назви комп’ютерних ігор та роки їх випуску, які розроблені для платформи
PC
жанруReal-Time Strategy
(стратегія в реальному часі), використовуючи дані із файлуgames.json
.
7.3. Прикладний програмний інтерфейс
7.3.1. Що таке API?
Прикладний програмний інтерфейс, або інтерфейс програмування застосунків (API) дозволяє різним застосункам взаємодіяти один з одним.
Цікавимось
Багато сайтів мають свої API
, які надають розробникам доступ до функцій та даних цих сайтів.
Сайти соціальних мереж (Facebook
, X
донедавна Twitter
, Instagram
, LinkedIn
та інші) мають свої власні API
, які дозволяють розробникам отримувати доступ до профілів користувачів, публікацій, коментарів і інших функцій соціальних мереж. Наприклад, всі ми звикли до того, що вхід на сайт можна здійснити без реєстрації, як такої, а через свої акаунти в соціальних мережах. Такі сайти за допомогою API
використовують бази даних соціальних мереж.
Сайти відеохостингу та стримінгу (відеотрансляція наживо), як-от YouTube
, Vimeo
та інші надають API
для інтеграції відеоконтенту в інші вебсайти та застосунки. Напевно кожен стикався з тим, коли у стрічці соціальної мережі з’являються відео, тематика яких пов’язана з тим відео, яке ви нещодавно вподобали в YouTube
. Це можливо завдяки API
, коли один сервіс (наприклад, Facebook
) використовує дані іншого (YouTube
).
Бази даних та хмарні сервіси (наприклад, Firebase
, Amazon Web Services (AWS)
та інші) також надають API
, які дозволяють розробникам працювати з базами даних та різноманітними хмарними послугами.
Геолокаційні сервіси, як-от Google Maps
, мають API
для відображення мап та отримання географічних даних.
API
може бути використано для отримання оновлень у реальному часі. Наприклад, віджет погоди на сайті може використовувати API
погоди для отримання актуальної інформації, а фінансовий застосунок може використовувати API
для отримання актуальної інформації про поточний стан на ринку акцій.
Розповсюдженим у вебі типом API є REST API (Representational State Transfer ), за допомогою якого різні компоненти вебзастосунків можуть спілкуватися між собою.
|
Цікавимось Додатково
Операційні системи, як-от Windows
, Linux
та інші, також мають власні API
. Створення, копіювання, видалення файлів і каталогів - ці найвживаніші дії виконуються завдяки API
операційної системи.
API
- це також набір готових функцій чи бібліотек, які розробники можуть використовувати для написання власних застосунків.
Наприклад, при розробці мобільного застосунку, в ролі API
може використовуватися бібліотека для роботи з розумним будинком. Розробник може, навіть, не знати, як реалізована бібліотека, а лише звертатися до її API
у своєму коді.
Бібліотека p5.js
також має свій API
- велику кількість готових функцій та методів для керування полотном, обробки подій миші та клавіатури тощо.
7.3.2. Отримання даних з Інтернету
API
є мостом між різними сервісами та вебзастосунками в Інтернеті, дозволяючи ефективно обмінюватися даними та використовувати функціонал інших сайтів у власних застосунках.
Отримання даних відбувається за допомогою запитів до визначених URL-адрес сайтів з API
.
Деякі API сайтів є повністю загальнодоступними, інші потребують автентифікації, зазвичай використовуючи унікальний ідентифікатор користувача або ключ. До того ж більшість API мають обмеження щодо частоти виконання запитів.
|
Розглянемо, як створювати GET
-запити - запити на отримання даних з сервера за вказаною URL
-адресою.
Github API
Розпочнемо із найпростішого запиту в адресному рядку вебпереглядача до Github API
(API
для взаємодії з GitHub
) за URL
-адресою https://api.github.com/users/mojombo . Цей запит не вимагатиме автентифікації, оскільки ми намагаємось отримати загальнодоступну інформацію.
GitHub - один з найбільших вебсервісів для спільної розробки програмного забезпечення. Базується на системі керування версіями Git і розроблений на Ruby on Rails і Erlang компанією GitHub .
|
У результаті отримаємо відповідь у JSON
-форматі. Це будуть дані про першого користувача та водночас одного із засновників GitHub
.
Загальнодоступні API найчастіше повертають дані у JSON -форматі, які легко обробляти програмно.
|
Тепер розглянемо код застосунку для отримання даних за вищенаведеною адресою. На основі отриманих даних, надрукуємо в консолі вебпереглядача ім’я цього засновника і покликання на його сайт.
let data; (1)
function preload() {
data = loadJSON("https://api.github.com/users/mojombo"); (2)
}
function setup() {
noCanvas();
print(data.name); (3)
print(data.blog); (4)
}
function draw() {}
1 | Оголошуємо змінну з ім’ям data , яке буде покликатися на JSON -об’єкт, що міститиме дані, завантажені за вказаною URL -адресою. |
2 | Завантажуємо у застосунок дані за вказаною URL -адресою за допомогою функції loadJSON() , яка повертає JSON -об’єкт. |
3 | За допомогою крапкової нотації отримуємо значення властивості name . |
4 | За допомогою крапкової нотації отримуємо значення властивості blog . |
Для належної роботи застосунків, які запускаються локально, необхідно використовувати локальний вебсервер . Вебсервер запускається із каталогу ескізу. У цьому разі, щоб переглянути свої ескізи, необхідно перейти у вебпереглядачі за адресою http://localhost:port/index.html , де port - номер порту.
|
У результаті виконання застосунку в консолі вебпереглядача надрукується результат із двох рядків.
Tom Preston-Werner
http://tom.preston-werner.com
My JSON Server
У GET
-запитах часто передаються, як частина URL
-адреси, параметри запиту. Коли сервер отримує такий запит з параметрами, він може обробити дані та повернути відповідь відповідно до параметрів, які були передані. Наприклад, будь-який запит в пошуковій системі Google
містить параметри в URL
-адресі.
Параметри запиту - це частина URL -адреси після адреси сторінки, яка визначає конкретний вміст або дії на основі даних, що передаються від вебпереглядача до сервера з метою отримання інформації, і починається зі знаку питання (? ). Окремий параметр містить ключ та значення, розділені символом дорівнює (= ). Якщо використовується кілька параметрів, вони розділяються символом амперсанда (& ).
|
Використаємо параметри запиту у нашому запиті, щоб відфільтрувати отримані дані, які повертає сервер.
Для цього скористаємось сервісом My JSON Server - Fake online REST server for teams . Цей сервіс - це вебсервер з API
, який може повертати вигадані дані у JSON
-форматі.
За потреби можна створити власний JSON Server зі своїм REST API .
|
Унаслідок наших дій у вікні вебпереглядача відкриється набір даних у JSON
-форматі:
[
{
"id": 1,
"title": "Post 1"
},
{
"id": 2,
"title": "Post 2"
},
{
"id": 3,
"title": "Post 3"
}
]
Використаємо параметр запиту id=1
, записавши його у кінець URL
-адреси після знака питання (?
): https://my-json-server.typicode.com/typicode/demo/posts?id=1
.
Перейшовши за адресою, яка включає параметр запиту, отримаємо дані, що відповідають параметру запиту:
[
{
"id": 1,
"title": "Post 1"
}
]
Як бачимо, у вікні вебпереглядача відображаються лише дані про допис з id
рівним 1
.
Тепер у кінець URL
-адреси після знака питання (?
) запишемо параметр запиту title=Post 2
: https://my-json-server.typicode.com/typicode/demo/posts?title=Post%202
.
В URL -адресах, записаних в адресному рядку вебпереглядача, деякі символи кодуються за допомогою послідовностей інших символів автоматично. Наприклад, пропуски кодуються символами %20 або замінюються символом плюса (+ ), кома - символами %2C , відсоток - %25 , лапки - %22 тощо.
|
У результаті отримаємо дані, що відповідають допису з назвою Post 2
:
[
{
"id": 2,
"title": "Post 2"
}
]
Використаємо у нашому запиті кілька параметрів, розділивши їх символом амперсанда (&
): https://my-json-server.typicode.com/typicode/demo/posts?id=1&id=3
.
У вікні вебпереглядача виводиться перший (відповідає першому параметру запиту) і третій дописи (відповідає другому параметру запиту):
[
{
"id": 1,
"title": "Post 1"
},
{
"id": 3,
"title": "Post 3"
}
]
Як бачимо, повертаються ті дані, які задовольняють усім визначеним критеріям. Якщо ж жодна з умов, визначених параметрами запиту, не виконуються, то повертається порожній масив []
.
Вправа 81
Виконати GET
-запит за URL
-адресою https://my-json-server.typicode.com/typicode/demo/comments
для отримання коментаря зі значенням id
, що дорівнює 2
.
The Space Devs APIs
Розглянемо ще один приклад роботи із загальнодоступними API
під назвою The Space Devs .
The Space Devs - це група розробників-ентузіастів, які працюють над низкою послуг, об’єднаних спільною метою - покращити знання та доступність інформації про космічні польоти, надаючи корисні дані та інструменти, доступні кожному безплатно.
|
Безплатне використання обмежене 15 запитами на годину. |
Сайт має велику базу даних та зручні інструменти API
для роботи з даними. На сторінці Launch Library API представлені категорії даних, зразки даних, покликання на документацію і відповідні API Endpoint
.
Endpoint (кінцева точка) - це URL -адреса, куди API надсилає запити та де знаходиться ресурс.
|
Створимо запит для отримання даних, пов’язаних з програмою Artemis
- програмою дослідження Місяця під керівництвом NASA .
Алгоритм наших дій буде таким:
-
На сторінці Launch Library API обрати категорію
Events
. -
На сторінці
Event List
натиснути кнопку API ENDPOINT. -
У вікні
Field filters
обрати поле з назвоюProgram
і встановити для нього значенняArtemis
, позначивши його у списку. -
У вікні
Field filters
натиснути кнопку SUBMIT для створенняGET
-запиту.
У результаті отримаємо кілька сторінок даних у JSON
-форматі відповідно до нашого запиту.
Стандартним налаштуванням є 10 елементів даних на сторінку. |
Тепер використаємо сторінку документації API Launch Library Docs , яка надає ширший інструментарій для роботи із базою даних подій космічних польотів. Для прикладу, дізнаємось, скільки зараз астронавтів перебуває у космосі.
Алгоритм наший дій буде таким:
-
Знайти розділ з назвою
astronaut
. -
Відкрити налаштування
GET
-запиту. -
Натиснути на кнопку Try it out, щоб мати змогу змінювати параметри запиту.
-
Серед параметрів знайти параметр з назвою
in_space
і встановити для нього значенняtrue
. -
Натиснути кнопку
Execute
для виконання запиту.
У підсумку, відповідь сервера у JSON
-форматі міститиме дані про усіх астронавтів, які перебувають у космосі.
З рядка Request URL
скопіюємо URL
-адресу запиту, щоб використати її у коді застосунку, який буде визначати чи є серед астронавтів, які перебувають у космосі, представник, наприклад, з Японії. Якщо так, то виведемо його ім’я.
let data; (1)
function preload() {
data = loadJSON("https://ll.thespacedevs.com/2.2.0/astronaut/?in_space=true"); (2)
}
function setup() {
noCanvas();
for (let i = 0; i < data.results.length; i++) { (3)
let astronaut = data.results[i]; (4)
if (astronaut.nationality === "Japanese") { (5)
print(astronaut.name); (6)
}
}
}
function draw() {}
Проаналізуємо наведений код.
1 | Оголошуємо змінну з ім’ям data , яке буде покликатися на об’єкт JSON , що міститиме дані, завантажені за URL -адресою. |
2 | Завантажуємо у застосунок дані за вказаною URL -адресою. Зверніть увагу на використання параметра in_space зі значенням true , який дозволить відфільтрувати дані, які стосуються лише астронавтів, що перебувають у космосі. |
3 | У циклі for проходимо по властивості data.results , яка є масивом об’єктів. |
4 | Ініціалізуємо змінну з ім’ям astronaut значенням поточного об’єкта data.results[i] . |
5 | Перевіряємо, чи властивість nationality поточного об’єкта astronaut має значення "Japanese" . |
6 | Якщо виконується пункт 5, друкуємо в консолі вебпереглядача ім’я астронавта, звертаючись до властивості name об’єкта astronaut . |
Результат у консолі вебпереглядача може бути таким:
Satoshi Furukawa
Подивімось, які орбітальні запуски вже відбулися чи плануються у майбутньому, починаючи з 2024 року.
Це можна зробити, як за допомогою інструментів на сторінці API Launch Library Docs у розділі з назвою launch
, так і за допомогою коду. Використаємо другий спосіб.
У цьому разі наш запит буде містити параметр net__gte
зі значенням дати у форматі 2024-01-01T21:45:00Z
.
Назва "net" розшифровується як "No Earlier Than" (не раніше, ніж) і позначає час запуску для конкретної місії або події пов’язаної із запуском ракети, а "gte" - це is greater than or equal to - більше або дорівнює.
|
Отже, відповіддю на запит мають бути дані про запуски, які заплановано не раніше, ніж на 1 січня 2024 року о 21:45:00 за Всесвітнім координованим часом (UTC) .
Розглянемо код застосунку, який це реалізує.
let data;
function preload() {
data = loadJSON(
"https://ll.thespacedevs.com/2.2.0/launch/?limit=20&net__gte=2024-01-01T21:45:00Z" (1)
);
}
function setup() {
noCanvas();
print(data); (2)
}
function draw() {}
1 | Додатковий параметр limit=20 у рядку запиту використовується для встановлення значення 20 для ліміту на кількість даних, отриманих від сервера, оскільки за стандартним налаштуванням сервер надсилає лише 10 елементів даних. Також у запиті використовується два параметри, тому вони об’єднуються за допомогою символу амперсанда (& ). |
2 | Друк у консолі вебпереглядача вмісту об’єкта JSON . |
В консолі вебпереглядача отримаємо такий результат:
{count: 345, next: "https://ll.thespacedevs.com/2.2.0/launch/?limit=20&net__gte=2024-01-01T21%3A45%3A00Z&offset=20", previous: null, results: Array(20)}
Цікавимось
Отже, значення поля results
містить масив із 20 елементів даних. Витягнемо із кожного елементу даних інформацію про місце, рік і місяць запусків та назви ракет-носіїв.
let data;
const months = [ (1)
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
function preload() {
data = loadJSON(
"https://ll.thespacedevs.com/2.2.0/launch/?limit=20&net__gte=2024-01-01T21:45:00Z"
);
}
function setup() {
noCanvas();
for (let i = 0; i < data.results.length; i++) { (2)
let launch = data.results[i]; (3)
let d = new Date(`${launch.net}`); (4)
let y = d.getFullYear(); (5)
let m = months[d.getMonth()]; (6)
print(launch.name, y, m); (7)
print(launch.pad.location.name); (8)
}
}
function draw() {}
1 | Ініціалізуємо масив з ім’ям months , який містить назви місяців року. |
2 | Завантаживши у застосунок дані за вказаною URL -адресою, у циклі for проходимо по властивості data.results , яка є масивом об’єктів. |
3 | Ініціалізуємо змінну з ім’ям launch значенням поточного об’єкта data.results[i] . |
4 | Ініціалізуємо змінну з ім’ям d значенням об’єкта дати, що створюється за допомогою класу Date() , у конструктор якого передається launch.net - дані про дату запуску у форматі 2024-01-01T21:45:00Z . У результаті під ім’ям d зберігається дата у форматі на зразок Mon Mar 04 2024 02:00:00 GMT+0200 (за східноєвропейським стандартним часом) . |
5 | Ініціалізуємо змінну з ім’ям y значенням чотиризначного цілого числа року, яке отримуємо за допомогою методу getFullYear() , що застосовуємо на об’єкті d . |
6 | Ініціалізуємо змінну з ім’ям m значенням назви місяця. Спочатку отримуємо за допомогою методу getMonth() , що застосовуємо на об’єкті d , номер місяця як ціле число, а потім звертаємось до масиву months для отримання назви місяця, використовуючи значення номера місяця як індекс. |
7 | Друкуємо у консолі вебпереглядача значення launch.name - назву ракети-носія, y - рік запуску, m - місяць запуску. |
8 | Друкуємо у консолі вебпереглядача значення launch.pad.location.name - назву місця запуску. |
У підсумку, в консолі вебпереглядача отримаємо результат (показаний лише фрагмент даних):
...
LVM-3 | Gaganyaan-1 2024 February
Satish Dhawan Space Centre, India
Falcon 9 Block 5 | Dragon CRS-2 SpX-30 2024 March
Kennedy Space Center, FL, USA
WTIA API
WTIA API - ще один проєкт, пов’язаний з тематикою космосу, який має загальнодоступний API
, обмежений одним запитом за секунду і (поки що) не потребує автентифікації.
Цей сервіс дозволяє в режимі реального часу отримувати дані про об’єкти, які рухаються по навколоземній орбіті. Усі відповіді за стандартним налаштуванням використовують JSON
-формат.
Для візуалізації у вебпереглядачі відповіді у JSON -форматі з відступами, необхідно у запиті використовувати параметр indent=4 , значення якого визначає число пропусків, які використовуються для відступів.
|
Отож, отримаємо дані про об’єкти на орбіті, виконавши запит https://api.wheretheiss.at/v1/satellites?indent=4
в адресному рядку вебпереглядача. У вікні вебпереглядача отримаємо відповідь у відформатованому вигляді, включаючи загальну назву та ідентифікатор об’єкта за супутниковим каталогом NORAD
:
[
{
"name":"iss",
"id":25544
}
]
Як бачимо, API
має інформацію лише про Міжнародну космічну станцію (International Space Station Current Location
, ISS
) - пілотовану космічну станцію на орбіті Землі, яка створена для наукових досліджень у космосі.
Розглянемо код застосунку, який повертає положення, швидкість та іншу пов’язану інформацію про космічну станцію у цей момент часу. Для цього використаємо цикл for in
, яким пройдемо по усіх властивостях об’єкта data
.
let data;
function preload() {
data = loadJSON("https://api.wheretheiss.at/v1/satellites/25544");
}
function setup() {
noCanvas();
for (let k in data) {
print(k, data[k]);
}
}
function draw() {}
У консолі вебпереглядача буде надрукована інформація про космічну станцію цей момент часу:
name iss
id 25544
latitude 31.807244912515
longitude -39.070205528184
altitude 416.35076448151
velocity 27596.312138889
visibility daylight
footprint 4488.8509955455
timestamp 1703339614
daynum 2460302.0788657
solar_lat -23.430429373069
solar_lon 331.35290669011
units kilometers
Якщо ще раз виконати застосунок, тобто зробити черговий запит, інформацію для деяких властивостей буде оновлено відповідно для нового моменту часу.
Цікавимось
fetch
Для реалізації асинхронного підходу до розробки застосунків мова JavaScript
має власний вбудований інтерфейс з назвою Fetch API .
Щоб використати Fetch API
, необхідно викликати метод fetch()
і передати йому як обов’язковий параметр URL
-адресу API
. Отож, перепишемо попередній код з використанням метода fetch()
.
function setup() {
noCanvas();
const url = "https://api.wheretheiss.at/v1/satellites/25544"; (1)
fetch(url) (2)
.then((response) => { (3)
return response.json(); (4)
})
.then((data) => { (5)
print(data);
});
}
function draw() {}
Проаналізуємо цей новий синтаксис у створенні запитів за допомогою fetch()
.
1 | Ініціалізуємо константу url зі значенням рядка запиту. |
2 | Виклик метода fetch() , передавання йому як обов’язкового параметра значення url та виконання GET -запиту. |
3 | Метод fetch() повертає Promise (проміс) JavaScript . Далі метод then() проміса повертає об’єкт response , який містить HTTP -відповідь з кодом стану, URL -адресою запиту та інші дані. |
4 | Коли метод then() поверне відповідь, до відповіді застосовуємо метод json() , оскільки відповідь надійшла у JSON -форматі, а результат у формі об’єкта повертаємо за допомогою return . |
5 | Ще раз викликаємо метод then() , аргументом в якому використовуємо ім’я data як покликання на об’єкт проміса, для виведення у консоль вебпереглядача. |
Результат в консолі вебпереглядача буде подібним цього (за умови, якщо запит був успішним і не виникало інших помилок):
{name: "iss", id: 25544, latitude: -40.345611695911, longitude: 93.681884921012, altitude: 428.1503111178…}
Проміси у JavaScript - це спеціальні об’єкти, що містять асинхронні методи, які замість негайного надсилання певних даних, надсилають обіцянку надати значення в якийсь момент у майбутньому. Це подібно ситуації, коли ви підписані на певну сторінку у соціальній мережі та отримуєте повідомлення про нові дописи на ній, коли такі дописи з’являються.
|
Змінимо код, щоб обробити появу помилок.
function setup() {
noCanvas();
const url = "https://api.wheretheiss.at/v1/satellites/25544";
fetch(url)
.then((response) => {
if (response.status >= 200 && response.status < 400) { (1)
return response.json();
} else { (2)
// якщо отримали помилку від сервера
console.log(`${response.status} ${response.statusText}`);
}
})
.then((data) => {
print(data);
})
.catch((error) => { (3)
// обробка інших помилок
console.log(error);
});
}
function draw() {}
Проаналізуємо код, який ми додали для обробки помилок.
1 | Перевіряємо код стану HTTP -відповіді від сервера. Якщо запит був успішний, то застосовуємо метод json() для синтаксичного аналізу відповіді, яка надійшла у JSON -форматі. |
2 | Інакше - друкуємо в консолі вебпереглядача повідомлення про помилку, використовуючи шаблонний рядок і значення полів response.statusText та response.status . |
3 | Обробляємо інші помилки за допомогою метода catch() . |
Використовуючи дані, отримані зі сервера, дізнаємось висоту (altitude
) орбіти космічної станції у км і швидкість (velocity
) її руху у км/год на цей момент часу.
function setup() {
noCanvas();
const url = "https://api.wheretheiss.at/v1/satellites/25544";
fetch(url)
.then((response) => {
if (response.status >= 200 && response.status < 400) {
return response.json();
} else {
// якщо отримали помилку від сервера
console.log(`${response.status} ${response.statusText}`);
}
})
.then((data) => {
print(data.altitude); (1)
print(data.velocity); (2)
})
.catch((error) => {
// обробка інших помилок
console.log(error);
});
}
function draw() {}
1 | Отримаємо значення висоти орбіти data.altitude МКС і виводимо в консоль вебпереглядача. |
2 | Отримаємо значення швидкості руху data.velocity МКС і виводимо в консоль вебпереглядача. |
У результаті виконання застосунку у консолі вебпереглядача отримаємо такі значення:
417.15298199586
27582.034295679
Вправа 82
Перевірити код застосунку, наведений вище, в обробці помилок. Для цього змусити код згенерувати помилку 404 Not Found
(сервер не може знайти запитуваний ресурс) і проаналізувати інші помилки, якщо такі будуть. У разі успішного запиту в консолі вебпереглядача виводиться інформація про видимість (visibility
) об’єкта на небі.
async/await
Для кращого розуміння і простоти використання промісів, приклад роботи з якими розглядався вище, застосовують зарезервовані слова async
і await
.
Зарезервоване слово async
записується перед оголошенням функції, внаслідок чого функція стане асинхронною. А зарезервоване слово await
змушує інтерпретатор JavaScript
чекати доти, доки проміс праворуч від await
виконається. Після чого await
поверне його результат і виконання коду продовжиться.
Використаємо метод fetch()
в синтаксисі async/await
у нашому застосунку про міжнародну космічну станцію.
Цікавимось
/* jshint esversion: 8 */ (1)
function setup() {
noCanvas();
const url = "https://api.wheretheiss.at/v1/satellites/25544";
async function whereTheISSat() { (2)
const response = await fetch(url); (3)
const data = await response.json(); (4)
print(data); (5)
}
whereTheISSat(); (6)
}
function draw() {}
1 | Встановлення версії ECMAScript , яка визначатиме правила написання коду. |
2 | Оголошення асинхронної функції whereTheISSat() за допомогою зарезервованого слова async . |
3 | Інтерпретатор JavaScript чекає доти, доки проміс у методі fetch() праворуч від зарезервованого слова await виконається, тобто буде отримана відповідь від сервера на запит за адресою url . Після цього відбудеться ініціалізація значення відповіді з ім’ям response . |
4 | Інтерпретатор JavaScript чекає доти, доки до response буде застосований метод json() . Після цього відбудеться ініціалізація отриманого результату з ім’ям data . |
5 | Значення data друкується в консолі вебпереглядача. |
6 | Виклик асинхронної функції whereTheISSat() . |
Хоча у разі виконання застосунку результат буде подібний попередньому, втім з оновленими значеннями певних властивостей для цього моменту часу, використання синтаксису async/await
є простішим для читання.
Оголосимо ще одну асинхронну функцію з назвою getImageISS()
, яка буде запитувати за URL
-адресою зображення МКС і розміщувати його на полотні. Оскільки WTIA API
не надає графічних даних, використаємо зображення МКС із сайту The Space Devs APIs
.
/* jshint esversion: 8 */
function setup() {
noCanvas();
const url = "https://api.wheretheiss.at/v1/satellites/25544";
async function whereTheISSat() {
const response = await fetch(url);
const data = await response.json();
print(data.name); (1)
}
whereTheISSat();
async function getImageISS() { (2)
const response = await fetch( (3)
"https://spacelaunchnow-prod-east.nyc3.digitaloceanspaces.com/media/spacestation_images/international2520space2520station_image_20190220215716.jpeg"
);
const blob = await response.blob(); (4)
let srcImage = URL.createObjectURL(blob); (5)
let img = createImg(srcImage, "ISS"); (6)
img.position(0, 0); (7)
img.attribute("width", "200px"); (8)
}
getImageISS(); (9)
}
function draw() {}
1 | Результат роботи асинхронної функції whereTheISSat() - виконання запиту і виведення у консоль вебпереглядача абревіатури МКС англійською. |
2 | Оголошення асинхронної функції getImageISS() . |
3 | Асинхронний запит за URL -адресою зображення та ініціалізація відповіді з ім’ям response . |
4 | Застосування до response методу blob() - метод синтаксичного аналізу тіла запиту, представленого як Blob (Binary Large Object ) та ініціалізація отриманого результату з ім’ям blob . |
5 | Створення за допомогою метода createObjectURL рядка, що містить URL -адресу, яка представляє об’єкт blob , вказаний як аргумент, та ініціалізація рядка з ім’ям srcImage . |
6 | Використання функції createImg() , яка створює елемент <img> у DOM з атрибутами src і значенням srcImage та alt зі значенням "ISS" відповідно. |
7 | Застосування методу position() , який встановлює положення елемента з ім’ям img у точці з координатами (0, 0) , що розміщена у лівому верхньому куті вікна перегляду. |
8 | За допомогою метода attribute() додамо атрибут width до елемента з ім’ям img зі значенням 200px для встановлення ширини зображення розміром 200 пікселів. |
9 | Виконуємо виклик асинхронної функції getImageISS() . |

Тип даних Blob (Binary Large Object ) використовується для зберігання та обробки мультимедійних даних, таких як зображення, відео та аудіо.
|
Для обидвох функцій whereTheISSat()
і getImageISS()
можна написати розширену обробку помилок з урахуванням стану відповіді, який повертається сервером.
Використаємо для цього інструкцію try...catch , яка складається з блоку try
і блоку catch
, блоку finally
або усіх перелічених блоків коду.
Код у блоці try
виконується першим, і якщо він генерує помилку, буде виконано код у блоці catch
. Код у блоці finally
завжди виконується, незалежно чи виконується try
, чи catch
.
У підсумку код застосунку матиме наступну структуру:
/* jshint esversion: 8 */
function setup() {
noCanvas();
const url = "https://api.wheretheiss.at/v1/satellites/25544";
async function whereTheISSat() {
try {
const response = await fetch(url);
if (response.status >= 200 && response.status < 400) {
const data = await response.json();
print(data.name);
} else {
// якщо отримали помилку від сервера
console.log(`${response.status} ${response.statusText}`);
}
} catch (error) {
// обробка інших помилок
console.log(error);
} finally {
console.log("Завершено для whereTheISSat");
}
}
whereTheISSat();
async function getImageISS() {
try {
const response = await fetch(
"https://spacelaunchnow-prod-east.nyc3.digitaloceanspaces.com/media/spacestation_images/international2520space2520station_image_20190220215716.jpeg"
);
if (response.status >= 200 && response.status < 400) {
const blob = await response.blob();
let srcImage = URL.createObjectURL(blob);
let img = createImg(srcImage, "ISS");
img.position(0, 0);
img.attribute("width", "200px");
} else {
// якщо отримали помилку від сервера
console.log(`${response.status} ${response.statusText}`);
}
} catch (error) {
// обробка інших помилок
console.log(error);
} finally {
console.log("Завершено для getImageISS");
}
}
getImageISS();
}
function draw() {}
У разі успішних запитів в консолі вебпереглядача матимемо такі результати:
Завершено для getImageISS
iss
Завершено для whereTheISSat
Цікавимось Додатково
Оскільки космічна станція рухається, відповідно змінюються її координати. Напишемо код застосунку, який буде виконувати запит з частотою 1 раз за 5 секунд (WTIA API
має обмеження на 1 запит за секунду), на отримання значень широти (latitude
) і довготи (longitude
) місця перебування МКС та відображення їх у консолі. Для створення запитів, які повторюються через кожні 5 секунд, використаємо метод fetch()
.
/* jshint esversion: 8 */
function setup() {
noCanvas();
const url = "https://api.wheretheiss.at/v1/satellites/25544";
async function whereTheISSat() {
const response = await fetch(url);
const data = await response.json();
let [latitude, longitude] = [data.latitude, data.longitude]; (1)
print(`${latitude.toFixed(3)}\u00B0, ${longitude.toFixed(3)}\u00B0`); (2)
}
setInterval(whereTheISSat, 5000); (3)
}
function draw() {}
1 | Ініціалізуємо змінні з іменами latitude і longitude значеннями data.latitude та data.longitude відповідно до отриманих даних відповіді від сервера. |
2 | Друкуємо в консолі вебпереглядача значення latitude і longitude . Застосовуючи метод toFixed() до значень ширити й довготи, встановлюємо кількість цифр після десяткової крапки, у цьому разі 3. Послідовність \u00B0 - шістнадцяткове значення символу градуса. |
3 | Використовуємо метод setInterval() , який багаторазово викликає асинхронну функцію whereTheISSat() з фіксованою затримкою між кожним викликом, яка дорівнює 5000 мілісекунд (5 секунд). |
Щоб скасувати повторення викликів setInterval() , використовується метод clearInterval() , який в ролі аргументу використовує ідентифікатор дії, яку потрібно скасувати. Ідентифікатор дії створюється і повертається відповідним викликом setInterval() .
|
Результатом виконання застосунку буде поява у консолі вебпереглядача пар значень широти й довготи через кожні 5 секунд.
Переглядаємо Аналізуємо
dummyJSON
Дуже часто потрібно обробити дані, які отримані в результаті асинхронних запитів.
Використаємо для ілюстрації цього DummyJSON - API
, який використовує вигадані набори даних у JSON
-форматі.
Розглянемо код застосунку, який отримує дані про номер, назву та кількість одиниць різних товарів.
function setup() {
noCanvas();
const url =
"https://dummyjson.com/products?limit=3&select=id,title,stock"; (1)
fetch(url)
.then((response) => {
if (response.status >= 200 && response.status < 400) {
return response.json();
} else {
console.log(`${response.status} ${response.statusText}`);
}
})
.then((data) => { (2)
let stuff = data.products;
for (let i = 0; i < stuff.length; i++) { (3)
let good = stuff[i]; (4)
print(good.id, good.title, good.stock); (5)
}
})
.catch((error) => {
console.log(error);
});
}
function draw() {}
1 | У рядку запиту використовуємо два параметри: limit=3 - визначає скільки елементів даних отримувати (limit=0 без обмежень) і select=id,title,stock,category , значенням якого є низка назв полів, розділених комами, які будуть присутні у кожному з елементів даних. |
2 | Після успішного запиту, в метод then() як аргумент передаємо (ініціалізуємо) змінну з ім’ям data (за потреби можна обрати інше ім’я), яке буде покликатися на об’єкт з отриманими даними. Далі використовуємо data для наших завдань, наприклад у цьому разі друкуємо в консолі вебпереглядача номер, назву та кількість одиниць 3-ох товарів. |
3 | Проходимо у циклі for по елементах масиву stuff , які є об’єктами з даними конкретних товарів. |
4 | Ініціалізуємо змінну з ім’ям good , яке буде покликатися на значення об’єкта stuff[i] , що позначає конкретний товар. |
5 | Друкуємо в консолі вебпереглядача значення обраних властивостей об’єкта good . |
1 "iPhone 9" 94
2 "iPhone X" 34
3 "Samsung Universe 9" 36
Інший спосіб роботи з даними, отриманими асинхронним способом, полягає у використанні метода finally() , код у тілі якого завжди виконується незалежно від результату виконання запиту. Це дає змогу проаналізувати дані, які були отримані, чи просто виконати певний фрагмент коду у будь-якому разі.
let fetchedData; (1)
function setup() {
noCanvas();
const url =
"https://dummyjson.com/products?limit=3&select=id,title,stock";
fetch(url)
.then((response) => {
if (response.status >= 200 && response.status < 400) {
return response.json();
} else {
console.log(`${response.status} ${response.statusText}`);
}
})
.then((data) => {
fetchedData = data; (2)
})
.catch((error) => {
console.log(error);
})
.finally(() => { (3)
let stuff = fetchedData.products;
for (let i = 0; i < stuff.length; i++) {
let good = stuff[i];
print(good.id, good.title, good.stock);
}
});
}
function draw() {}
1 | Оголошуємо глобальну змінну з ім’ям fetchedData , яке буде покликатися на об’єкт отриманих даних data . |
2 | Зберігаємо об’єкт отриманих даних під ім’ям fetchedData . |
3 | У метод finally() як аргумент передаємо у стрілочну функцію без параметрів (() > ), а у тілі стрілочної функції ({ } ) використовуємо код для обробки отриманих даних. |
Варто звернути увагу на те, що у вищенаведених прикладах обробка даних відбувається у самій конструкції асинхронного запиту. А як бути у разі, коли отримані дані необхідно використати в інших місцях коду, наприклад у блоці draw()
?
Змінимо код попереднього застосунку, увівши змінну results
, яка буде визначати масив.
let fetchedData;
let results = []; (1)
function setup() {
noCanvas();
const url = "https://dummyjson.com/products?limit=3&select=id,title,stock";
fetch(url)
.then((response) => {
if (response.status >= 200 && response.status < 400) {
return response.json();
} else {
console.log(`${response.status} ${response.statusText}`);
}
})
.then((data) => {
fetchedData = data;
})
.catch((error) => {
console.log(error);
})
.finally(() => {
let stuff = fetchedData.products;
for (let i = 0; i < stuff.length; i++) {
let good = stuff[i];
results.push(good); (2)
}
});
}
function draw() {
print(results); (3)
}
1 | Ініціалізуємо порожній масив з ім’ям results . |
2 | Об’єкти good з даними про конкретний товар додаємо у масив results використовуючи метод push() . |
3 | У блоці draw() друкуємо в консолі вебпереглядача значення масиву results . |
Оскільки тіло функції draw()
виконується безперервно, коли застосунок працює, можемо спостерігати таку картину, як спочатку друкуються порожні масиви ([]
), а через деякий час вже виводяться масиви, які містять по три об’єкти кожен.
...
[]
[]
[]
(3) [Object, Object, Object]
(3) [Object, Object, Object]
...
Для отримання даних асинхронним методом потрібен певний час, тому коли ще дані не отримані, а значення масиву вже друкуються в консолі завдяки інструкції print(results);
у блоці draw()
, ми спостерігаємо літерали порожніх масивів. Як тільки асинхронний запит виконається, масив друкуватиметься із доданими у нього даними.
Таку поведінку можна змінити.
let fetchedData;
let success = false; (1)
let results = [];
function setup() {
noCanvas();
const url = "https://dummyjson.com/products?limit=3&select=id,title,stock";
fetch(url)
.then((response) => {
if (response.status >= 200 && response.status < 400) {
return response.json();
} else {
console.log(`${response.status} ${response.statusText}`);
}
})
.then((data) => {
fetchedData = data;
})
.catch((error) => {
console.log(error);
})
.finally(() => {
let stuff = fetchedData.products;
for (let i = 0; i < stuff.length; i++) {
let good = stuff[i];
results.push(good);
}
success = true; (2)
});
}
function draw() {
if (success) { (3)
print(results);
success = false; (4)
}
}
1 | Ініціалізуємо змінну з ім’ям success значенням false . За допомогою неї будемо визначати, чи завершився процес отримання даних. |
2 | Дані отримані, тому у тілі метода finally() змінюємо значення success на true . |
3 | У блоці draw() перевіряємо значення success . Якщо true (дані отримано), друкуємо вміст масиву results , який містить дані відповідно до нашого завдання або виконуємо додаткові з його вмістом, наприклад візуалізуємо дані. |
4 | Повертаємо значення false для success , щоб уникнути повторення виведення у draw() . Після цього зміна значення success на true не відбудеться, оскільки перший і єдиний раз це стається у тілі метода finally() , в якому код виконується лише раз. |
Тепер вміст масиву у консолі вебпереглядача виводиться лише один раз.
Змінимо код застосунку у разі використання синтаксису async/await
.
let results = [];
function setup() {
noCanvas();
const url = "https://dummyjson.com/products?limit=3&select=id,title,stock";
async function fetchData() {
try {
const response = await fetch(url);
if (response.status >= 200 && response.status < 400) {
const data = await response.json();
return data; (1)
} else {
console.log(`${response.status} ${response.statusText}`);
}
} catch (error) {
console.log(error);
}
}
// виклик асинхронної функції fetchData()
let result = fetchData(); (2)
result.then((fetchedData) => { (3)
parseData(fetchedData); (4)
});
}
function parseData(receivedData) { (5)
let stuff = receivedData.products;
for (let i = 0; i < stuff.length; i++) {
let good = stuff[i];
results.push(good);
}
print(results); (6)
}
1 | Значення data буде повертатися з асинхронної функції fetchData() при її виклику. |
2 | Ініціалізація змінної з ім’ям result , яке буде покликанням на результат виклику асинхронної функції fetchData() , зі значенням, яке функція повертає (проміс). |
3 | Застосовуємо до проміса result метод then() , у який як аргумент передаємо ім’я fetchedData , ініціалізуючи змінну з таким ім’ям (за потреби можна обрати інше ім’я), яке буде покликатися на об’єкт з отриманими даними. Тут змінна fetchedData є локальною, на відміну від попереднього прикладу. |
4 | Викликаємо функцію parseData() з аргументом fetchedData . |
5 | Оголошення функції parseData() , у тілі якої розбираємо отримані дані та додаємо у масив results . |
6 | Друк масиву results . |
Результат виконання застосунку буде аналогічний попередньому прикладу.
Як бачимо, є різні шляхи для отримання даних поза асинхронним викликом і використання їх в іншому місці коду застосунку. Який з них вибрати залежить від ваших цілей і вподобань.
OpenWeather API
Розглянемо ще одну онлайн-платформу під назвою OpenWeather , яка надає низку API
для отримання даних про погоду у будь-якій точці земної кулі.
Безплатний план використання платформи має обмеження в кількості запитів (60 за хвилину / 1 мільйон на місяць) і передбачає надання таких послуг:
-
поточна погода;
-
3-годинний прогноз на 5 днів;
-
основні мапи погоди;
-
візуальний інструмент (дашборд) для роботи з даними про погоду;
-
Air Pollution API
- дані про забруднення повітря; -
Geocoding API
- інструмент для полегшення пошуку місць під час роботи з географічними назвами та координатами.
Дотепер усі API
, які ми використовували, не вимагали обов’язкової автентифікації, тобто використання спеціального унікального ключа, який ідентифікує користувача.
Для безплатного користування послугами OpenWeather API
, необхідно виконати декілька кроків, які стосуються реєстрації:
-
Зареєструватися на сайті.
-
Підтвердити реєстрацію у листі, який надійде на електронну скриньку, що була використана при реєстрації.
Після підтвердження реєстрації на електронну скриньку надійде ще один лист з інструкцією та унікальним ключем, який потрібно буде використовувати у кожному запиті.
В усі наведених нижче прикладах запитів замість назви myAPIKEY використовуйте власний унікальний ключ API .
|
Використаємо готовий варіант запиту
api.openweathermap.org/data/2.5/weather?q=London,uk&APPID=myAPIKEY
який був наведений у листі з інструкцією, для отримання погодних даних у Лондоні.
У цьому запиті використовується два параметри зі значеннями:
-
q
- назва міста та код країни, розділені комою; -
APPID
- унікальний ключAPI
, який завжди можна знайти у розділіMy API keys
свого облікового запису у кабінеті на сайті, де ви також можете створити додаткові ключіAPI
.
Цей запит можна виконати в адресному рядку вебпереглядача та отримати відповідні результати у JSON
-форматі за стандартним налаштуванням. Втім, зробимо це за допомогою застосунку, код якого наведений нижче.
/* jshint esversion: 8 */
function setup() {
noCanvas();
const apiHost = "https://api.openweathermap.org/data/2.5/weather"; (1)
const apiKey = "myAPIKEY"; (2)
const queryGET = `?q=London,uk&APPID=${apiKey}`; (3)
const url = apiHost + queryGET; (4)
async function getWeather() {
const response = await fetch(url);
const data = await response.json();
print(data.name); (5)
print(data.wind.speed); (6)
print(data.weather[0].description); (7)
}
getWeather();
}
function draw() {}
1 | Ініціалізуємо константу з ім’ям apiHost рядком URL -адреси API . |
2 | Ініціалізуємо константу з ім’ям apiKey рядком, який містить унікальний ключ myAPIKEY . |
3 | Ініціалізуємо константу з ім’ям queryGET шаблонним рядком, вміст якого починається зі знака питання і містить параметри запиту - назву міста та код країни й унікальний ключ apiKey . |
4 | Ініціалізуємо константу з ім’ям url рядком, який містить конкатенацію (об’єднання) рядків із пунктів 1 і 3. |
5 | Отримуємо доступ до назви міста і друкуємо її в консолі вебпереглядача. |
6 | Отримуємо доступ до значення швидкості вітру у метрах на секунду і друкуємо його в консолі вебпереглядача. |
7 | Отримуємо доступ до загального опису погодних умов і друкуємо його в консолі вебпереглядача. |
У результаті виконання застосунку результати можуть бути такими:
London
9.77
overcast clouds
За потреби виведення результатів можна отримати різними мовами. Наприклад, для української необхідно додати у рядок запиту в параметрі lang=ua код країни ua . Коди країн записуються у форматі ISO 3166 .
|
Запити за географічними координатами є найточнішим способом вказати будь-яке місцеперебування. Якщо потрібно автоматично перетворити назви міст та поштові індекси в географічні координати та навпаки, використовують Geocoding API .
Наприклад, запит на отримання координат за назвою міста (місто Луцьк, Волинська область, Україна) може бути таким:
https://api.openweathermap.org/geo/1.0/direct?q=Lutsk,ua&APPID=myAPIKEY
А так виглядатиме запит на отримання координат за поштовим індексом (45101, місто Рожище, Волинська область, Україна):
https://api.openweathermap.org/geo/1.0/zip?zip=45101,ua&APPID=myAPIKEY
Використовуючи зворотне геокодування можна отримати назву місця (назву міста або назву області) за допомогою географічних координат lat
(широта) і lon
(довгота) (Київ, Україна).
http://api.openweathermap.org/geo/1.0/reverse?lat=50.4547&lon=30.5238&APPID=myAPIKEY
Так визначивши координати свого населеного пункту, можна дізнатися погоду у ньому.
Насамкінець дізнаємось погодні дані у населеному пункті, що має координатами lat=50.9154
і lon=25.2691
.
/* jshint esversion: 8 */
function setup() {
noCanvas();
const apiHost = "https://api.openweathermap.org/data/2.5/weather";
const apiKey = "myAPIKEY";
const queryGET = `?lat=50.9154&lon=25.2691&units=metric&lang=ua&APPID=${apiKey}`; (1)
const url = apiHost + queryGET;
print(url);
async function getWeather() {
const response = await fetch(url);
const data = await response.json();
print(data.name); (2)
print(data.main.temp + `\u00B0C`); (3)
print(data.weather[0].description); (4)
}
getWeather();
}
function draw() {}
Проаналізуємо використані у запиті параметри і отримані результати.
1 | У рядку запиту, окрім параметрів lat=50.9154 , lon=25.2691 і ключа apiKey , використовуються параметр lang=ua для виведення даних у консоль вебпереглядача українською і параметр units=metric , який встановлює для різних значень одиниці вимірювання метричної системи. У такій системі вимірювання величин, наприклад, температура вимірюється у градусах Цельсія, а не у градусах Кельвіна чи Фаренгейта. |
2 | Друкуємо в консолі вебпереглядача назву data.name населеного пункту. |
3 | Друкуємо в консолі вебпереглядача значення температури data.main.temp у градусах (шістнадцяткове значення \u00B0 використовується для позначення символу градуса) для населеного пункту. |
4 | Отримуємо доступ до загального опису погодних умов і друкуємо його в консолі вебпереглядача. |
Результатом виконання застосунку буде коротка інформація про погоду у населеному пункті:
Rozhyshche
-1.04°C
хмарно
Перегляньте сторінку документації , щоб знайти всю технічну інформацію із фактичними прикладами та вичерпним описом запитів, відповідей і параметрів. |
7.3.3. Ресурси
Корисні джерела
7.3.4. Контрольні запитання
Міркуємо Обговорюємо
-
Для чого використовується
API
? -
Наведіть приклади вебсервісів, які мають
API
. -
Опишіть алгоритм створення запитів із параметрами, використовуючи адресний рядок користувача.
-
Як інструменти надають бібліотека
p5.js
і мова програмуванняJavaScript
для роботи зAPI
?
7.3.5. Практичні завдання
Початковий
-
Створити застосунок, який радить вам чим зайнятися, коли ви нудьгуєте, - пропонує вам назву активності для певної кількості учасників. Нижче наведені приклади активностей для різної кількості учасників та покликання для завантаження даних для застосунку. Для роботи із запитами в адресному рядку вебпереглядача використовуйте сайт Bored API Documentation .
Файл-архів з даними у JSON -форматі можна завантажити за покликанням. Дані для цього завдання отримані зі сховища api-example-bored-api .
|
Learn about the Golden Ratio
education
1
Compliment someone
social
2
Play basketball with a group of friends
social
5
Bake a pie with some friends
cooking
3
Go to a music festival with some friends
social
4
-
Перейти на сторінку API Таємниці Марса і виконати в адресному рядку вебпереглядача наведені приклади простих і складених запитів.
Середній
-
Створити застосунок для отримання даних про загадкові артефакти в області
Cydonia Mensae
на Марсі, використавши API Таємниці Марса . У консолі вебпереглядача надрукувати інформацію відповідно до наведеної структури.
артефакт №1
таємниця: опис
пояснення: опис
артефакт №2
таємниця: опис
пояснення: опис
...
-
Створити застосунок, який виконує запит за
URL
-адресоюhttps://my-json-server.typicode.com/typicode/demo/db
і друкує у консоль вебпереглядача дані як у наведеному прикладі.
1: Post 1
2: Post 2
3: Post 3
1: some comment
2: some comment
-
Створити застосунок, який використовує Launch Library API для отримання даних про назви та деталі космічних місій NASA , про які лише відомо, що вони заплановані на грудень 2030 року.
Високий
-
Створити застосунок, який отримує дані (номер, ціна, категорія) про усі товари, використовуючи кінцеву точку
https://dummyjson.com/products
і методfetch()
. У консолі вебпереглядача надрукувати лише ті назви категорій, які містять принаймні один товар з ціною понад1000
умовних одиниць. Назви категорій не повинні повторюватися.
["smartphones", "laptops", "motorcycle"]
-
Створити застосунок, який використовує синтаксис
async/await
та кінцеву точкуhttps://dummyjson.com/quotes
для отримання усіх цитатAlbert Einstein
та їх виведення у консоль вебпереглядача.
Strive not to be a success, but rather to be of value.
A person who never made a mistake never tried anything new.
-
Використовуючи OpenWeather і Geocoding API , отримати інформацію про погоду на цей момент у вашому населеному пункті.
Екстремальний
-
Створити застосунок, який використовує кінцеву точку https://jsonplaceholder.typicode.com/users/1/todos для отримання даних про усі незавершені справи у списку справ кожного із 10 користувачів (наведений приклад
URL
-адреси використовується для першого користувача). Результат необхідно вивести в консоль вебпереглядача відповідно до зразка для двох перших користувачів.
{
"user1": [
1,
2,
3,
5,
6,
7,
9,
13,
18
],
"user2": [
21,
23,
24,
28,
29,
31,
32,
33,
34,
37,
38,
39
]
}
7.4. Дані сенсорів та датчиків
Датчики та сенсори є ключовими компонентами в сучасних технологіях, які забезпечують збір реальних даних з навколишнього середовища. Вони використовуються для вимірювання фізичних, хімічних та біологічних властивостей, надаючи цінну інформацію для різних галузей, включаючи науку, медицину, Інтернет речей (IoT
), робототехніку та інші галузі.
Ось лише невелика частина прикладів застосування датчиків і сенсорів:
-
вимірювання за допомогою датчиків параметрів фізичних явищ та перебігу процесів у лабораторних умовах;
-
датчики для моніторингу здоров’я людини, які вимірюють пульс, температуру тіла та інші показники;
-
використання сенсорів для автоматичного управління освітленням та температурою у розумному будинку;
-
автоматизації процесу виробництва тощо.
Датчики здатні відстежувати параметри, такі як температура, вологість, тиск, освітленість, акустичні характеристики та багато інших. Сенсори зі свого боку можуть реагувати на конкретні подразники, такі як рух, звук або дотик.
Терміни «датчик» і «сенсор» часто використовуються як синоніми, але загалом, датчик - це пристрій, який вимірює фізичні величини, як-от температура чи прискорення, водночас сенсор - це пристрій, який реагує на конкретний стимул, як-от дотик або світло. |
Сьогодні ці пристрої стали важливими джерелами даних і є невіддільною частиною мобільних пристроїв, як-от смартфони, навігатори, планшети тощо.
Бібліотека p5.js
має низку функцій та вбудованих змінних для роботи з сенсорами та датчиками пристроїв. Застосуємо їх для отримання даних із сенсорів та датчиків мобільного пристрою.
Для наших цілей будемо використовувати:
-
мобільний пристрій - смартфон;
-
вебпереглядач на мобільному пристрої -
Google Chrome
; -
середовище запуску ескізів - онлайн-редактор p5.js Web Editor .
7.4.1. Акселерометр і гіроскоп
Розглянемо код застосунку, який виводить на полотні назву орієнтації екрана смартфона на цей момент: portrait
(книжкова) або landscape
(альбомна).
function setup() {
createCanvas(200, 200);
textAlign(CENTER, CENTER);
textSize(16);
}
function draw() {
background(220);
let oriented = deviceOrientation; (1)
fill(51, 102, 153); // Lapis Lazuli
text(oriented, width / 2, height / 2); (2)
}
1 | Ініціалізуємо змінну з ім’ям oriented значенням вбудованої у бібліотеку p5.js змінної deviceOrientation , яка містить значення орієнтації екрана смартфона. Якщо дані про орієнтацію не можна отримати, змінна deviceOrientation набуває значення undefined . |
2 | Виводимо у центрі полотна значення oriented . |
Запустивши на смартфоні застосунок, отримаємо на полотні назву орієнтації екрана на цей момент. Якщо у смартфоні увімкнути опцію Автоматичний поворот екрана і повертати пристрій, назви на полотні будуть змінюватися в залежності від орієнтації екрана.
Положення смартфона у просторі регулюється за допомогою таких датчиків:
-
акселерометр - вимірює прискорення пристрою вздовж трьох осей. За допомогою даних з акселерометра можна визначити такі рухи, як розгойдування, нахили, обертання, струшування.
-
гіроскоп - вимірює кутову швидкість обертання пристрою навколо трьох осей.
При використанні обидвох датчиків, смартфон може автоматично адаптувати орієнтацію екрана в залежності від того, у якому положенні перебуває. Наприклад, ці датчики використовуються для вимірювання зміни кута нахилу мобільних пристроїв при керуванні рухом об’єктів у іграх, коли сам мобільний пристрій використовується як геймпад.
Для отримання даних акселерометра смартфона використовуються змінні accelerationX , accelerationY , accelerationZ , які містять значення прискорення пристрою вздовж відповідних осей, виміряні у квадратних метрах за секунду.
Напишемо код застосунку, у якому величина прискорення смартфона вздовж осі X
відображатиметься як розмір кола на полотні.
function setup() {
createCanvas(200, 200);
noStroke();
}
function draw() {
background(220);
fill(255, 140, 66); // Pumpkin
ellipse(width / 2, height / 2, accelerationX * 10); (1)
print(accelerationX, accelerationY, accelerationY); (2)
}
1 | Оскільки величина accelerationX набуває невеликих значень, для відображення суттєвих змін розміру кола використовуємо множення на 10 . |
2 | В консоль вебпереглядача друкуємо значення прискорення за трьома осями. |
Тепер, якщо смартфон, що перебуває у горизонтальному положенні, різко повертати вліво-вправо, на полотні спостерігатимемо зміну розміру кола.
Переглядаємо Аналізуємо
Роботу гіроскопа смартфона можна фіксувати, використовуючи змінні rotationX , rotationY , rotationZ , що містять значення кутів обертання смартфона навколо відповідних осей.
function setup() {
createCanvas(200, 200, WEBGL); (1)
debugMode(AXES, 200, -75, -75, 0); (2)
}
function draw() {
background(220);
orbitControl(); (3)
rotateY(radians(rotationY)); (4)
box(75, 75, 75); (5)
}
1 | Увімкнення режиму 3D (третім аргументом у виклику функції createCanvas() є значення WEBGL ), початок координат розміщується в центрі полотна. |
2 | Використання функції debugMode() для малювання індикатора осей на полотні, де 200 - довжина осей, -75 - зсув осей X та Y від центру полотна вліво і вгору, 0 - вісь Z починається в центрі полотна. |
3 | Використання функції orbitControl() , яка дозволяє переміщатися по тривимірному ескізу за допомогою миші або дотику. |
4 | Використання змінної rotationY (на відміну від rotationX і rotationY , rotationY доступна лише для мобільних пристроїв із вбудованим компасом), яка набуває значення у радіанах від 0 до 2 * PI . Функція rotateY() повертає фігуру на величину, визначену параметром кута rotationY у радіанах. |
5 | Малювання куба за допомогою функції box() . |
Якщо використовуються одночасно усі функції (rotateX , rotateY , rotateZ ) для поворотів навколо осей, важливим є порядок їх викликів, а саме, Z-X-Y , щоб уникнути несподіваної поведінки.
|
За допомогою дотику до екрана можна обертати куб і осі одночасно, а повертання смартфона вправо-вліво буде впливати на обертання лише куба навколо осі Y
зеленого кольору (вісь X
- червоного кольору, вісь Z
- синього кольору).
Переглядаємо Аналізуємо
Бібліотека p5.js
має кілька готових функцій, які викликаються за певних значень даних, отриманих із датчиків смартфона.
Наприклад, функція deviceMoved() викликається, коли пристрій переміщується вздовж осей далі, ніж порогове значення, яке за стандартним налаштуванням дорівнює 0.5
. За потреби порогове значення можна змінити за допомогою функції setMoveThreshold() .
Розглянемо код застосунку, який змінюватиме зображення на полотні у разі руху смартфона у просторі.
let c = 0; (1)
let threshold = 0.5; (2)
function setup() {
createCanvas(200, 200);
setMoveThreshold(threshold); (3)
}
function draw() {
background(46, 41, 78); // Space cadet
stroke(c); (4)
fill(46, 41, 78);
strokeWeight(25);
circle(width / 2, height / 2, width / 2);
}
function deviceMoved() { (5)
c = c + 10;
threshold = threshold + 0.1;
if (c > 255) {
c = 0;
threshold = 0.5;
}
setMoveThreshold(threshold);
}
1 | Ініціалізуємо змінну c значенням 0 . Її ім’я буде покликатися на значення кольору, який у процесі руху смартфона буде змінюватися. Початковий колір - чорний. |
2 | Ініціалізуємо змінну threshold пороговим значенням 0.5 , вихід за межі якого буде викликати функцію deviceMoved() . |
3 | Встановлюємо порогове значення threshold за допомогою функції setMoveThreshold() . |
4 | Встановлюємо поточне значення кольору для контуру об’єкта на полотні. Об’єктом є коло, яке має контур, товщина якого встановлена за допомогою функції strokeWeight() у пікселях. Кольори зафарбовування кола і полотна однакові, що утворює на полотні ілюзію зображення кільця. |
5 | Оголошення функції deviceMoved() , у тілі якої змінюється значення кольору c і порогу threshold . За умови, коли значення c стає понад 255 , c і threshold набувають початкових значень. При кожному виклику функції deviceMoved() за допомогою функції setMoveThreshold() встановлюється значення threshold на цей момент. |
Переміщуючи смартфон у просторі вздовж осей X
, Y
або Z
можна фіксувати на полотні зміну кольору контуру кола у відтінках сірого кольору.
Переглядаємо Аналізуємо
Коли пристрій повертати більш ніж на 90
градусів, викликається функція deviceTurned() .
Назва осі, яка викликає deviceTurned()
, зберігається в змінній під ім’ям turnAxis
. Завдяки цьому можна передбачити у коді, яку вісь потрібно брати до уваги, шляхом порівняння значення turnAxis
з X
, Y
або Z
. Проілюструємо таку поведінку за допомогою наступного коду.
let c, c1, c2; (1)
function setup() {
createCanvas(200, 200);
c1 = color(231, 29, 54); // Red (Pantone)
c2 = color(27, 153, 139); // Persian green
c = c1; (2)
rectMode(CENTER);
noStroke();
}
function draw() {
background(220);
fill(c); (3)
rect(width / 2, height / 2, 100, 100);
}
function deviceTurned() {
if (turnAxis === "X") { (4)
if (c === c1) {
c = c2;
} else if (c === c2) {
c = c1;
}
} else {
print(turnAxis); (5)
}
}
1 | Оголошуємо змінну c , ім’я якої буде покликатися на значення кольору на цей момент, та змінні c1 і c2 , що матимуть сталі значення кольорів відповідно. |
2 | Встановлюємо значення c1 для c як значення кольору на цей момент. |
3 | Використовуємо значення поточного кольору c для зафарбовування квадрата на полотні. |
4 | У тілі функції deviceTurned() перевіряємо, чи пристрій здійснює обертання навколо осі X (обертання від користувача і до нього) більш ніж на 90 градусів. Якщо так, то поточний колір c набуває почергово значень c1 і c2 . |
5 | Якщо ж обертання пристрою відбувається навколо інших осей, назви цих осей друкуються в консолі вебпереглядача. |
Отож, обертаючи смартфон навколо осі X
(від себе чи до себе) на понад 90
градусів, можна спостерігати, як квадрат змінює колір.
Переглядаємо Аналізуємо
Розглянемо ще одну функцію з ім’ям deviceShaken() , яка викликається, коли зміни загального прискорення пристрою для значень accelerationX
і accelerationY
перевищують порогове значення. Інакше, коли пристрій струшують.
Стандартним налаштуванням порогового значення є 30
, втім це значення можна змінити за допомогою функції setShakeThreshold() .
Розглянемо застосунок, в якому коло не полотні стає прозорішим завдяки струшуванню смартфона. Оскільки код подібний попередньому, проаналізуємо лише ті рядки, в яких відбулися зміни.
let a = 255; (1)
let threshold = 30;
function setup() {
createCanvas(200, 200);
setShakeThreshold(threshold);
noStroke();
}
function draw() {
background(220);
fill(color(68, 169, 94, a)); // Pigment green
circle(width / 2, height / 2, 100);
}
function deviceShaken() {
a = a - 1;
threshold = threshold - 1;
if (a < 0) { (2)
a = 255;
threshold = 30;
}
setShakeThreshold(threshold);
}
1 | Ініціалізуємо змінну a зі значенням 255 , чиє ім’я буде покликанням на значення прозорості на цей момент. Початкове значення (255 ) - повністю непрозорий. |
2 | У тілі функції deviceShaken() зменшуємо значення прозорості при кожному струшуванні пристрою і перевіряємо, чи стало воно меншим нуля. Якщо так, повертаємо початкове значення для a . |
Переглядаємо Аналізуємо
Вправа 83
Змінити код застосунку, щоб при струшуванні смартфона полотно зафарбовувалось випадковим кольором.
Окрім вищезгаданих датчиків, мобільні пристрої оснащуються й іншими датчиками, серед яких:
-
датчик наближення - визначає факт наближення до смартфона будь-яких об’єктів, наприклад, у разі наближення смартфона до вуха під час розмови, вимикає підсвічування екрана;
-
датчик освітлення - автоматично регулює яскравість екрана, роблячи роботу із пристроєм комфортнішою, у такий спосіб економиться споживана енергія;
-
компас - відстежує орієнтацію пристрою у просторі відносно магнітних полюсів Землі.
7.4.2. Сенсорний екран
Сенсорний екран мобільного пристрою є важливим елементом, який розширює можливості взаємодії з пристроєм, дозволяючи користувачам взаємодіяти за допомогою дотику та рухів пальця. Погляньмо, які інструменти для роботи із сенсорним екраном смартфона пропонує бібліотека p5.js
.
Для отримання даних про позиції усіх точок дотику використовується системна змінна touches .
touches
є масивом, кожен елемент якого є об’єктом із властивостями id
(ідентифікатор дотику), x
та y
(координати точки дотику відносно початку системи координат полотна, який за стандартним налаштуванням розташований у верхньому лівому куті полотна у точці (0, 0)
).
Розглянемо приклад застосунку, який фіксує кількість дотиків до екрана смартфона і відображає цю інформацію на полотні.
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
let numberTouches = touches.length + " дотиків"; (1)
text(numberTouches, 10, 20); (2)
for (let i = 0; i < touches.length; i++) { (3)
print(touches[i]);
}
}
1 | Ініціалізуємо змінну numberTouches значенням кількості елементів масиву touches . |
2 | Відображаємо значення numberTouches на полотні. |
3 | У циклі for проходимо вмістом масиву touches і друкуємо його елементи (об’єкти) в консолі вебпереглядача. |
У разі запуску застосунку, дотики до екрану смартфона як в області полотна, так і поза ним в області перегляду, будуть фіксуватися у масиві touches
, а на полотні відображатиметься значення довжини масиву, яке і визначатиме їх одночасну кількість.
Переглядаємо Аналізуємо
Один раз після кожного дотику викликається функція touchStarted() . Якщо функція touchStarted()
не оголошена у коді, замість неї буде викликана функція mousePressed() (викликається один раз після кожного натискання кнопки миші), якщо вона оголошена.
Щоразу, коли дотик завершується, викликається інша функція - touchEnded() . Якщо функція touchEnded()
не оголошена у коді, замість неї буде викликана функція mouseReleased() (викликається щоразу, коли відпускається кнопка миші), якщо вона оголошена.
Вебпереглядачі за стандартним налаштуванням можуть мати різну поведінку, пов’язану з різними подіями дотику. Щоб запобігти будь-якій поведінці за стандартним налаштуванням для цієї події, додайте return false у кінець тіла функцій для роботи із сенсорним екраном.
|
Візуалізуємо виклики обидвох функцій на прикладі застосунку, у якому за дотиком на екрані об’єкт збільшується у розмірі, а коли дотик завершується - розмір об’єкта повертається до свого початкового значення.
function setup() {
createCanvas(200, 200);
background(220);
noStroke();
}
function draw() {
fill(188, 182, 255); // Periwinkle
}
function touchStarted() { (1)
createCircle(100);
return false;
}
function touchEnded() { (2)
createCircle(50);
return false;
}
function createCircle(d) { (3)
background(220);
ellipse(width / 2, height / 2, d);
}
1 | Оголошення функції touchStarted() , яка буде викликатися щоразу після дотику до екрана смартфона. У її тілі буде викликатися користувацька функція createCircle() , яка як аргумент отримуватиме значення діаметра кола. |
2 | Оголошення функції touchEnded() , яка буде викликатися щоразу після завершення дотику до екрана смартфона. У її тілі також буде викликатися користувацька функція createCircle() , яка як аргумент отримуватиме значення діаметра кола, меншого у два рази. |
3 | Оголошення користувацької функції createCircle() для малювання кола. |
Щоб створити ефект збільшення розміру кола за дотиком і повернення його до початкового розміру, у тілах функцій createCircle()
і setup()
використовується зафарбовування тла полотна однаковим (сірим) кольором.
Переглядаємо Аналізуємо
Функція touchMoved() - ще одна функція, яку можна використовувати для взаємодії із сенсорним екраном пристрою.
Вона викликається щоразу, коли реєструється рух дотиком. Якщо функція touchMoved()
не оголошена у коді, замість неї буде викликана функція mouseDragged() (викликається один раз під час кожного натискання кнопки миші та руху її вказівника), якщо вона оголошена.
Розглянемо приклад застосування функції touchMoved()
, створивши просту версію графічного редактора для малювання ліній на полотні за допомогою дотику.
function setup() {
createCanvas(200, 200);
background(239, 216, 29); // Citrine
stroke(0);
strokeWeight(5);
}
function draw() {}
function touchMoved() {
line(mouseX, mouseY, pmouseX, pmouseY);
return false;
}
Результатом виконання застосунку буде інтерактивне полотно, торкаючись якого і здійснюючи рухи дотиком на якому можна буде малювати як олівцем.
Переглядаємо Аналізуємо
Вправа 84
Змінити код застосунку для малювання дотиком ліній у формі кіл.
Як бачимо, бібліотека p5.js
надає простий інтерфейс для отримання даних сенсорів та датчиків, які можна використовувати для створення інтерактивних застосунків, що реагують на дотики та рухи користувачів.
7.4.3. Ресурси
Корисні джерела
7.4.4. Контрольні запитання
Міркуємо Обговорюємо
-
Що таке «акселерометр»?
-
Які основні функції гіроскопа і які дані він надає?
-
В чому відмінність між «датчиком» та «сенсором»?
7.4.5. Практичні завдання
Початковий
-
Створити застосунок, який у точках дотику на екрані малює кола різного кольору. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Середній
-
Створити застосунок, який реагує на автоматичний поворот смартфона у просторі, малюючи на полотні прямокутник альбомної чи книжкової орієнтацій з відповідним написом як на малюнках.

-
Створити застосунок, який реагує на повертання смартфона у просторі. Якщо обертання відбувається навколо осі
X
, у випадкових точках на полотні малюються кола малого діаметра, якщо обертання відбувається навколо осіY
- великого. У колах записуються назви відповідних осей. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Високий
-
Створити застосунок-гумку, який за допомогою рухомих дотиків на екрані смартфона поступово видаляє тло, за яким приховане зображення. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Зображення, яке використовується в застосунку, можна завантажити за покликанням. |
-
Створити застосунок, в якому за допомогою руху дотиком від однієї до точки до іншої малюється лінія з колами на обидвох її кінцях. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Екстремальний
-
Створити застосунок, в якому вгорі полотна у випадковій точці з’являється кулька і відразу починає рухатися вертикально вниз. Її можна піймати дотиком, утримувати та переміщувати горизонтально. Кульку необхідно скерувати на платформу, що з’являється внизу полотна. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок, в якому користувач, роблячи нахили смартфона керує кулькою на полотні. Кулька має зібрати на полотні інші кульки меншого діаметра. Орієнтовний взірець роботи застосунку представлений в демонстрації.
7.5. Візуалізація даних, отриманих із зовнішніх джерел
В сучасному світі збір та обробка даних є невіддільною частиною багатьох програмних продуктів. Щоб наочно представити отримані дані, розробники використовують різноманітні інструменти для візуалізації.
Візуалізація даних - це процес перетворення числової інформації в графічні або візуальні форми, які допомагають розуміти, аналізувати та знаходити зв’язки у даних. Це дозволяє людям швидше і легше сприймати складну інформацію, яку було б важко розгледіти у вигляді сухих цифр. |
Інтерфейс програмного продукту тісно пов’язаний з візуалізацією даних, оскільки через інтерфейс користувач взаємодіє з даними та може їх сприймати та аналізувати.
Сучасні програмні продукти надають інтерактивні можливості для візуалізації даних у вигляді графіків, діаграм, мап. Ці можливості включають масштабування вигляду графіків, перетягування об’єктів на мапі, фільтрацію даних за певними параметрами в реальному часі, вибір діапазонів даних тощо на будь-яких пристроях.
Бібліотека p5.js
дозволяє легко візуалізувати дані із різних джерел, роблячи їх доступними для розуміння та аналізу. Розглянемо, як можна використовувати p5.js
для візуалізації даних, отриманих із зовнішніх джерел, як-от API
, файли форматів TXT
, CSV
та JSON
.
7.5.1. Файли
Збережемо декілька афоризмів відомого польського письменника-сатирика XX століття Станіслава Єжи Леца у файлі quotes.txt (формат для зберігання звичайного тексту) і візуалізуємо його текстовий вміст.
Обчислимо кількість слів у кожному рядку тексту, використовуючи пропуск як розділювач, і намалюємо на полотні низку кіл відповідного діаметру.
let quotes; (1)
function preload() {
quotes = loadStrings("quotes.txt"); (2)
}
function setup() {
createCanvas(200, 200);
noStroke();
textAlign(CENTER, CENTER);
background(96, 77, 83); // Wenge
for (let i = 0; i < quotes.length; i++) {
let words = quotes[i].split(" "); (3)
let d = words.length; (4)
fill(255, 219, 218, 128); // Misty rose
ellipse(i * 35 + 25, height / 2, d * 5, d * 5); (5)
fill(255);
text(d, i * 35 + 25, height / 2); (6)
print(`${i + 1}. ${quotes[i]}`); (7)
}
}
function draw() {}
Проаналізуємо наведений код.
1 | Оголошуємо змінну з ім’ям quotes , що буде покликатися на масив, елементами якого будуть текстові рядки афоризмів. |
2 | Використовуємо функцію loadStrings() , яка прочитає вміст файлу quotes.txt , для заповнення масиву quotes окремими рядками файлу. На місці імені файлу може бути URL -адреса для завантаження даних. Оскільки функція є асинхронною, тобто її виклик може не завершитися до виконання наступного рядка ескізу, тому використовуємо функцію preload() . |
3 | Проходимо у циклі for по масиву quotes і на кожній ітерації ініціалізуємо змінну words масивом слів кожного афоризму quotes[i] . Масив слів створюється за допомогою метода split() , який використовує як розділювач символ пропуску. |
4 | Обчислюємо кількість слів d у поточному афоризмі, обчисливши довжину масиву words за допомогою метода length() . |
5 | Малюємо на полотні коло, діаметр якого має значення d * 5 , де d - кількість слів у поточному афоризмі. Оскільки кількість слів у афоризмі є невеликою, число 5 застосовується як множник для додаткового збільшення розміру для усіх кіл. |
6 | В центрі кола відображаємо значення кількості слів у афоризмі. |
7 | Використовуючи шаблонний рядок, друкуємо в консолі вебпереглядача текст афоризму. Нумерація афоризмів починається з 1 . |

Окрім аналізу зображення на полотні, у консолі вебпереглядача можна прочитати самі афоризми.
1. Щоб дістатися джерела, треба пливти проти течії.
2. Є люди, котрі полюбляють поразки, бо їх тоді запрошують на бенкети переможців.
3. Болото створює часом враження глибини.
4. Багато речей так і не з’явились через неможливість їхнього найменування.
5. Коли народ позбавлений голосу, це можна помітити навіть при співанні гімнів.
Дуже часто дані візуалізують за допомогою різноманітних графіків та діаграм. Отже, створимо стовпчикову діаграму (гістограму) популяції птахів на основі даних з файлу birds.csv. Напишемо і проаналізуємо код застосунку для побудови гістограми.
let birds; (1)
function preload() {
birds = loadTable("birds.csv", "csv", "header"); (2)
}
function setup() {
createCanvas(400, 200);
}
function draw() {
background(220);
let x = 15; (3)
let y = height / 2 + height / 3;
let barWidth = 45;
for (let i = 0; i < birds.getRowCount(); i++) { (4)
let birdPopulation = birds.getNum(i, "кількість");
let barHeight = map(birdPopulation, 100000, 1500000, 50, 150);
let birdName = birds.getString(i, "назва");
let birdColor = birds.getString(i, "колір");
fill(getColor(birdColor)); (5)
rect(x, y - barHeight, barWidth, barHeight); (6)
fill(0);
textAlign(CENTER, TOP);
text(birdName, x + barWidth / 2, y + 10); (7)
x += barWidth + 20; (8)
}
}
function getColor(birdColor) { (9)
switch (birdColor) {
case "Рожевий":
return color(229, 134, 123); // Coral pink
case "Сірий":
return color(128); // Gray
case "Білий":
return color(255); // White
case "Коричневий":
return color(154, 80, 27); // Brown
default:
return color(0); // Black
}
}
1 | Оголошуємо змінну з ім’ям birds , що буде покликатися на об’єкт завантажених даних. |
2 | Використовуємо функцію loadTable() для завантаження даних з CSV -файлу. |
3 | Ініціалізуємо змінні із початковими даними щодо розміщення стовпчиків діаграми, а саме: x - відступ першого стовпця діаграми від лівої межі полотна по горизонталі, y - відступ від верхньої межі до основи діаграми по вертикалі, barWidth - ширина стовпчиків діаграми. |
4 | Отримання при проході у циклі for доступу до значень табличних даних, які містяться у двох масивах з назвами columns (стовпці) і rows (рядки) відповідно: birdPopulation - значення на перетині стовпця з назвою "кількість" та i -го рядка, barHeight - формування висоти стовпця, використовуючи функцію map() , що змінює поточний діапазон значень (від 100000 до 1500000 ) на новий діапазон значень (від 50 до 150 ) для birdPopulation , birdName - значення на перетині стовпця з назвою "назва" та i -го рядка, birdColor - значення на перетині стовпця з назвою "колір" та i -го рядка. |
5 | Отримання значення кольору для стовпця даних на діаграмі із користувацької функції getColor() , яка викликається з аргументом birdColor . |
6 | Малювання стовпця гістограми у вигляді прямокутника. За стандартним налаштуванням у функції rect() перші два параметри (x , y - barHeight ) встановлюють розташування верхнього лівого кута прямокутника, а третій (barWidth ) і четвертий (barHeight ) - ширину і висоту фігури відповідно. |
7 | Відображення під основою гістограми назви птаха birdName . |
8 | Збільшення відступу від лівої межі полотна по горизонталі для наступного стовпця діаграми на значення barWidth + 20 . |
9 | Оголошення користувацької функції getColor() , яка повертає значення кольору за назвою birdColor , яка передається їй як аргумент при її виклику. У тілі функції використовується конструкція розгалуження switch . |
Результатом виконання застосунку буде стовпчикова діаграма з даними про популяцію птахів. Проаналізувавши гістограму можна легко зробити висновок, що найменша популяція серед птахів у представників фламінго.

Розглянемо як джерело даних для візуалізації вміст JSON
-файлу cities.json про міста світу.
Використовуючи географічні координати міст, візуалізуємо точки розташування міст на мапі світу, зображення якої можна безплатно завантажити на сайті GISGeography або завантажити локально.
Запишемо код застосунку, який намалює напівпрозорі різноколірні кола на карті у тих точках, де розташовані міста. Діаметри кіл відповідатимуть значенням кількості населення у містах.
let cities, world; (1)
let finished = false; (2)
function preload() {
cities = loadJSON("cities.json"); (3)
world = loadImage("World-Map-Latitude-Longitudes.jpg"); (4)
}
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
if (cities && world && !finished) { (5)
image(world, 0, 0, width, height);
drawCities();
finished = true;
}
}
function drawCities() { (6)
for (let i = 0; i < cities.cities.length; i++) {
let city = cities.cities[i];
let population = city.population; (7)
let d = map(population, 0, 3562000, 10, 50); (8)
// перетворення координат на мапі до координат на полотні
let x = map(city.coordinates.longitude, -180, 180, 0, width); (9)
let y = map(city.coordinates.latitude, 90, -90, 0, height);
// відображення кола на мапі
noStroke();
fill(random(255), random(255), random(255), 128);
ellipse(x - 3, y - 27, d, d); (10)
}
}
1 | Оголошуємо змінні cities і world , імена яких будуть покликаннями на об’єкти завантажених даних із файлу cities.json і завантаженого зображення World-Map-Latitude-Longitudes.jpg відповідно. |
2 | Ініціалізуємо змінну finished значенням false , яка визначатиме завершення процесу завантаження як даних, так і зображення. |
3 | Завантаження в ескіз даних з файлу cities.json . |
4 | Завантаження в ескіз зображення з файлу World-Map-Latitude-Longitudes.jpg . |
5 | У блоці draw() виконуємо перевірку виразу cities && world && !finished на істинність. Якщо вираз істинний, розміщуємо завантажене зображення на полотні за допомогою виклику функції image() , перший аргумент якої - це об’єкт world , що містить завантажене зображення. Також, у разі істинності виразу, змінюємо значення finished на true , що сигналізує, усі дані, які мали бути завантажені в ескіз, вже завантажені та викликаємо користувацьку функцію drawCities() . |
6 | Оголошення користувацької функції drawCities() . У тілі функції в циклі for отримуємо доступ до кожного міста cities.cities[i] масиву міст cities.cities та ініціалізуємо змінну з ім’ям city значенням об’єкта конкретного міста. |
7 | Отримуємо з об’єкта city значення властивості city.population під ім’ям population . |
8 | Для population змінюємо поточний діапазон значень (від 0 до 3562000 ) на новий діапазон значень (від 10 до 50 ). |
9 | Змінюємо діапазони значень для географічних координат city.coordinates.longitude та city.coordinates.latitude на діапазони для координат x та y на полотні. |
10 | Малюємо напівпрозоре кольорове коло діаметра d у координатах x та y із врахуванням певного зміщення, яке підбираємо вручну. |
У результаті виконання застосунку отримаємо варіант бульбашкової діаграми.

7.5.2. API
Розглянемо кілька зовнішніх API
для отримання даних та їх візуалізації.
Використаємо простий Dog API для отримання випадкового фото собаки.
let data, breed, breedImage; (1)
function preload() {
data = loadJSON("https://dog.ceo/api/breeds/list/all"); (2)
}
function setup() {
noCanvas();
let breeds = Object.keys(data.message); (3)
breed = random(breeds); (4)
const urlDog = `https://dog.ceo/api/breed/${breed}/images/random`; (5)
async function getBreed() { (6)
const responseDog = await fetch(urlDog);
const dataDog = await responseDog.json();
return dataDog;
}
getBreed().then((b) => { (7)
breedImage = b.message;
let img = createImg(breedImage, breed);
img.attribute("width", "200px");
img.attribute("title", breed);
img.position(0, 0);
});
}
function draw() {}
1 | Оголошуємо змінні з іменами data (об’єкт з отриманими даними про усі породи собак) , breed (назва породи) і breedImage (покликання на зображення собаки). |
2 | Завантажуємо дані про породи собак. |
3 | Отримуємо усі назви властивостей об’єкта data.message , інакше - усі назви порід собак. |
4 | Випадково обираємо назву однієї породи. |
5 | Створюємо запит відповідно до обраної назви породи для отримання випадкового зображення собаки саме цієї породи. |
6 | Запит виконується у тілі асинхронної функції getBreed() . |
7 | Обробляємо відповідь на запит, яку повертає функція getBreed() . Отримуємо покликання breedImage на фото собаки та створюємо елемент <img> у DOM вебсторінки ескізу за допомогою функції createImg() , в яку як аргументи передаємо значення URL -адреси зображення собаки (breedImage ) та назву її породи для атрибута alt (breed ). За допомогою attribute() встановлюємо ширину зображення 200 пікселів і додаємо ще один атрибут title зі значенням назви породи собаки. Використовуючи position() розміщуємо зображення у верхньому лівому куті вікна перегляду. |
Тепер, при наведенні вказівника миші на випадковому зображенні собаки, над ним буде з’являтися назва її породи.
Переглядаємо Аналізуємо
Використаємо сервіси OpenWeather і Geocoding API для отримання метеорологічних значень швидкості та поривів вітру у м/с в певному населеному пункті, а далі застосуємо їх для створення анімації.
/* jshint esversion: 8 */
let weatherData, weather; (1)
let completed = false; (2)
let x = 0; (3)
function setup() {
createCanvas(200, 200);
noStroke();
rectMode(CENTER);
const coords =
"https://api.openweathermap.org/geo/1.0/zip?zip=01001,ua&APPID=myAPIKEY"; (4)
async function getWeather() { (5)
const responseCoords = await fetch(coords);
const dataCoords = await responseCoords.json();
const [lat, lon] = [dataCoords.lat, dataCoords.lon];
const apiHost = "https://api.openweathermap.org/data/2.5/weather";
const apiKey = "myAPIKEY";
const queryGET = `?lat=${lat}&lon=${lon}&units=metric&lang=ua&APPID=${apiKey}`;
const url = apiHost + queryGET;
const responseWeather = await fetch(url);
const dataWeather = await responseWeather.json();
return dataWeather;
}
weatherData = getWeather(); (6)
weatherData.then((wd) => {
weather = wd;
completed = true;
});
}
function draw() {
background(220);
if (completed) { (7)
fill(104, 48, 239); // Electric indigo
rect(x, height / 2, weather.wind.speed * 4); // швидкість вітру
fill(255, 104, 62, 128); // Tomato
circle(x, height / 2, weather.wind.gust * 4); // пориви вітру
x = x + weather.wind.speed / 10 + weather.wind.gust / 10;
if (x > width) {
x = 0;
}
}
}
1 | Оголошуємо змінні з іменами weatherData і weather . Ім’я weatherData буде покликатися на результат роботи асинхронної функції getWeather() , а weather - на об’єкт з отриманими за запитом даними. |
2 | Ініціалізуємо змінну completed значенням false , яка визначатиме завершення запиту на отримання даних. |
3 | Ініціалізуємо змінну x значенням 0 . Це початкове значення x -координати фігур, які будуть рухатися на полотні. Значення x у процесі руху буде змінюватися. |
4 | Рядок запиту на отримання географічних координат населеного пункту, який має індекс 01001 і розташований в Україні (ua ). myAPIKEY - ваш ключ API . |
5 | Оголошення асинхронної функції getWeather() , яка повертає результат запиту на отримання погодних даних за координатами, взятими із пункту 4. |
6 | Ім’ям weather буде покликанням на отримані погодні дані з асинхронної функції getWeather() . Змінна completed набуває значення true . |
7 | Якщо змінна completed має значення true , малюємо на полотні квадрат і коло. Перевіряємо значення x -координати, яке змінюється при виконанні застосунку, чи воно більше за праву межу полотна width , якщо так - присвоюємо x початкове значення 0 . Для повільнішого темпу анімації значення weather.wind.speed і weather.wind.gust ділимо на 10 , а для кращої візуалізації - множимо на 10 при малюванні фігур на полотні. Ці коефіцієнти підбираємо залежно від отриманих значень weather.wind.speed і weather.wind.gust . |
Розміри квадрата визначаються даними про швидкість вітру у м/с (weather.wind.speed
), розміри кола - даними про пориви вітру у м/с (weather.wind.gust
). Аналізуючи розміри цих двох фігур і швидкість анімації можна зробити висновки про погодні вітрові умови в обраному населеному пункті.
Переглядаємо Аналізуємо
7.5.3. Датчики й сенсори
У попередньому розділі вже створювалися застосунки, які отримують дані за допомогою датчиків і сенсорів смартфона. Наведемо ще один приклад такої взаємодії, візуалізуючи дані зі смартфона за допомогою анімації руху двох об’єктів.
let x = 0, y = 0; (1)
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
let aX = accelerationX; (2)
let aY = accelerationY; (3)
x += aX; (4)
y += aY; (5)
fill(245, 238, 158); // Vanilla
ellipse(x, height / 2, 50); (6)
fill(36, 110, 185); // Azul
ellipse(width / 2, y, 50); (7)
}
1 | Ініціалізуємо змінні з іменами x та y початковими нульовими значеннями. Імена цих змінних будуть покликатися на поточні значення координат об’єктів на полотні. |
2 | Ініціалізуємо змінну aX значенням accelerationX прискорення пристрою вздовж осі X . |
3 | Ініціалізуємо змінну aY значенням accelerationY прискорення пристрою вздовж осі Y . |
4 | Збільшуємо значення x -координати на величину aX . |
5 | Збільшуємо значення y -координати на величину aY . |
6 | Малюємо на полотні коло, яке буде рухатися горизонтально у центрі полотна. |
7 | Малюємо на полотні коло, яке буде рухатися вертикально у центрі полотна. |
Тепер, керуючи нахилом смартфона, об’єктами кіл можна керувати.
Переглядаємо Аналізуємо
7.5.4. Ресурси
Корисні джерела
7.5.5. Контрольні запитання
Міркуємо Обговорюємо
-
Які зовнішні джерела можна використовувати для отримання даних та їх візуалізації за допомогою
p5.js
? -
Як здійснити візуалізацію даних, отриманих зовнішнім
API
, у застосунку на основіp5.js
? -
Поміркуйте, які труднощі можуть виникати при отриманні та візуалізації даних за допомогою бібліотеки
p5.js
? Які шляхи їх подолання?
7.5.6. Практичні завдання
Початковий
-
Створити застосунок, який отримує числові дані з файлу
data.txt
і візуалізує їх у вигляді вертикальних ліній відповідної довжини. Орієнтовний взірець результату візуалізації представлений на малюнку.

-
Створити застосунок, який отримує дані з файлу
color.csv
і візуалізує їх. Орієнтовний зразок результату візуалізації представлений на малюнку.

-
Використовуючи
TheCatAPI
та кінцеву точкуhttps://api.thecatapi.com/v1/images/search
отримати дані для розміщення на полотні випадкового фото кота. Орієнтовний зразок результату візуалізації представлений на малюнку.

Середній
-
Створити застосунок, який використовуючи кінцеву точку
https://api.github.com/users
візуалізує на полотні випадкові імена розробників. Орієнтовний зразок роботи застосунку представлений в демонстрації.
-
Створити застосунок, який візуалізує коливання частинки, використовуючи функцію sin() . Коливання описуються рівнянням
y = sin(x * v) * d + height / 2
, деv
- значення, що впливає на швидкість коливання, іd
- відхилення вгору/вниз на полотні отримуються з файлуf.txt
. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок, який отримує дані із датчиків та сенсорів смартфона та використовує їх для візуалізації точок на полотні, координати яких визначаються нахилами пристрою. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Високий
-
Створити застосунок, який отримує дані про стан хмарності та вологості у населеному пункті за допомогою OpenWeather і Geocoding API та візуалізує їх. Орієнтовний взірець результату візуалізації представлений на малюнку.

-
Створити застосунок, який наносить на карту світу назви морів та візуалізує значення максимальної глибини морів. Орієнтовний взірець результату візуалізації представлений на малюнку.
Зображення мапи світу можна безплатно завантажити на сайті GISGeography або завантажити локально. |

Екстремальний
-
Створити застосунок, до якого приєднати інтерактивну мапу за допомогою бібліотеки Leaflet та візуалізувати рух Міжнародної космічної станції в реальному часі. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Завантажити файл із зображенням МКС можна за покликанням. |
Для ознайомлення з бібліотекою Leaflet скористайтеся покроковим підручником або перегляньте навчальне відео .
|
7.6. Поняття та приклади інтерактивних інсталяцій
Інтерактивні інсталяції - це художні або технічні конструкції, які дозволяють взаємодіяти із глядачами або іншими системами в реальному часі. Тобто користувач може активно впливати на об’єкт чи систему, а не просто спостерігати чи отримувати інформацію.
Інтерактивність (від англ. Interaction
- взаємодія) може стосуватися не лише фізичної взаємодії, а й взаємодії з електронними та програмними системами, що робить цей процес більш динамічним і захопливим для користувача.
Інсталяція може охоплювати різні елементи, такі як скульптура, малюнки, світлові та звукові ефекти, а також інші художні чи технічні компоненти. Цей термін також використовується для позначення самого процесу створення та розташування такого об’єкта в певному просторі.
Інтерактивні інсталяції використовують різні технології та засоби, як-от сенсори, кінетику (дослідження явищ і процесів, що змінюються з часом, наприклад, рух тіл у просторі), проєкції, голосові команди тощо. Створення інтерактивних інсталяцій може містити програмування застосунків для обробки введених даних від сенсорів та інших джерел даних.
Інтерактивні інсталяції застосовуються у різних сферах, включаючи мистецтво, науку, освіту, рекламу, розваги та часто встановлюються в галереях мистецтва, музеях, виставкових залах, публічних просторах.
Вони можуть використовуватися для підвищення емоційного зв’язку аудиторії з твором мистецтва, для навчання та розваг, бути платформою для висловлення та дослідження ідей або концепцій через взаємодію та сприйняття.
Захопливим прикладом інтерактивної платформи для проведення практичних музичних експериментів є сервіс Chrome Music Lab , створений Google . Сервіс є музичною лабораторією, що містить бібліотеку інтерактивних інструментів, які можна спробувати відразу у вікні вебпереглядача.
|
Розгляньмо деякі приклади інтерактивних інсталяцій, які вражають не лише своєю естетикою, креативністю та технологічними рішеннями, але і підкреслюють важливість взаємодії мистецтва та технологій.
7.6.1. Дощова кімната
Інтерактивна інсталяція Rain Room (Дощова кімната) від Random International спричинила справжній фурор у світі мистецтва.
Інсталяція використовує систему камер та датчиків для відстежування рухів глядача, дозволяючи йому буквально керувати дощем, який зупиняється у тому місці простору, де фіксується присутність людини.
Це створює унікальне середовище, в якому людські відносини один з одним і з природою все більше переплітаються із технологіями.
Інші роботи студії Random International можна переглянути тут .
|
7.6.2. Басейн
Інтерактивна інсталяція The Pool (Басейн) від Jen Lewin , всесвітньо визнаної художниці-інженерки з Нью-Йорку.
The Pool
- це гігантське поле концентричних кіл, створене за допомогою світлодіодних сенсорів, які світяться, коли їх активують дотиком.
Активуючи кола, відвідувачі створюють своєрідний басейн зі світла.
Інші роботи Jen Lewin можна переглянути тут .
|
7.6.3. Абстрактні ландшафти
Інтерактивна інсталяція XYZT: Abstract Landscapes (Абстрактні ландшафти) від The company Adrien M & Claire B поєднує рух і світло для створення абстрактних ландшафтів.
Експозиційна доріжка інсталяції містить кілька інсталяцій, призначених для фізичного залучення аудиторії, яка може створювати світлові абстрактні поверхні за допомогою рухів.
Зображення створюються в режимі реального часу.
Інші роботи The company Adrien M & Claire B можна переглянути тут .
|
7.6.4. Занурення
Інтерактивна інсталяція Submergence (Занурення) від Squidsoup використовує багато тисяч світлодіодних підвішених джерел світла, щоб створити відчуття присутності та руху у фізичному просторі.
Джерела світла реагують на рухи глядачів і створюють неповторні світлові пейзажі.
Інші роботи Squidsoup можна переглянути тут .
|
Вищенаведені інтерактивні інсталяції спроєктовані для активної взаємодії з глядачами (реагують на рухи, дотик тощо) і мають свої технічні особливості, які визначають їхній рівень складності. Окрім того, інсталяції орієнтовані на певні теми та концепції, такі як природа, абстракція та інші й намагаються викликати емоції або думки.
Усі ці відмінності та спільні риси роблять кожну інтерактивну інсталяцію унікальною, представляючи різноманітність в сучасному мистецтві.
Вправа 85
Запропонувати власну ідею інтерактивної інсталяції. З’ясувати, які засоби та технології потрібно використати для реалізації задуму.
Як бачимо, поєднання технології та креативності відкривають нові можливості для взаємодії глядачів із творами мистецтва, перетворюючи спостерігання в експеримент та відчуття, а інтерактивні інсталяції стають дивовижним зразком того, як сучасне мистецтво вплітається в цифровий світ.
7.6.5. Ресурси
Корисні джерела
7.6.6. Контрольні запитання
Міркуємо Обговорюємо
-
Що називають «інтерактивною інсталяцією»? Наведіть приклади.
-
Які технології та засоби використовуються для створення інтерактивних інсталяцій?
-
Пригадайте, які можливості для творців, які бажають поєднати програмування та мистецтво в єдиному творі, надає бібліотека
p5.js
?
7.6.7. Практичні завдання
Початковий
-
Створити застосунок, в якому коло завжди слідує за вказівником миші. Орієнтовний зразок роботи застосунку представлений в демонстрації.
Середній
-
Намалювати коло у центрі полотна. При натиканні будь-якої кнопки миші на полотні поза фігурою випадково змінюється розмір фігури. Натискання кнопкою миші в межах кола не викликає зміну розмірів фігури. Якщо вказівник миші розташовується в межах кола, то коло зафарбовується. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок, в якому за рух вказівника миші змінює колір тла полотна. Орієнтовний зразок роботи застосунку представлений в демонстрації.
Високий
-
Створити застосунок, в якому створюється гравець, який позначений кольоровою цяткою, та його клон. За допомогою клавіатури можна керувати рухом гравця. Водночас клон також повторює рухи гравця, він розташовується недалеко від гравця і може бути іншого кольору. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок, який візуалізує рух великої кількості частинок в межах полотна. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок, в якому обертається площина, з розміщеними на ній різноколірними сферами довільних розмірів. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Екстремальний
-
Створити застосунок, який імітує вертикальний рух м’ячів під дією сили тяжіння та їх зупинку після кількох відбивань від поверхні. М’ячі падають з різної висоти. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок, в якому м’яч кидають горизонтально з певної висоти. Він рухається горизонтально, відбиваючись від поверхні, до повної зупинки. Орієнтовний взірець роботи застосунку представлений в демонстрації.
-
Створити застосунок, що імітує дощ. Орієнтовний взірець роботи застосунку представлений в демонстрації.
Завантажити файл зі звуком дощу можна за покликанням. |
8. Проєктна діяльність
У цьому розділі представлені теми та демонстрації проєктів, які можна використати як ідеї для власних проєктів. |
8.1. Виконання індивідуальних та колективних проєктів
Середній
8.1.1. Проєкт #1 з образотворчого мистецтва "Графічний редактор"
Розробити простий інтерфейс графічного редактора з базовим функціоналом. Орієнтовний зразок роботи застосунку представлений у демонстрації.
Високий
8.1.2. Проєкт #2 з технологій "Кавова машина"
Створити симуляцію процесу приготування кави за допомогою кавової машини. Орієнтовний зразок роботи застосунку представлений у демонстрації.
8.1.3. Проєкт #3 з алгоритмізації "Візуалізація алгоритму"
Створити візуалізацію алгоритму сортування бульбашкою чи будь-якого іншого алгоритму. Орієнтовний взірець роботи застосунку представлений у демонстрації.
8.1.4. Проєкт #4 з астрономії "Сонячна система"
Створити модель Сонячної системи, яка відтворює планетарні рухи навколо Сонця. Орієнтовний взірець роботи застосунку представлений у демонстрації.
Завантажити файл із зображенням зоряного неба можна за покликанням. |
Екстремальний
8.1.5. Проєкт #5 з біології "Клітини"
Створити симуляцію руху і взаємодії клітин між собою, реакції на зовнішні фактори (наприклад, вказівник миші) тощо. Орієнтовний взірець роботи застосунку представлений у демонстрації.
У цьому проєкті для опису положення, швидкості об’єктів у двовимірному просторі використовується клас p5.Vector . Ознайомитись із поняттям вектора можна у розділі про вектори книги Деніела Шиффмана «The Nature of Code». |
8.1.6. Проєкт #6 "Розробка гри"
Розробити комп’ютерну гру. У демонстрації представлена аркадна гра-шутер з назвою Geometric Chase. Мета гри полягає в тому, щоб керувати об’єктом у формі квадрата в просторі, в якому рухаються інші багатокутники різних форм і розмірів, стріляти та розбивати їх, не стикаючись із жодними з них чи з їх фрагментами, а також відбивати атаки від окремих багатокутників.
Гра з демонстрації розроблена за мотивами Asteroids - популярної гри на ігрових автоматах, що була випущена Atari у 1979 році.
|
8.1.7. Проєкт на свій задум
Творчий проєкт на вільну тему.
8.2. Представлення та захист проєктів
Представлення та захист проєктів за допомогою презентацій і вебсторінок.
Для розміщення на вебсторінці коду застосунку можна скористатися віджетом . |
Словник
- Автентифікація
-
процедура встановлення належності користувачеві інформації в системі пред’явленого ним ідентифікатора.
- Адреса ресурсу (англ.
Uniform Resource Locator
- єдиний вказівник на ресурс,URL
) -
стандартизована адреса певного ресурсу (як-от документ або зображення) в Інтернеті (чи деінде).
- Алгоритм
-
набір інструкцій, які описують порядок дій виконавця для отримання результату розв’язання задачі за скінченну кількість кроків.
- Альфа-канал
-
кольорова складова, яка визначає ступінь прозорості (або непрозорості) кольору (тобто червоного, зеленого та синього каналів) для колірної моделі RGB. Альфа-канал використовується переважно для альфа-композитингу - комбінування зображення з тлом з метою створення ефекту часткової прозорості.
- Амплітуда
-
найбільше відхилення величини, яка періодично змінюється від деякого значення, умовно прийнятого за нульове. У гармонічних коливаннях амплітуда є сталою величиною. Термін «амплітуда» часто вживають у ширшому сенсі - неформально називають рамки, в яких відбуваються зміни будь-якої природи, - щодо величин, які змінюються за законом, більше чи менше наближеним до гармонічного (амплітуда кров’яного тиску), а часом до коливань, що є далекими від гармонічних (амплітуда коливань річної температури). Амплітудою звукової хвилі є гучність, а світлової - яскравість.
- Аплет (англ.
Applet
) -
невеликий прикладний застосунок, що функціонально розширює можливості основного застосунку.
- Асинхронність
-
полягає у відкладенні в часі виконання коду, доки не будуть отримані певні дані чи не настануть деякі події. Наприклад, код
JavaScript
у вебпереглядачі може зупинятися у своєму виконанні, очікуючи настання певних подій, як-от натискання користувача на кнопці, рух вказівником миші тощо. - База даних (англ.
database
) -
організований набір структурованої інформації або даних, які зазвичай зберігаються в електронному вигляді в комп’ютерній системі. Зазвичай базою даних керує система керування базами даних (СКБД).
- Бібліотека
WebGL
(Web Graphics Library
) -
міжплатформний API>
JavaScript
для відтворення високопродуктивної інтерактивної3D
- і2D
-графіки в будь-якому сумісному вебпереглядачі без використання плагінів.WebGL
використовує графічний процесор (англ.graphics processing unit
,GPU
) комп’ютера, спеціалізований апаратний компонент, призначений для ефективного обчислення кольорів багатьох пікселів одночасно для відтворення комп’ютерної графіки на екрані. Бібліотекаp5.js
для роботи зWebGL
використовує спеціальний режимWebGL
. - Вебпереглядач
-
програмне забезпечення для комп’ютера або іншого електронного пристрою, як правило, під’єднаного до Інтернету, що дає можливість користувачеві взаємодіяти з текстом, зображеннями або іншою інформацією на гіпертекстовій вебсторінці.
- Вебсервер (
Web Server
) -
термін може стосуватися апаратного (комп’ютер, який приєднаний до Інтернету і на якому зберігається програмне забезпечення вебсервера та файли сайтів) чи програмного (застосунки, які приймають запит від вебпереглядача, оброблять його, наприклад, шукають у базі даних запитувану вебсторінку і надсилають її (інакше повідомлення про помилку з кодом помилки) назад у вебпереглядач користувача, використовуючи протокол передачі даних
HTTP
,HyperText Transfer Protocol
) забезпечення або обох, які працюють разом. - Вебсторінка (англ.
webpage
) -
інформаційний ресурс, доступний в мережі Інтернет, який можна переглянути у вебпереглядачі. Зазвичай інформація на вебсторінці зберігається у
HTML
-форматі. - Генеративний дизайн
-
підхід до проєктування і дизайну цифрового (сайт, зображення, мелодія, анімація) або фізичного продукту (архітектурна модель, деталь машини). Дизайнер, інженер або інший замовник безпосередньо не шукає розв’язання поставленої задачі, а описує її параметри в застосунку, після чого той створює (генерує) варіанти рішення, які формують бачення продукту. Генеративні системи напівавтономно створюють і відбирають варіанти рішень. Це змінює характер взаємодії людини з системою: застосунок сприймається не як засіб, а як повноцінний учасник творчого процесу.
- Гіпертекст (англ.
Hypertext
) -
текст для перегляду на комп’ютері, який містить зв’язки з іншими документами. Ці зв’язки реалізуються за допомогою гіперпосилань - активний (виділений кольором) текст, зображення чи кнопка на вебсторінці, натиснення на які (активізація гіперпосилання) викликає перехід на іншу сторінку чи іншу частину поточної сторінки.
- Деструктуризація
-
дія, що дозволяє розбивати об’єкт чи масив на змінні під час присвоєння. Використовуючи спеціальний синтаксис, можна розпакувати масиви або об’єкти в купу змінних. Деструктуризація також чудово працює із функціями, які мають багато параметрів.
- Дизайн-код
-
набір правил, який встановлює деталі оформлення забудови міського простору.
- Інтернет речей (англ.
Internet of Things
,IoT
) -
концепція мережі, яка складається із пристроїв, які мають вбудовані технології, що дозволяють здійснювати взаємодію з зовнішнім середовищем, передавати відомості про свій стан і приймати дані ззовні.
- Комп’ютерна графіка
-
розділ інформатики, який вивчає методи цифрового синтезу та обробки зорового контенту засобами комп’ютерної техніки. Розрізняють три види комп’ютерної графіки: растрова графіка, векторна графіка і фрактальна графіка. Вони відрізняються принципами формування зображення при відображенні на екранах пристроїв чи у разі друку на папері. Растрову графіку використовують при розробці електронних (мультимедійних) і поліграфічних видань. Більшість графічних редакторів, призначених для роботи з растровими зображеннями, орієнтовані більше на обробку, а не створення зображення. Прикладами растрових зображень є цифрові світлини. Векторна графіка стосується застосування шрифтів і простих геометричних елементів у створенні зображень. Програмні засоби для роботи з фрактальною графікою призначені для автоматичної генерації зображення шляхом математичних розрахунків. Принцип створення фрактальної художньої композиції полягає не в малюванні чи оформленні, а у програмуванні.
- Комп’ютерне мистецтво, цифрове мистецтво (англ.
Digital art
) -
напрямок медіамистецтва, заснований на використанні комп’ютера, інформаційних технологій, як основи для художнього твору.
- Літерал об’єкта (
object literal
) -
спосіб створити значення об’єкта, буквально записавши його у свій застосунок, наприклад
{}
або{flavor: "vanilla"}
. Усередині{}
можна зберігати кілька властивостей: пари значень, розділені комами. - Масив
-
структура даних для зберігання сукупності елементів, впорядкованих за індексами, які репрезентовані натуральними числами та визначають положення елемента в масиві.
- Мова програмування (англ.
Programming language
) -
штучна мова для опису алгоритмів, які виконуються комп’ютером.
- 3D-модель
-
об’ємне цифрове зображення об’єкта. Тривимірні (
3D
) моделі представляють об’єкт за допомогою набору точок у3D
-просторі, з’єднаних різними геометричними об’єктами, як-от трикутники, лінії, вигнуті поверхні тощо. Сам процес створення об’ємного зображення об’єктів у цифровому форматі називається3D
-моделюванням. - Модель HSV (також HSB)
-
колірна модель, заснована на трьох характеристиках кольору: колірному тоні (
Hue
, варіюється в межах0-360°
, але іноді приводиться до діапазону0-100
або0-1
), насиченості (Saturation
, в межах0-100
або0-1
, чим ближчий цей параметр до нуля, тим колір ближчий до нейтрального сірого) і значенні кольору (Value
, задається в межах0-100
або0-1
). - Модель RGB
-
адитивна (сприйнятий колір можна передбачити шляхом підсумовуванням числових коефіцієнтів кольорів компонентів) колірна модель, що описує спосіб синтезу кольору, за якою червоне, зелене та синє світло накладаються разом, змішуючись у різноманітні кольори.
- Модель RGBA
-
триканальна колірна модель RGB, доповнена четвертим альфа-каналом.
- Об’єкт
-
ключове поняття об’єктоорієнтованих технологій проєктування та програмування; втілення абстрактної моделі окремої сутності (предмету або поняття), що має чітко виражене функціональне призначення в деякій області, належить до визначеного класу та характеризується своїми властивостями та поведінкою. Об’єкти є базовими елементами побудови застосунку — застосунок в об’єктоорієнтованому програмуванні розглядається як сукупність об’єктів, що знаходяться у визначених відношеннях та обмінюються повідомленнями.
- Об’єктоорієнтоване програмування
-
метод програмування, який розглядає програму як сукупність об’єктів, які взаємодіють між собою. Кожен з об’єктів є екземпляром певного класу, а класи є членами певної ієрархії наслідування. Клас можна визначити як певну сукупність даних (характеристик об’єкта) та методів роботи з ними.
- Об’єктна модель документа (англ.
Document Object Model
,DOM
) -
міжплатформний інтерфейс програмування застосунків (API) для маніпулювання
HTML
-документами.DOM
дозволяє застосункам отримувати доступ до вмістуHTML
-документів, змінювати вміст, структуру та оформлення таких документів.HTML
іJavaScript
взаємодіють між собою за допомогоюDOM
. - Перспектива камери
-
ефект, який відтворює враження глибини та простору в тривимірному зображенні, завдяки чому об’єкти на відстані здаються меншими, тоді як об’єкти, що знаходяться ближче, здаються більшими. Коли ми дивимося на об’єкти в реальному світі, об’єкти, розташовані далеко, здаються меншими, оскільки лінії їхнього перетину з нашим зором прямують до однієї точки в безмежності, відомої як точка зникнення. Цей ефект перспективи можна відтворити в тривимірному комп’ютерному зображенні за допомогою перспективної проєкції камери. Перспектива камери є важливим аспектом в тривимірній графіці, оскільки вона додає візуальну глибину, реалістичність та враження простору до
3D
-сцен і допомагає відтворити сприйняття простору та розташування об’єктів в тривимірному просторі. - Піксель (елемент зображення)
-
найдрібніша одиниця цифрового зображення в растровій графіці. Він являє собою неподільний об’єкт зазвичай квадратної форми, що має певний колір. Будь-яке растрове комп’ютерне зображення складається з пікселів, розташованих рядами по горизонталі й вертикалі. Якщо збільшувати масштаб зображення, пікселі перетворюються на великі зерна і їх можна побачити. Пікселі не є мірою розміру зображення, оскільки багато цифрових апаратних пристроїв використовують показник пікселі на дюйм (англ.
Pixels Per Inch
,PPI
) - одиницю вимірювання щільності пікселів, яка визначає число пікселів, що припадають на дюйм поверхні. Це впливає більше на якість зображення, ніж на його фізичний розмір. - Піксельна графіка (англ.
Pixel art
) -
напрямок цифрового мистецтва, яке полягає у створенні зображень на рівні пікселів. Далеко не всі растрові зображення є піксель-артом, хоча всі вони складаються з пікселів. Зрештою, поняття
pixel art
вміщує в себе не стільки результат, скільки процес створення ілюстрації піксель за пікселем. Якщо ви візьмете цифрову світлину і сильно її збільшите (так, щоб пікселі стали видимі), то це не вважатиметься піксель-артом. - Початковий код, програмний код (англ.
source code
) -
набір інструкцій, написаних мовою програмування у формі, що її може прочитати та модифікувати людина.
- Прикладний програмний інтерфейс, інтерфейс програмування застосунків (англ.
Application Programming Interface
,API
) -
набір протоколів взаємодії та засобів для створення програмного забезпечення.
API
надає розробнику засоби для швидкої розробки програмного забезпечення і використовується для вебсистем, операційних систем, баз даних тощо. - Програмне забезпечення, програмні засоби, ПЗ (англ.
software
) -
сукупність програм, призначених для розв’язання різних завдань за допомогою комп’ютера.
- Програмування
-
процес проєктування, написання, тестування, налагодження і підтримки комп’ютерних застосунків. У більш конкретному значенні програмування розглядається як кодування - реалізація у вигляді застосунку одного чи кількох взаємопов’язаних алгоритмів за допомогою мови програмування.
- Процедурне програмування
-
метод програмування, заснований на концепції виклику процедури. Процедури, також відомі як підпрограми, методи, або функції.
- Растрова графіка
-
складова комп’ютерної графіки, яка має справу зі створенням, обробкою та зберіганням растрових зображень. Растрове зображення є масивом кольорових точок - пікселів.
- Рендеринг
-
процес створення зображення або відео з тривимірної сцени (3D-сцени), який охоплює обчислення освітлення, тіней, кольорів та інших візуальних ефектів для отримання реалістичного зображення або анімації. У контексті тривимірної графіки, рендеринг відбувається за допомогою графічного процесора (
GPU
) або спеціалізованого програмного забезпечення, яке обробляє геометрію об’єктів, розраховує освітлення та тіні, застосовує текстури та матеріали до поверхонь об’єктів, та виконує інші обчислення, щоб згенерувати тривимірне зображення. Рендеринг використовується в багатьох галузях, включаючи комп’ютерну графіку, відеоігри, візуалізацію даних, архітектурне моделювання, виробництво фільмів. - Робототехніка (англ.
robotics
) -
прикладна наука, що опікується проєктуванням, розробкою, виготовленням та використанням роботів, а також комп’ютерних систем для керування ними.
- Розумний дім (розумний будинок,
smart home
) -
система домашніх пристроїв, здатних виконувати дії й вирішувати певні повсякденні завдання без участі людини. Пристрої можуть бути під’єднані до комп’ютерної мережі, що дозволяє керувати ними віддалено через Інтернет. Така система повинна вміти розпізнавати конкретні ситуації, що відбуваються в будинку, і відповідним чином на них реагувати.
- Семпл
-
відрізок аудіоінформації, вирізаний або записаний з якого-небудь наявного джерела. Наприклад, звук акустичного музичного інструменту, звук техногенного чи природного походження, звук, вирізаний з наявної аудіокомпозиції чи відеофільму тощо.
- Семплер
-
електронний або цифровий музичний інструмент, який використовує звукові записи (семпли) реальних звуків інструментів (наприклад, фортепіано, скрипки чи труби), уривки із записаних пісень (наприклад, п’ятисекундний риф бас-гітари з фанк-пісні) або знайдені звуки (наприклад, океанські хвилі).
- Середовище програмування (
IDE
) -
інтегроване середовище розробки, яке містить редактор початкового коду, компілятор чи інтерпретатор, засоби автоматизації збірки та засоби для спрощення розробки графічного інтерфейсу користувача.
- Синтезатори
-
електронні пристрої, які синтезують звук за допомогою одного чи кількох електричних генераторів коливань.
- Словесна бульбашка, хмаринка думок (англ.
Speech Balloon
) -
графічний засіб, виноска, що використовується в основному в коміксах і манґах (комікси, що створені в Японії) для ілюстрації слів чи думок персонажа. Найбільш поширеними формами виносок є «бульбашка», яка вказує на слова, і «хмаринка», яка вказує на думки.
- Стилус
-
інструмент у вигляді ручки для вводу команд на сенсорний екран планшетного комп’ютера чи іншого мобільного пристрою. Являє собою невелику металеву або пластикову паличку зі спеціальним силіконовим наконечником, якою торкаються сенсорної поверхні екрана - або для управління пристроєм (як замінник людського пальця) або для писання, креслення чи малювання.
- Сцена 3D (
3D
-сцена) -
віртуальний простір, який складається з тривимірних об’єктів, світла, матеріалів, камер та інших елементів, які дозволяють відтворити візуальні ефекти, такі як перспектива, освітлення, тіні та рельєф.
3D
-сцена може бути статичною (фотографія або зображення) або динамічною (відео або інтерактивний застосунок), і створена за допомогою спеціалізованого програмного забезпечення або бібліотек для рендерингу тривимірних об’єктів. - Тривимірна графіка (
3D
-графіка) -
графічний процес візуалізації об’єктів та
3D
-сцен в тривимірному просторі, що дозволяє створювати реалістичні зображення або анімації. - Файл OBJ
-
відкритий формат файлу для
3D
-моделей, який зберігає геометричні дані, а також деякі дані про матеріали та текстури. - Файл STL
-
формат файлів для
3D
-моделей. Він зберігає лише інформацію про геометрію. - Формат CSV (англ.
comma-separated values
- значення, розділені комою) -
текстовий формат для зберігання табличних даних, де кожен рядок файлу зазвичай представляє один запис даних, поля якого відокремлюються символом коми та переходу на новий рядок. Формат
CSV
використовується для обміну даними між базами даних та застосунками для роботи з електронними таблицями. - Формат JSON (англ.
JavaScript Object Notation
, вимовляється джейсон) -
незалежний від мови формат відкритих даних, який використовує зрозумілий для людини текст для опису об’єктів даних, що складаються з пар ключ: значення. Попри те, що дані JSON спочатку були похідними від мови
JavaScript
, їх можна генерувати та аналізувати за допомогою широкого спектра мов програмування, включаючиJavaScript
,PHP
,Python
,Ruby
таJava
. Формат застосовується для зберігання та передавання структурованої інформації. Розробив і популяризував формат Дуглас Крокфорд. - Фрактальне мистецтво
-
форма цифрового мистецтва, створена шляхом обчислення фрактальних об’єктів (в широкому розумінні фрактал означає фігуру, малі частини якої в довільному збільшенні є подібними до неї самої) і представляє результати обчислень як нерухомі зображення, анімацію та автоматично створювані мультимедійні дані. Фрактальне мистецтво зародилося в середині 1980-х років.
- Цифровий живопис
-
новий вид мистецтва, в якому традиційні техніки живопису, такі як акварель, олія та інші, імітуються за допомогою комп’ютера, графічного планшета, стилуса та програмного забезпечення.
- Частота (англ.
frequency
) -
фізична величина, що дорівнює кількості однакових подій за одиницю часу (рухів, коливань тощо) і є однією з основних характеристик періодичних процесів. Частота показує, скільки періодів процесу відбувається за одиницю часу. Розрізняють лінійну частоту (
ν
) - кількість періодичних процесів за секунду й циклічну (кутову) частоту (ω
) - кількість коливань за2π
секунд. Циклічна частота використовується в формулах для того, щоб не писати множник2π
, але числові значення приводяться для лінійної частоти. Тому, коли говорять, що тактова частота комп’ютера 3 ГГц, або, що людина чує звуки частотою від 16 Гц до 20000 Гц, йдеться про лінійну частотаν
. Наприклад, частота звукової хвилі сприймається людським вухом як тон, частота електромагнітної хвилі світлового діапазону сприймається людським оком як колір. Одиницею вимірювання частоти в Міжнародній системі одиниць є герц (Гц). - Черепашача графіка
-
термін, що застосовується до зображень, створених за допомогою інструкцій, які виконує виконавець (Черепашка). Черепашкова графіка з’явилася як складова оригінальної мови програмування
Logo
, розробленої Воллі Фойрцайґом, Сеймуром Пейпертом і Синтією Соломон у 1967 році в освітніх цілях для навчання дітей дошкільного й молодшого шкільного віку основним концепціям програмування. Використання у мовіLogo
середовища для програмування Черепашки мало на меті допомогти дітям вчитися програмувати, оскільки вони могли б розуміти програму, уявляючи як би вони виконували команди, бувши на місці Черепашки. Популярним прикладом такого середовища є класична бібліотека turtle у мові програмуванняPython
. - Штучний інтелект (
Artificial intelligence
,AI
) -
напрямок в інформатиці та інформаційних технологіях, завданням якого є відтворення за допомогою обчислювальних систем та інших штучних пристроїв розумних міркувань і дій.
Додаток A: Редактори коду
У таблиці наведений короткий огляд рекомендованих середовищ програмування і редакторів коду для створення й виконання застосунків за допомогою бібліотеки p5.js
.
Назва | Встановлення | Онлайн | Опис |
---|---|---|---|
|
|
безплатне інтегроване середовище розробки з офіційного сайту |
|
|
|
онлайн-редактор коду з підтримкою |
|
|
|
онлайн-середовище розробки з підтримкою |
|
|
|
онлайн-редактор коду з підтримкою |
|
|
|
безплатне інтегроване онлайн-середовище розробки (при створенні нового |
|
|
|
безплатне інтегроване середовище розробки (для належної роботи застосунків варто встановити розширення локального вебсервера Live Server ) |
|
|
|
безплатний онлайн-редактор коду, створений на основі p5.js-widget - інструменту для вбудовування на сторінки сайтів редактора для запуску і редагування ескізів |
Додаток B: DOM
У цьому розділі ви дізнаєтесь про те, як керувати вебсторінкою ескізу за допомогою засобів бібліотеки p5.js і мови JavaScript .
|
Сьогодні JavaScript
разом із HTML (мова розмітки гіпертексту, що використовується для створення HTML
-документів) і CSS (спеціальна мова для опису зовнішнього вигляду HTML
-документів) є основними технологіями Всесвітньої павутини .
Одним з аспектів використання мови JavaScript
є керування елементами HTML
-документів. Отож, розглянемо основні можливості, які надають JavaScript
і бібліотека p5.js
для зміни вмісту вебсторінки, на якій виконуються ескізи.
Для наших цілей будемо використовувати онлайн-редактор p5.js Web Editor . За бажанням, ви можете обрати інший редактор із рекомендованих для підручника або ваш улюблений редактор коду. Заразом не забувайте, що для належної роботи застосунків, які запускаються локально, необхідно використовувати локальний вебсервер . |
При переході в онлайн-редактор у вікні вебпереглядача відкривається файл ескізу sketch.js
, а якщо натиснути на кнопку >
під кнопкою запуску, перед нами розгорнеться список усіх файлів нашого проєкту: index.html
, sketch.js
і style.css
.

Файли проєкту вже містять певний код за стандартним налаштуванням. Файл ескізу sketch.js
, в якому записують початковий код ескізу за допомогою мови JavaScript
і засобів p5.js
, має наступний вміст:
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
}
Файл index.html
містить код для приєднання файлів бібліотеки p5.js
та ескізу sketch.js
чи у разі потреби інших бібліотек:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" />
</head>
<body>
<main>
</main>
<script src="sketch.js"></script>
</body>
</html>
Приєднання JavaScript -файлів у HTML -документ index.html виконується за допомогою атрибуту src тегу <script> . Атрибут src містить значення URL -адреси файлів, що приєднуються. У редакторі p5.js Web Editor для бібліотек цей шлях є абсолютним, а для файлу ескізу - відносним щодо файлу index.html .
|
Якщо ви використовуєте локальну розробку і власний редактор коду, слідкуйте за правильністю прописаних шляхів до файлів проєкту. |
У файлі style.css
записаний початковий набір стилів:
html, body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
Додамо в тіло документа index.html
певний вміст
<!DOCTYPE html>
<html lang="uk">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css" />
<meta charset="utf-8" />
<title>JavaScript, p5.js і DOM</title>
</head>
<body>
<main id="content">
<h1>JavaScript, p5.js і DOM</h1>
<div>
<p>p5.js - бібліотека JavaScript для креативного кодування.</p>
<p>Безплатна, з відкритим початковим кодом.</p>
<p>Використовуючи метафору ескізу, p5.js має повний набір функцій малювання.</p>
</div>
<div class="sample">
<p>Малюнок кола - зразок простого застосунку.</p>
</div>
<div>
<p>Створено за допомогою <a href="https://editor.p5js.org/">p5.js Web Editor</a></p>
</div>
</main>
<script src="sketch.js"></script>
</body>
</html>
і змінимо код у файлі ескізу sketch.js
для створення простого застосунку
function setup() {
createCanvas(200, 200);
}
function draw() {
background(220);
fill("#2a9d8f");
noStroke();
circle(100, 100, 75);
}
Після запуску застосунку вебпереглядач, використовуючи HTML
-розмітку у файлі index.html
і розмітку, яка була створена динамічно за допомогою коду із файлу ескізу sketch.js
, сформує DOM
для вебсторінки index.html
.
B.1. Що таке DOM?
Об’єктна модель документа (англ. Document Object Model , DOM ) - це міжплатформний інтерфейс програмування застосунків (англ. Application Programming Interface , API ) для маніпулювання HTML -документами. DOM дозволяє застосункам отримувати доступ до вмісту HTML -документів, змінювати вміст, структуру та оформлення таких документів. HTML і JavaScript взаємодіють між собою за допомогою DOM .
|
Коли вебпереглядач завантажує певну вебсторінку, він будує DOM
- дерево об’єктів, які представляють HTML
-розмітку.
Переглянути DOM -дерево можна у вкладці Елементи на панелі розробника у вебпереглядачі за допомогою сполучення клавіш Ctrl+Shift+C.
|
Створені об’єкти зберігаються як вузли об’єктної моделі документа і є JavaScript
-об’єктами.
Наприклад, поглянемо як виглядає вузол тега <head>
у вигляді об’єкта. Для цього у консолі вебпереглядача (вкладка Консоль на панелі розробника у вебпереглядачі) введемо команду console.dir(document.head);
.

Вищенаведена команда друкує JSON-подання переданого об’єкта, що зручно для аналізу його властивостей.
У прикладах коду для друку повідомлень в консолі вебпереглядача ми будемо використовувати ще одну консольну команду: console.log("log"); .
|
З детальним описом команд консолі вебпереглядача (Chrome ) можна ознайомитися в довіднику API вебпереглядача .
|
Кожен вузол у DOM
належить до певного вбудованого класу JavaScript
. Є базовий клас для всіх вузлів - клас Node
, а від нього успадковуються інші типи вузлів:
-
теги створюють вузли-елементи (клас
Element
); -
текст усередині тегів створює текстові вузли (клас
Text
); -
атрибути тегів створюють вузли-атрибути (клас
Attr
); -
коментарі у розмітці створюють вузли-коментарі (клас
Comment
).
DOM подібний на HTML -код, але не є ним, а лише формується з нього. У прикладі вище, за допомогою JavaScript і бібліотеки p5.js ми додали нові вузли й DOM став іншим, ніж HTML для документа index.html . Інакше, HTML представляє початковий вміст вебсторінки, а DOM представляє поточний вміст сторінки.
|
DOM вебсторінки index.html можна також переглянути за допомогою інструмента Live DOM Viewer . Оскільки JavaScript взаємодіє, як правило, з тегами, а для зміни вмісту тегів використовує методи та властивості вузлів-елементів, для кращого візуального огляду схеми DOM , що складається лише з вузлів-елементів, зручно використовувати інструмент DOM Visualizer .
|
Отож, DOM
представляє HTML
-документ у вигляді перевернутого дерева вузлів, в якому корінь дерева - вгорі, а листки-вузли - розгалужуються донизу. Гілки цієї деревоподібної ієрархії визначають зв’язки між вузлами-елементами.
Якщо використовувати термінологію відносин у сім’ї, вузли-елементи можуть бути батьками, дітьми, рідними братами й сестрами та нащадками.
Діти (дочірні елементи) - елементи, які містяться безпосередньо всередині батьківського елемента. Наприклад, всередині батьківського елемента <html> знаходяться його дочірні елементи <head> і <body> . Рідні брати й сестри - елементи, у яких один і той самий батько, наприклад, <head> і <body> - є рідними братами й сестрами, оскільки мають одного батька - <html> . Нащадки - усі елементи разом з їхніми дітьми, дітьми їхніх дітей і так далі, які лежать всередині даного батьківського елемента.
|
На верхньому рівні DOM
-дерева завжди знаходиться об’єкт document
- кореневий батьківський вузол, який представляє HTML
-документ, що відображається у вікні переглядача.
Об’єкт document
використовується в JavaScript
для доступу до всього дерева DOM
і має один дочірній вузол, який є елементом <html>
.
Враховуючи кореневий вузол document
, схема DOM
набуває вигляду:

У нашій схемі батько-тег <main>
має чотирьох дітей - один тег <h1>
і три теги <div>
(плюс ще кілька порожнів текстових вузлів). <h1>
і три теги <div>
- це рідні брати й сестри, оскільки вони мають спільного батька.
Перший <div>
(рахуємо зліва направо) є батьком для трьох дочірніх елементів <p>
, другий і третій <div>
- мають по одній дитині <p>
. Третій <div>
має нащадка <a>
.
Нащадками <main>
є його діти - один <h1>
і три <div>
, усі елементи <p>
і елемент <a>
.
Отже, коли JavaScript
взаємодіє з вебсторінкою і намагається отримати вміст, наприклад, елемента <h1>
, використовується DOM
для входу у дерево вузлів, щоб отримати покликання на вузол-елемент <h1>
.
Отримавши таке покликання на <h1>
, можна використовувати різні методи та властивості для цього покликання, щоб змінити вміст елемента-вузла, його стиль тощо.
Загалом, отримавши доступ до елементів та їх вміст через DOM
, JavaScript
може виконувати будь-які маніпуляції над ними, зокрема:
-
додавати, змінювати, видаляти вміст на вебсторінці;
-
змінювати оформлення вебсторінки;
-
реагувати на події у вікні вебпереглядача (натискання кнопок, рух вказівника миші тощо).
Будь-яка робота з DOM повинна починатися лише після повного завантаження вебсторінки, оскільки це гарантує, що DOM -дерево утворено і до нього додані всі елементи вебсторінки. Найпростіший спосіб це забезпечити - розмістити тег <script> з вашим кодом перед тегом </body> .
|
B.2. Пошук елементів
Щоб виконувати дії (змінювати вміст, властивості, обробляти події тощо) над елементами HTML
-документів, спочатку їх шукають в DOM
-дереві.
Алгоритм пошуку будь-якого елемента або фрагмента HTML
-документа за допомогою JavaScript
можна описати так:
-
За допомогою зарезервованого слова
const
(абоlet
) визначити ім’я, яке буде містити покликання на шуканий елемент або частинуHTML
-документа. -
Звернутися до кореневого вузла
HTML
-документа -document
. -
За допомогою крапкової нотації застосувати на об’єкті
document
методquerySelector()
у разі пошуку якогось одного елемента чи методquerySelectorAll()
, якщо потрібний список усіх елементів. -
У методах
querySelector()
іquerySelectorAll()
як аргумент використати рядок - значення селектора, який описує те, що шукаємо.
Цікавимось
Скористаємось зразком проєкту, щоб проілюструвати різні способи пошуку елементів.
Наприклад, перевіримо HTML
-документ на наявність в ньому елемента <p>
. Перетворимо вищезгаданий алгоритм у код і запишемо його у файл ескізу у функцію setup()
:
function setup() {
createCanvas(200, 200);
const result = document.querySelector("p"); (1)
console.log(result); (2)
}
function draw() {
background(220);
fill("#2a9d8f");
noStroke();
circle(100, 100, 75);
}
1 | Виконуємо запит на пошук елемента <p> в DOM -дереві документа index.html і результат запиту зберігаємо з назвою result . Тут використовується метод querySelector() , оскільки шукаємо один елемент <p> . |
2 | Друкуємо значення результату в консолі вебпереглядача за допомогою команди console.log() . |
З надрукованого в консолі значення result
дізнаємось, що елемент <p>
успішно знайдений:
<p>p5.js - бібліотека JavaScript для креативного кодування.</p>
Пошук елементів відбувається згори донизу сторінки. |
Якщо поглянути на код файлу index.html
, в якому відбувався пошук, то можна нарахувати п’ять елементів <p>
...
<div>
<p>p5.js - бібліотека JavaScript для креативного кодування.</p>
<p>Безплатна, з відкритим початковим кодом.</p>
<p>Використовуючи метафору ескізу, p5.js має повний набір функцій малювання.</p>
</div>
<div class="sample">
<p>Малюнок кола - зразок простого застосунку.</p>
</div>
<div>
<p>Створено за допомогою <a href="https://editor.p5js.org/">p5.js Web Editor</a></p>
</div>
...
а в результаті пошуку ми отримали лише перший з абзаців.
querySelector() - метод, який повертає перший елемент від початку документа, який відповідає зазначеному селектору або групі CSS -селекторів. Якщо збігу не знайдено, повертає значення null .
|
Розглянемо ще кілька прикладів використання методу querySelector()
.
Селектор за ідентифікатором #content
const result = document.querySelector("#content");
console.log(result);
вибирає елемент <main>
із його вмістом
<main id="content">
<h1>JavaScript, p5.js і DOM</h1>
<div>
<p>p5.js - бібліотека JavaScript для креативного кодування.</p>
<p>Безплатна, з відкритим початковим кодом.</p>
<p>Використовуючи метафору ескізу, p5.js має повний набір функцій малювання.</p>
</div>
<div class="sample">
<p>Малюнок кола - зразок простого застосунку.</p>
</div>
<div>
<p>Створено за допомогою <a href="https://editor.p5js.org/">p5.js Web Editor</a></p>
</div>
<canvas id="defaultCanvas0" class="p5Canvas" width="200" height="200" style="width: 200px; height: 200px;"></canvas>
</main>
Селектор за класом .sample
const result = document.querySelector(".sample");
console.log(result);
вибирає елемент <div>
, для якого вказаний CSS
-клас .sample
<div class="sample">
<p>Малюнок кола - зразок простого застосунку.</p>
</div>
Комбінований селектор div.sample p
const result = document.querySelector("div.sample p");
console.log(result);
вибирає елемент <p>
, який міститься в елементі <div>
, який зі свого боку має CSS
-клас .sample
<p>Малюнок кола - зразок простого застосунку.</p>
Комбінований селектор за атрибутом div[class='sample'] > p
const result = document.querySelector("div[class='sample'] > p");
console.log(result);
повертає елемент <p>
, який є дочірнім елементом для елемента <div>
, який зі свого боку має атрибут class
зі значенням sample
<p>Малюнок кола - зразок простого застосунку.</p>
Тепер застосуємо вищезгаданий алгоритм для пошуку усіх елементів <p>
. У цьому разі використаємо метод querySelectorAll()
:
const resultNodeList = document.querySelectorAll("p");
console.log(resultNodeList);
Список знайдених елементів буде міститися в колекції вузлів з назвою NodeList
:
NodeList {0: HTMLParagraphElement, 1: HTMLParagraphElement, 2: HTMLParagraphElement, 3: HTMLParagraphElement, 4: HTMLParagraphElement…}
0: <p>p5.js - бібліотека JavaScript для креативного кодування.</p>
1: <p>Безплатна, з відкритим початковим кодом.</p>
2: <p>Використовуючи метафору ескізу, p5.js має повний набір функцій малювання.</p>
3: <p>Малюнок кола - зразок простого застосунку.</p>
4: <p>…</p>
entries: ƒ entries() {}
keys: ƒ keys() {}
values: ƒ values() {}
forEach: ƒ forEach() {}
length: 5
item: ƒ item() {}
<constructor>: "NodeList"
querySelectorALL() - метод, який повертає список усіх елементів документа, що відповідають зазначеному селектору або групі CSS -селекторів. Список знайдених елементів - це колекція NodeList . Якщо збігів не знайдено, властивість length об’єкта NodeList дорівнюватиме нулю.
|
Об’єкт NodeList
має властивість length
і може індексуватися подібно масиву (колекції схожі на масиви, для проходу по колекціях можна використовувати цикл for
) і є ітерабельним - повертає наступний елемент з колекції (для проходу по колекціях можна використовувати цикл for..of
).
Якщо для проходу по елементах отриманої колекції NodeList
використати цикл for
const paragraphs = document.querySelectorAll("p");
for (let i = 0; i < paragraphs.length; i++) {
console.log(paragraphs[i]);
}
або цикл for..of
const paragraphs = document.querySelectorAll("p");
for (const paragraph of paragraphs) {
console.log(paragraph);
}
або метод forEach
const paragraphs = document.querySelectorAll("p");
paragraphs.forEach((paragraph) => {
console.log(paragraph);
});
в усіх випадках отримаємо однаковий результат - список усіх елементів <p>
:
<p>p5.js - бібліотека JavaScript для креативного кодування.</p>
<p>Безплатна, з відкритим початковим кодом.</p>
<p>Використовуючи метафору ескізу, p5.js має повний набір функцій малювання.</p>
<p>Малюнок кола - зразок простого застосунку.</p>
<p>
Створено за допомогою
<a href="https://editor.p5js.org/">p5.js Web Editor</a>
</p>
До певного елемента колекції можна звернутися за індексом (нумерація індексів починається з нуля)
const paragraphs = document.querySelectorAll("p");
console.log(paragraphs[2]);
і отримати цей елемент
<p>Використовуючи метафору ескізу, p5.js має повний набір функцій малювання.</p>
Для роботи з колекціями визначені спеціальні методи: entries()
, keys()
, values()
. Наприклад, використання методу keys()
і циклу for..of
const paragraphs = document.querySelectorAll("p");
for(const key of paragraphs.keys()) {
console.log(key);
}
дає змогу отримати список індексів елементів колекції
0
1
2
3
4
А метод entries()
і цикл for..of
const paragraphs = document.querySelectorAll("p");
for(const p of paragraphs.entries()) {
console.log(p[0], p[1]);
}
дозволяє отримати як індекси, так і значення елементів колекції:
0 <p>p5.js - бібліотека JavaScript для креативного кодування.</p>
1 <p>Безплатна, з відкритим початковим кодом.</p>
2 <p>Використовуючи метафору ескізу, p5.js має повний набір функцій малювання.</p>
3 <p>Малюнок кола - зразок простого застосунку.</p>
4 <p>Створено за допомогою <a href="https://editor.p5js.org/">p5.js Web Editor</a></p>
Цікавимось
Бібліотека p5.js
для пошуку елементів має власні засоби - функції select() і selectAll() .
Функція select()
шукає на сторінці перший елемент, який відповідає заданому рядку CSS
-селектора, який може бути ідентифікатором, CSS
-класом, тегом або комбінованим варіантом і повертає об’єкт p5.Element
.
p5.Element є базовим класом для усіх об’єктів, доданих до ескізу, включаючи HTML -елементи, і містить методи, які можна застосувати до цих об’єктів.
|
Виконаємо простий код для вибору на вебсторінці першого елемента <p>
за допомогою функції select()
:
function setup() {
createCanvas(200, 200);
const paragraph = select("p");
console.log(paragraph);
}
function draw() {
background(220);
fill("#2a9d8f");
noStroke();
circle(100, 100, 75);
}
У консолі отримаємо об’єкт абзацу з його властивостями та методами:
{elt: HTMLParagraphElement, _pixelsState: p5, _pInst: p5, _events: Object, width: 946…}
Відповідно для пошуку усіх елементів <p>
документа використаємо функцію selectAll()
:
const paragraphs = selectAll("p");
console.log(paragraphs);
Функція selectAll()
шукає на сторінці усі елементи, що відповідають заданому рядку CSS
-селектора, який може бути ідентифікатором, CSS
-класом, тегом або комбінованим варіантом, і повертає масив об’єктів p5.Element
, інакше - порожній масив, якщо жодного елемента не знайдено.
У підсумку, в консолі отримуємо масив із п’яти об’єктів:
(5) [Object, Object, Object, Object, Object]
B.3. Властивості елементів
Коли пошук елемента на вебсторінці виявився успішним, далі над цим елементом можна виконати наступні дії:
-
отримати вміст (текст або
HTML
-розмітку) елемента і змінити його; -
отримати та змінити атрибути елемента.
Розглянемо властивості та методи вузлів-елементів за допомогою яких можна отримати характеристики елемента та змінити їх у разі потреби. Щоб проілюструвати наші дії, скористаємось зразком проєкту.
Отже, отримаємо текстовий вміст для першого елемента <p>
на сторінці index.html
.
Для цього використаємо властивість textContent , яка дозволяє встановлювати або отримувати текстовий вміст елемента та його нащадків.
Розглянемо такий код:
function setup() {
createCanvas(200, 200);
const firstParagraph = document.querySelector("p"); (1)
let textFirstParagraph = firstParagraph.textContent; (2)
console.log(textFirstParagraph); (3)
}
function draw() {
background(220);
fill("#2a9d8f");
noStroke();
circle(100, 100, 75);
}
1 | Шукаємо елемент <p> за допомогою методу querySelector() і зберігаємо результат з ім’ям firstParagraph . |
2 | Отримуємо за допомогою крапкової нотації значення властивості textContent знайденого елемента і зберігаємо результат з ім’ям textFirstParagraph . |
3 | Друкуємо значення textFirstParagraph в консолі. |
В консолі отримуємо текст, що міститься в першому елементі <p>
:
p5.js - бібліотека JavaScript для креативного кодування.
За допомогою властивості textContent
також можна замінити текст в елементі на інший.
Внесемо деякі зміни в попередній код
const firstParagraph = document.querySelector("p"); (1)
let textFirstParagraph = firstParagraph.textContent; (2)
console.log(textFirstParagraph); (3)
firstParagraph.textContent = "Перший абзац зазнав змін."; (4)
let textFirstParagraphChange = firstParagraph.textContent; (5)
console.log(textFirstParagraphChange); (6)
і отримаємо в консолі наступні результати:
p5.js - бібліотека JavaScript для креативного кодування.
Перший абзац зазнав змін.
Проаналізуємо результати:
1 | Шукаємо на вебсторінці перший елемент <p> і зберігаємо результат пошуку з ім’ям firstParagraph . |
2 | Отримуємо текст першого елемента <p> із firstParagraph і зберігаємо з ім’ям textFirstParagraph . |
3 | Друкуємо в консолі значення тексту з textFirstParagraph . |
4 | Замінюємо за допомогою операції присвоєння поточне значення властивості textContent для першого елемента <p> на рядок "Перший абзац зазнав змін." . |
5 | Отримуємо новий текст першого елемента <p> із firstParagraph і зберігаємо з ім’ям textFirstParagraphChange . |
6 | Друкуємо в консолі значення тексту з textFirstParagraphChange і переглядаємо оновлений текст в першому абзаці у вебпереглядачі на вебсторінці застосунку. |
Окрім повної заміни текстового вмісту, текст елемента можна доповнювати. Виконаємо доповнення першого абзацу іншим текстом за допомогою операції додавання з присвоєнням +=
const firstParagraph = document.querySelector("p");
firstParagraph.textContent += " Вона робить кодування доступним для будь-кого!";
let textFirstParagraph = firstParagraph.textContent;
console.log(textFirstParagraph);
і отримаємо в консолі результат конкатенації (об’єднання) тексту:
p5.js - бібліотека JavaScript для креативного кодування. Вона робить кодування доступним для будь-кого!
Оновлення тексту у першому абзаці також відбудеться й у вебпереглядачі на вебсторінці index.html
застосунку.
Якщо необхідно замінити чи доповнити текст для декількох елементів, то використовують цикли for
чи for..of
або метод forEach
. Скористаємось варіантом з forEach
і вказівкою розгалуження
const paragraphs = document.querySelectorAll("p");
paragraphs.forEach((paragraph) => {
if (paragraph.textContent.trim() != "Створено за допомогою p5.js Web Editor") {
paragraph.textContent += "👍";
} else {
paragraph.textContent += "❤️";
}
console.log(paragraph.textContent);
});
щоб отримати в консолі результат конкатенації тексту кожного абзацу вебсторінки й смайлика Emoji
:
p5.js - бібліотека JavaScript для креативного кодування.👍
Безплатна, з відкритим початковим кодом.👍
Використовуючи метафору ескізу, p5.js має повний набір функцій малювання.👍
Малюнок кола - зразок простого застосунку.👍
Створено за допомогою p5.js Web Editor ❤️
У коді вище ми використали JavaScript -метод trim() , який видаляє символи пропусків (пропуск, табуляція, символи завершення рядка та інші) на початку та наприкінці текстового рядка.
|
У разі використання операції присвоєння = , вміст елемента повністю перезаписується. У разі додавання з присвоєнням += - до поточного вмісту елемента додається новий.
|
Інша властивість - innerHTML - використовується для встановлення або отримання HTML
-розмітки всередині елемента.
Додамо трішки HTML
-розмітки у наш застосунок за допомогою наступного коду JavaScript
:
const div = document.querySelector(".sample"); (1)
div.innerHTML += "<p>Створи власний застосунок!</p>"; (2)
console.log(div); (3)
Отож, згідно із наведеним кодом виконуються такі кроки:
1 | Знаходимо елемент <div> , який має клас .sample , і зберігаємо результат з ім’ям div . |
2 | За допомогою крапкової нотації звертаємось до властивості innerHTML , яку має div , і додаємо за допомогою += розмітку з абзацом тексту. |
3 | Друкуємо результат - оновлену розмітку елемента <div> , який має клас .sample . |
Завдяки операції +=
у вікні вебпереглядача і в консолі, в елемент <div>
був доданий новий абзац тексту, який розмістився після вмісту, що вже був присутнім в елементі <div>
:
<div class="sample">
<p>Малюнок кола - зразок простого застосунку.</p>
<p>Створи власний застосунок!</p>
</div>
Інакше, у разі використання операції присвоєння =
, увесь вміст елемента <div>
, а саме елемент <p>Малюнок кола - зразок простого застосунку.</p>
, був би видалений. В елементі <div>
містився б лише доданий новий елемент <p>Створи власний застосунок!</p>
і результат в консолі (також у вебпереглядачі) мав би наступний вигляд:
<div class="sample">
<p>Створи власний застосунок!</p>
</div>
Розглянемо ще один приклад використання властивості innerHTML
в контексті додавання HTML
-розмітки за допомогою шаблонних літералів - рядків-шаблонів, які для запису використовують зворотні лапки ` `
і дозволяють використовувати вирази.
Наведений нижче код динамічно додає на вебсторінку ескізу три нових елементи абзацу, текстові значення яких зберігаються у масиві langs
:
let langs = ["HTML", "CSS", "JavaScript"];
let div = document.querySelector(".sample");
div.innerHTML += "<p>Технології, які були використані:</p>";
for (const lang of langs) {
div.innerHTML += `<p>${lang}</p>`;
}
console.log(div);
Це реалізовується за допомогою циклу for..of
і шаблонного рядка, у який на кожній ітерації масиву підставляється значення lang
із масиву у форматі ${lang}
, а властивість innerHTML
додає сформований елемент абзацу на вебсторінку:
<div class="sample">
<p>Малюнок кола - зразок простого застосунку.</p>
<p>Технології, які були використані:</p>
<p>HTML</p>
<p>CSS</p>
<p>JavaScript</p>
</div>
Окрім зміни текстового і HTML
-вмісту, ми можемо змінювати й атрибути елементів.
Атрибути - це спеціальні слова, які використовуються всередині вступного тегу для контролю поведінки HTML -елемента. Атрибут має вигляд name="value" (назва атрибута name і пов’язане з ним текстове значення value ), наприклад, для <main id="content"> - id - атрибут, а content - значення атрибута.
|
Коли вебпереглядач завантажує вебсторінку, він аналізує HTML
і генерує з нього DOM
-вузли. Водночас більшість стандартних HTML
-атрибутів автоматично стають властивостями DOM
-елементів.
Щоб уникнути плутанини: атрибути - те, що написано у HTML , а властивості - те, що знаходиться в DOM -елементах.
|
Є кілька способів отримати та змінити властивості у DOM
-елементів. Для стандартних HTML
-атрибутів крапкова нотація - найпростіший спосіб зробити це.
Наприклад, отримаємо зі сторінки застосунку значення атрибутів: href
елемента <a>
і class
елемента <div>
із CSS
-класом .sample
:
const a = document.querySelector("a");
console.log(a.href);
const div = document.querySelector(".sample");
console.log(div.className);
У цьому разі атрибут class
елемента <div>
не перетворюється автоматично у DOM
-властивість із такою ж назвою. Тому тут використовуємо властивість з назвою className
.
Використовуючи властивість className , можна швидко замінити всі CSS -класи елемента. Наприклад, так: div.className = "sample other"; , де .sample , .other - це CSS -класи.
|
Значення властивостей - це покликання на сторінку онлайн-редактора і назва класу відповідно:
https://editor.p5js.org/
sample
При аналізі HTML вебпереглядач не завжди перетворює атрибути тегів на текстові DOM -властивості, хоча більшість властивостей - це текстові рядки.
|
Інший спосіб доступу до атрибутів - використання методів:
-
hasAttribute() - перевіряє наявність атрибута;
-
setAttribute() - отримує значення атрибута;
-
getAttribute() - встановлює значення атрибута;
-
removeAttribute() - видаляє атрибут.
Розглянемо приклади використання цих методів. Для початку скористаємось методом hasAttribute()
для перевірки існування деяких атрибутів, наприклад, в елементі <a>
:
const a = document.querySelector("a");
console.log(a.hasAttribute("type"));
console.log(a.hasAttribute("href"));
console.log(a.hasAttribute("target"));
Поглянувши на результат виконання застосунку
false
true
false
робимо висновок, що елемент <a>
має зараз лише один атрибут href
із поданого переліку. В цьому можна переконатися, зазирнувши в HTML
-код сторінки index.html
застосунку.
Отримаємо значення атрибута href
для елемента <a>
:
const a = document.querySelector("a");
console.log(a.getAttribute("href"));
В консолі знову побачимо URL
-адресу редактора коду, як у разі використання крапкової нотації вище:
https://editor.p5js.org/
Якщо у вебпереглядачі натиснути на покликання на редактор коду, він відкриється безпосередньо в області перегляду застосунку.
Додамо атрибут target
зі значенням _blank
до елемента <a>
, щоб змінити таку поведінку:
const a = document.querySelector("a");
a.setAttribute("target", "_blank");
Якщо поглянути на HTML
-код сторінки запущеного застосунку (Ctrl+Shift+C), можна помітити, що для нашого покликання динамічно був доданий атрибут target
зі значенням _blank
.
Чудово! Тепер вікно редактора відкривається у новій вкладці вебпереглядача.
Для видалення атрибутів використовують метод removeAttribute()
. Застосуємо його для видалення атрибута id
тега <main>
:
const main = document.querySelector("main");
main.removeAttribute("id");
Тепер поглянемо на можливості p5.js
для зміни вмісту, властивостей і атрибутів елементів.
Отже, отримаємо усі елементи <p>
на вебсторінці застосунку за допомогою функції selectAll()
і пройдемо по отриманому масиві об’єктів за допомогою циклу for
:
const paragraphs = selectAll("p");
for (let i = 0; i < paragraphs.length; i++) {
console.log(paragraphs[i].elt);
}
В коді ми використали властивість elt , за допомогою якої можна отримати доступ до вузла DOM
кожного об’єкта масиву
<p>p5.js - бібліотека JavaScript для креативного кодування.</p>
<p>Безплатна, з відкритим початковим кодом.</p>
<p>Використовуючи метафору ескізу, p5.js має повний набір функцій малювання.</p>
<p>Малюнок кола - зразок простого застосунку.</p>
<p>
Створено за допомогою
<a href="https://editor.p5js.org/">p5.js Web Editor</a>
</p>
Особливості використання властивості elt
можна проілюструвати на наступному прикладі:
let one = document.querySelector(".sample");
let two = select(".sample");
console.log(one);
console.log(one.innerHTML);
console.log(one.textContent);
console.log("(U・ᴥ・U)"); // розділювач у вигляді песика Фідо
console.log(two);
console.log(two.elt);
console.log(two.elt.innerHTML);
console.log(two.elt.textContent);
У консолі отримаємо такі результати (не враховуючи пропуски в HTML
-розмітці):
<div class="sample">…</div>
<p>Малюнок кола - зразок простого застосунку.</p>
Малюнок кола - зразок простого застосунку.
(U・ᴥ・U)
{elt: HTMLDivElement, _pixelsState: p5, _pInst: p5, _events: Object, width: 1206…}
<div class="sample">…</div>
<p>Малюнок кола - зразок простого застосунку.</p>
Малюнок кола - зразок простого застосунку.
Властивість elt надає доступ до HTML -елемента і дозволяє змінити його властивості, наприклад, HTML -вміст, текстовий вміст, стиль тощо.
|
Для доступу до елементів і зміни їхнього вмісту також можна використати метод html() .
Подивимось, як це працює на наступному прикладі:
const h1 = select("h1"); (1)
h1.html(" <i>та інші технології</i>", true); (2)
let heading = h1.html(); (3)
console.log(heading); (4)
1 | Шукаємо елемент <h1> і зберігаємо результат з ім’ям h1 . |
2 | Додаємо за допомогою метода html() новий HTML -вміст для h1 . |
3 | Отримаємо за допомогою метода html() вміст h1 і зберігаємо з ім’ям heading . |
4 | Друкуємо в консолі значення heading . |
Після запуску застосунку у вебпереглядачі заголовок зміниться на JavaScript, p5.js і DOM та інші технології
, а у консолі буде надрукований вміст заголовка, який ми отримали за допомогою метода html()
:
JavaScript, p5.js і DOM <i>та інші технології</i>
Метод html()
додав до наявного тексту у заголовка першого рівня HTML
-вміст, який передавався у першому аргументі, а доповнення відбулось завдяки другому (необов’язковому) аргументу - логічному значенню true
.
Якщо у методі html()
не вказувати другий аргумент, то вміст заголовку буде перезаписаний, про що ви можете переконатися самостійно.
Бібліотека p5.js
надає засоби також для зміни атрибутів елементів.
За допомогою метода attribute() додамо атрибут style
до заголовку <h1>
:
const h1 = select("h1");
h1.attribute("style", "text-decoration: underline dotted red;");
Отже, заголовок отримає вбудований стиль за допомогою атрибута style
- підкреслення штриховою лінією червоного кольору.
За допомогою метода attribute()
можна отримати значення атрибутів елемента. Наприклад, значення ширини полотна застосунку можна отримати так:
const cnv = select("canvas");
let widthCanvas = cnv.attribute("width");
console.log(widthCanvas);
У підсумку, в консолі буде надруковано значення ширина елемента <canvas>
у пікселях: 200
.
Для встановлення атрибута id
бібліотека p5.js
має окремий метод id() , який встановлює ідентифікатор для елемента.
Якщо аргумент в id()
не передається, цей метод повертає поточний ідентифікатор елемента, наприклад:
let cnv = createCanvas(200, 200);
console.log(cnv.id());
У консолі отримаємо значення id
для полотна: defaultCanvas0
.
На вебсторінці лише один елемент повинен мати певний ідентифікатор. |
Для видалення атрибутів елементів бібліотека p5.js використовує метод removeAttribute() .
|
Цікавим методом p5.js
є метод position() , який встановлює положення елементів. Застосуємо цей метод для елемента <canvas>
.
Створивши полотно за допомогою методу createCanvas()
, збережемо покликання на нього з ім’ям cnv
та змінимо положення полотна на вебсторінці застосунку:
function setup() {
let cnv = createCanvas(200, 200);
cnv.position(100, 100);
}
function draw() {
background(220);
fill("#2a9d8f");
noStroke();
circle(100, 100, 75);
}
Застосувавши метод position(100, 100)
для cnv
, позиція лівого верхнього кута полотна зміниться на 100 пікселів праворуч від лівої межі та на 100 пікселів униз від верхньої межі області перегляду, в чому можна переконатися у вікні вебпереглядача після запуску застосунку.
Якщо аргументи для методу position()
не вказані, метод повертає об’єкт, що містить координати x
та y
позиції елемента.
Продемонструємо це для елемента <canvas>
:
function setup() {
let cnv = createCanvas(200, 200);
cnv.position(10, 10); (1)
let coords = cnv.position(); (2)
console.log(coords); (3)
console.log("x:", coords.x); (4)
console.log("y:", coords.y);
}
function draw() {
background(220);
fill("#2a9d8f");
noStroke();
circle(100, 100, 75);
}
1 | Розміщуємо полотно в точці (10, 10) . Координати позиції елемента вказуються відносно точки (0, 0) вікна перегляду. |
2 | Отримуємо координати полотна (метод position() використовуємо без аргументів) і зберігаємо результат з ім’ям coords . |
3 | Друкуємо в консолі об’єкт coords . |
4 | Отримуємо доступ до координат x та y об’єкта coords , використовуючи крапкову нотацію. |
В консолі результат матиме вигляд:
{x: 10, y: 10}
x: 10
y: 10
B.4. Створення і видалення елементів
У разі статичної вебсторінки вебпереглядач повністю формує DOM
-дерево. Але частіше вебпереглядач має справу з відображенням даних, які прийшли з вебсервера як відповідь на запит користувача.
У такому разі виникає необхідність динамічно створювати чи видаляти елементи, змінювати їхні характеристики та встановлювати зв’язки між ними, інакше змінювати DOM
.
Скористаємось зразком проєкту, щоб продемонструвати способи створення і видалення елементів в DOM
.
Для видалення елемента з DOM
-дерева використовується JavaScript
-метод remove() .
Наприклад:
const h1 = document.querySelector("h1");
h1.remove();
Якщо виконати застосунок, у вікні перегляду вебпереглядача елемент <h1>
буде відсутнім, тобто видалений з DOM
. Якщо відкрити файл index.html
- заголовок буде на своєму місці. Цей приклад ще раз демонструє відмінність між HTML
і DOM
вебсторінки.
Для створення елемента використовують createElement() - метод, який створює новий елемент із заданим тегом.
Наприклад, створимо елемент абзацу <p>
з текстовим вмістом "Чудовий редактор!"
. Для цього метод createElement()
необхідно застосувати для об’єкта document
:
const p = document.createElement("p");
p.textContent = "Чудовий редактор!";
console.log(p);
У вебпереглядачі абзац з текстом відображатися не буде, але з консолі дізнаємось, що він існує:
<p>Чудовий редактор!</p>
Метод createElement() створює новий елемент, який існує сам по собі й не прив’язаний до жодного DOM -вузла, тому елемент не відображатиметься на вебсторінці. Однак, для нього можна встановлювати властивості, наповнювати його вмістом тощо.
|
Щоб створений DOM
-вузол зайняв своє місце у DOM
-дереві та відобразився на вебсторінці, використовують методи, які вставляють вузли-елементи чи текстові вузли в DOM
-дерево:
Створимо кілька вузлів і додамо їх в DOM
-дерево:
const div = document.createElement("div"); (1)
div.innerHTML = "<p>Гарна робота!</p>";
const p = document.createElement('p'); (2)
p.textContent = "Чудовий редактор!";
div.prepend(p);
const main = document.querySelector('main'); (3)
main.append(div, "😉");
console.log(main);
Проаналізуємо наш код:
1 | Створюємо вузол-елемент <div> з ім’ям div і використовуємо властивість innerHTML для додавання HTML -розмітки. |
2 | Створюємо вузол-елемент <p> з ім’ям p і використовуємо властивість textContent для додавання текстового вмісту. Вставляємо <p> перед усіма дочірніми елементами <div> . |
3 | Вибираємо елемент <main> і приєднуємо вузол-елемент <div> та смайлик як текстовий вузол наприкінці усіх дочірніх елементів <main> . При додаванні кількох елементів, записуємо їх через кому. |
У консолі (також у вебпереглядачі) отримаємо такий результат:
<main id="content">
<h1>JavaScript, p5.js і DOM</h1>
<div>…</div>
<div class="sample">…</div>
<div>…</div>
<canvas id="defaultCanvas0" class="p5Canvas" width="200" height="200" style="width: 200px; height: 200px;"></canvas>
<div>
<p>Чудовий редактор!</p>
<p>Гарна робота!</p>
</div>
😉
</main>
Бібліотека p5.js
містить власні інструменти для створення конкретних HTML
-елементів.
Наприклад, використовуючи функція createP() можна створити елемент абзацу. Як аргумент, метод отримає рядок, що може містити як текст, так і HTML
:
function setup() {
createCanvas(200, 200);
createP("Вебсторінку, на якій виконується <i>застосунок</i>, можна розглядати як <b>ескіз</b>.");
}
function draw() {
background(220);
fill("#2a9d8f");
noStroke();
circle(100, 100, 75);
}
Після запуску застосунку новий елемент абзацу із вказаним вмістом з’явиться під полотном застосунку - перед тегом </body>
вебсторінки index.html
.
Якщо необхідно створити на вебсторінці елемент зображення, застосовують функцію createImg() , яка створює елемент <img>
у DOM
з атрибутами src
та alt
.
Наприклад:
let img = createImg("https://res.cloudinary.com/gtstack/image/upload/v1655927356/p5js/p5jslogo_wls3fj.png", "p5.js"); (1)
img.size(60, 60); (2)
console.log(img.size()); (3)
1 | Створюємо елемент зображення <img> з URL -адресою зображення для атрибута src й текстом "p5.js" для атрибута alt . |
2 | Використовуємо метод size() для встановлення розмірів елемента - ширини й висоти зображення на вебсторінці. |
3 | Якщо аргументи для size() не вказані, метод повертає ширину та висоту елемента. У цьому разі метод без аргументів друкує в консолі об’єкт зі значеннями ширини й висоти зображення, доступ до яких можна отримати через крапкову нотацію. |
Отже, отримуємо в консолі об’єкт зі значеннями розмірів зображення у пікселях:
{width: 60, height: 60}
Для елементів, які потрібно завантажувати, зокрема зображень, рекомендується викликати size() після того, як завантаження елемента завершиться.
|
У довідці по функціях p5.js , у розділі DOM представлені багато інших функцій для створення окремих елементів.
|
У бібліотеці p5.js
існує універсальна функція для створення будь-яких HTML
-елементів - createElement() .
Використаємо цю функцію, щоб створити елемент <small>
і збережемо покликання на нього з ім’ям small
:
let small = createElement('small', 'за підтримки <b>Processing Foundation</b>'); (1)
console.log(small.elt); (2)
1 | Для функції createElement() вказуємо два аргументи: назву тега і його вміст. |
2 | За допомогою властивості elt отримуємо доступ до створеного елемента <small> , який зберігається у small . |
У вебпереглядачі під полотном застосунку буде виведений дрібний текст за допомогою тега <small>
, а у консолі надрукується значення вузла-елемента DOM
:
<small>
за підтримки
<b>Processing Foundation</b>
</small>
Видалення елементів з DOM можна виконати за допомогою метода remove() бібліотеки p5.js .
|
B.5. Стилізація елементів
Використовуючи JavaScript
можна керувати не лише вмістом, але й зовнішнім виглядом елементів.
Одним зі способів надання стильового оформлення елементам - приєднання стилів за допомогою атрибута style
. Наприклад, встановимо колір Azure
і курсивне накреслення для тексту абзацу, використовуючи атрибут style
:
const sample = document.querySelector(".sample p");
sample.setAttribute("style", "color: #3a86ff; font-style: italic;");
console.log(sample);
В консолі отримаємо елемент абзацу із доданим атрибутом style
<p style="color: #3a86ff; font-style: italic;">Малюнок кола - зразок простого застосунку.</p>
а у вебпереглядачі - колірне оформлення абзацу із курсивним накресленням тексту відповідно.
Такий спосіб стилізації елементів має суттєвий недолік: якщо застосувати інший стиль до елемента абзацу за допомогою атрибуту style
, попереднє значення стилю буде перезаписане новим.
Цікавимось
Отже, використаємо інший спосіб стильового оформлення елементів - властивість style
, яка відкриває доступ до вбудованих стилів DOM
-елемента:
const p = document.querySelector("p");
console.log(p.style);
В консолі ми отримаємо об’єкт CSSStyleDeclaration
, який містить набір CSS
-властивостей для елемента <p>
:
CSSStyleDeclaration {accentColor: "", additiveSymbols: "", alignContent: "", alignItems: "", alignSelf: ""…}
Атрибут style - це текстовий рядок у HTML -розмітці, а властивість style - це об’єкт.
|
Щоб отримати доступ до значення конкретної властивості елемента, використовують крапкову нотацію і назву цієї властивості:
const p = document.querySelector("p");
console.log(p.style.color);
Оскільки для елемента <p>
не призначено жодних стилів, в результаті звернення до властивості color
ми отримуємо порожній рядок ""
. Зараз усі властивості об’єкта style
для елемента <p>
мають значення порожніх рядків.
Додамо до першого абзацу декілька стилів, використовуючи властивість style
. Стилі встановлюватимуть колір color
і внутрішні відступи padding
:
const p = document.querySelector("p");
p.style.color = "#3a86ff";
p.style.padding = "15px";
console.log(p.style);
Результат можна переглянути у вебпереглядачі на сторінці застосунку, а в консолі переконатися, що властивості color
і padding
об’єкта style
отримали значення:
...
color: "rgb(58, 134, 255)"
...
padding: "15px"
...
Кілька стилів для елемента <p>
можна записати одним рядком за допомогою властивості cssText
:
const p = document.querySelector("p");
p.style.cssText = "color: #3a86ff; padding: 15px";
console.log(p.style);
Цікавимось
Приклад доступу до властивості, назва якої складається з кількох слів:
const p = document.querySelector("p");
p.style.fontSize = "42px";
console.log(p.style);
Для видалення властивості елемента, значення для цієї властивості встановлюється рівним порожньому рядку. |
Більш оптимальний підхід у роботі зі стилями - опис заздалегідь усіх потрібних стилів у CSS
-класах і зберігання їх в окремому файлі з розширенням .css
, а потім додавання, зміна чи видалення CSS
-класів для будь-яких елементів, використовувати JavaScript
.
Завдяки редактору p5.js Web Editor
наш зразок проєкту вже містить створений файл стилів з назвою style.css
.
А втім, можна створити власний CSS
-файл і приєднати його до сторінки index.html
, за аналогією з файлом style.css
.

У файлі style.css
вже описаний стандартний набір стилів для основних елементів <html>
, <body
і <canvas>
вебсторінки застосунку.
html, body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
Додамо у правило для елемента <canvas>
, який утворює полотно застосунку, опис стилю background-color: #fefae0;
для встановлення кольору Cornsilk
для тла полотна:
canvas {
display: block;
background-color: #fefae0; // Cornsilk
}
Щоб колір тла застосунку змінився із сірого на Cornsilk
, необхідно також у файлі sketch.js
закоментувати рядок з функцією background(220);
, яка постійно замальовує новий колір тла:
function draw() {
// background(220);
...
}
Зберігши зміни у файлах, у вебпереглядачі отримаємо бажаний результат.
Додамо у файл style.css
опис ще двох CSS
-класів:
.features {
color: #ef476f; // Paradise Pink
}
.sample {
color: #06d6a0; // Caribbean Green
}
Для роботи з CSS
-класами DOM
-елементів використовується властивість classList
, яка повертає об’єкт - псевдомасив DOMTokenList
, що містить усі класи вузла-елемента.
Отримаємо список CSS
-класів для кожного з <div>
-елементів вебсторінки ескізу:
const divs = document.querySelectorAll("div");
for(const div of divs) {
console.log(div.classList);
}
Отримаємо три об’єкти DOMTokenList
для кожного <div>
відповідно.
Оскільки CSS
-клас .sample
вже застосований до другого <div>
вебсторінки ескізу index.html
, інформація про цей CSS
-клас міститься в другому DOMTokenList
:
DOMTokenList {0: "sample", entries: ƒ entries(), keys: ƒ keys(), values: ƒ values(), forEach: ƒ forEach()…}
0: "sample"
entries: ƒ entries() {}
keys: ƒ keys() {}
values: ƒ values() {}
forEach: ƒ forEach() {}
length: 1
value: "sample"
add: ƒ add() {}
contains: ƒ contains() {}
item: ƒ item() {}
remove: ƒ remove() {}
replace: ƒ replace() {}
supports: ƒ supports() {}
toggle: ƒ toggle() {}
toString: ƒ toString() {}
<constructor>: "DOMTokenList"
Об’єкт classList
має низку методів по роботі з CSS
-класами.
Використаємо метод add()
для додавання CSS
-класу .features
до інших елементів <div>
вебсторінки ескізу і метод contains()
, який перевіряє, чи є в елемента певний CSS
-клас:
const divs = document.querySelectorAll("div"); (1)
for(const div of divs) { (2)
if (!div.classList.contains("sample")) { (3)
div.classList.add("features"); (4)
}
console.log(div);
}
Отже, поданий код можна пояснити так:
1 | Шукаємо усі елементи <div> на вебсторінці застосунку. |
2 | Проходимо в циклі for..of по кожному елементу <div> . |
3 | Перевіряємо, чи поточний <div> не містить CSS -класу .sample . |
4 | Якщо відповідь так, не містить, додаємо до елемента CSS -клас .features . |
У підсумку, в консолі надрукується три елементи <div>
з CSS
-класами
<div class="features">…</div>
<div class="sample">…</div>
<div class="features">…</div>
а у вебпереглядачі будемо спостерігати оформлення різними кольорами текстового вмісту елементів <div>
залежно від застосованих CSS
-класів.
Для видалення в елемента CSS -класів використовують метод remove() властивості classList .
|
Ще один метод об’єкта classList
- toggle()
- додає CSS
-клас для елемента, якщо CSS
-клас відсутній для цього елемента, інакше - видаляє CSS
-клас.
Подивимось, як це відбувається на практиці, використавши код:
const divs = document.querySelectorAll("div");
for(const div of divs) {
div.classList.toggle("sample");
console.log(div);
}
Як бачимо, для елементів <div>
, в яких не було CSS
-класу .sample
, він був доданий, а для елементів, де був встановлений CSS
-клас .sample
- видалений:
<div class="sample">…</div>
<div class="">…</div>
<div class="sample">…</div>
Чи можна додати/видалити більше одного CSS
-класу? Так.
Наприклад:
const divs = document.querySelectorAll("div");
for(const div of divs) {
div.classList.add("features", "sample");
console.log(div);
}
При додаванні кількох CSS
-класів до елемента <div>
, якщо CSS
-клас вже присутній в елементі, він не буде доданий повторно:
<div class="features sample">…</div>
<div class="sample features">…</div>
<div class="features sample">…</div>
Бібліотека p5.js
також надає інструменти для стильового оформлення елементів. Використовуючи їх, можна легко змінити зовнішній вигляд вебсторінки, на якій виконується ескіз.
Отже, створимо кнопку, встановимо колір Beige
для тла кнопки й змінимо позицію кнопки у вікні перегляду:
let button = createButton("Надіслати відгук");
button.style("background-color", "#efebce");
button.position(0, 460);
У коді ми використали:
-
createButton() - функція для створення кнопки;
-
style() - метод для встановлення
CSS
-правила (кольору тла) для кнопки; -
position() - метод для розміщення кнопки у вікні перегляду.
Метод style()
у цьому разі можна використати інакше:
let button = createButton("Надіслати відгук");
button.style("background-color: #efebce; color: red;"); (1)
console.log(button.style("height")); (2)
button.position(0, 460);
1 | Використовуємо в методі style() один аргумент як рядок CSS -правил. |
2 | Коли метод style() приймає один аргумент, у цьому разі назву CSS -властивості height , отримуємо значення цієї CSS -властивості для кнопки. |
Результат можна переглянути у вікні вебпереглядача, а у консолі прочитати значення висоти кнопки у пікселях: 21px
.
Для роботи з CSS
-класами бібліотека p5.js
має у своєму арсеналі кілька методів:
-
class() - додає вказаний клас до елемента, а якщо аргумент - назва
CSS
-класу - не передається, метод повертає рядок, що містить поточнийCSS
-клас(и) елемента; -
addClass() - додає вказаний
CSS
-клас до елемента; -
removeClass() - видаляє вказаний
CSS
-клас з елемента; -
hasClass() - перевіряє, чи вказаний
CSS
-клас уже встановлено для елемента; -
toggleClass() - перемикає
CSS
-клас елемента.
Для створення CSS -класів метод class() варто застосовувати для елементів, у яких відсутні CSS -класи, оскільки він перезаписує в елементах наявні.
|
Поєднаємо методи для роботи з CSS
-класами в одному коді, щоб проаналізувати як вони працюють:
function setup() {
let cnv = createCanvas(200, 200);
let showCSS = "show"; (1)
// cnv.addClass(showCSS);
if (cnv.hasClass(showCSS)) { (2)
cnv.removeClass(showCSS);
console.log(`CSS-клас ${showCSS} був видалений із полотна.`);
} else { (3)
cnv.addClass(showCSS);
console.log(`CSS-клас ${showCSS} був доданий до полотна.`);
}
console.log("CSS-класи полотна:", cnv.class()); (4)
}
function draw() {
background(220);
fill("#2a9d8f");
noStroke();
circle(100, 100, 75);
}
1 | Зберігаємо назву CSS -класу show з ім’я showCSS . |
2 | За допомогою метода hasClass() перевіряємо, чи має полотно CSS -клас, що зберігається в showCSS . Якщо так - видаляємо з полотна CSS -клас, що зберігається в showCSS , за допомогою метода removeClass() і друкуємо про це повідомлення в консолі. |
3 | Інакше - додаємо до полотна CSS -клас, що зберігається в showCSS , за допомогою метода addClass() і друкуємо про це повідомлення в консолі. |
4 | Друкуємо в консолі інформацію про поточні класи, які має полотно, використовуючи метод class() . |
Результати виконання застосунку можна переглянути в консолі:
CSS-клас show був доданий до полотна.
CSS-класи полотна: p5Canvas show
На початку полотно має єдиний CSS
-клас: p5Canvas
. Якщо розкоментувати рядок у коді вище, до елемента полотна за допомогою метода addClass()
буде додано CSS
-клас, що зберігається в showCSS
і в консолі буде надрукована інформація про протилежну дію:
CSS-клас show був видалений із полотна.
CSS-класи полотна: p5Canvas
Результат вище, але з меншою кількістю рядків коду і без використання розгалуження, можна досягти за допомогою методу toggleClass()
:
function setup() {
let cnv = createCanvas(200, 200);
let showCSS = "show";
// cnv.addClass(showCSS);
cnv.toggleClass(showCSS);
console.log("CSS-класи полотна:", cnv.class());
}
function draw() {
background(220);
fill("#2a9d8f");
noStroke();
circle(100, 100, 75);
}
Цей метод працює як перемикач CSS
-класів: якщо в елемента CSS
-клас відсутній, метод його додає і навпаки.
B.6. Навігація по елементах
Методи пошуку - не єдиний спосіб отримати потрібні елементи чи колекції елементів. Дістатися до елемента можна за допомогою родинних зв’язків.
Використовуючи родинні зв’язки між вузлами-елементами можна отримати доступ до членів родини DOM
-дерева.
Для доступу до дітей певного вузла у DOM
-дереві використовують властивість children , яка повертає лише дочірні елементи цього вузла у вигляді колекції HTMLCollection
.
Наприклад:
const div = document.querySelector("div");
console.log(div.children);
У результаті отримаємо дітей першого <div>
-елемента:
HTMLCollection {0: HTMLParagraphElement, 1: HTMLParagraphElement, 2: HTMLParagraphElement, length: 3, item: ƒ item()…}
0: <p>p5.js - бібліотека JavaScript для креативного кодування.</p>
1: <p>Безплатна, з відкритим початковим кодом.</p>
2: <p>Використовуючи метафору ескізу, p5.js має повний набір функцій малювання.</p>
length: 3
item: ƒ item() {}
namedItem: ƒ namedItem() {}
<constructor>: "HTMLCollection"
childNodes - аналогічна children властивість, яка повертає колекцію типу NodeList , що містить дочірні вузли-елементи, текстові вузли та вузли-коментарі.
|
Щоб отримати текстові значення дочірніх елементів, можна скористатися циклом for..of
const div = document.querySelector("div");
for(const child of div.children) {
console.log(child.textContent);
}
і властивістю textContent
:
p5.js - бібліотека JavaScript для креативного кодування.
Безплатна, з відкритим початковим кодом.
Використовуючи метафору ескізу, p5.js має повний набір функцій малювання.
Для навігації в DOM
-дереві можна використати також parentElement і parentNode - властивості, які повертають батька вузла-елемента або null
, якщо вузол немає батька.
Наприклад:
const heading = document.querySelector("h1");
console.log(heading.parentElement);
console.log(heading.parentElement.parentElement);
У результаті отримаємо <main>
- батька <h1>
-елемента і <body>
- батька <main>
-елемента:
<main id="content">…</main>
<body>…</body>
Існують й інші властивості, які допомагають здійснювати навігацію по вузлам-елементам DOM -дерева: firstElementChild , lastElementChild , previousElementSibling , lastElementChild , previousSibling , nextSibling .
|
Ідею родинних зв’язків бібліотека p5.js
реалізує через власні вбудовані методи: parent() і child() .
Щоб подивитись на батьків чи дітей певного вузла-елемента, вищезгадані методи використовують без аргументів.
Наприклад, визначимо родинні зв’язки для елемента <main>
, який має id
зі значенням content
:
const main = select("#content");
console.log(main.parent());
console.log(main.child());
У цьому разі вузол-елемент main
має батька <body>
і колекцію NodeList
дочірніх вузлів: <h1>
, три <div>
, <canvas>
і текстові вузли.
У попередніх прикладах, використовуючи метод position()
, ми встановлювали положення для будь-яких елементів на вебсторінці ескізу. Використовуючи методи child()
і parent()
, можна приєднувати елемент до вказаного батьківського елемента.
Проілюструємо родинні зв’язки на прикладі використання метода child()
(це можна зробити й за допомогою parent()
):
let div = createDiv("це батько");
let p = createP("це дитина");
let span = createSpan("це нащадок");
p.child(span); (1)
div.child(p); (2)
console.log(div.elt);
1 | За допомогою метода child() елемент <span> розміщуємо в елементі <p> . У цьому разі <p> - батьківський елемент для дочірнього елемента <span> . |
2 | За допомогою метода child() елемент <p> розміщуємо в елементі <div> . У цьому разі <div> - батьківський елемент для дочірнього елемента <p> . |
Окрім того, елемент <span>
є нащадком для <div>
, у чому можна переконатися в консолі:
<div>
це батько
<p>
це дитина
<span>це нащадок</span>
</p>
</div>
У підсумку, наведемо ще один приклад приєднання елементів:
function setup() {
let cnv = createCanvas(200, 200);
let div = select("div");
div.child(cnv);
}
function draw() {
background(220);
fill("#2a9d8f");
noStroke();
circle(100, 100, 75);
}
У цьому разі елемент полотна <canvas>
був приєднаний до першого елемента <div>
вебсторінки ескізу, в чому можна переконатися у вікні вебпереглядача.
B.7. Події
Вебзастосунки проєктуються у такий спосіб, що вони завжди очікують взаємодії з користувачем, тобто чекають настання певних подій, як-от натискання кнопки, виділення тексту на вебсторінці й т. д., а потім реагують на них.
Кожна така подія має свій тип (ім’я), наприклад, mousemove
- користувач перемістив вказівник миші, click
- користувач натиснув один раз ліву кнопку миші, keydown/keyup
- користувач натиснув/відпустив клавішу на клавіатурі тощо.
Щоб реагувати на різні типи подій у вебпереглядачі, використовують функції, які називаються обробниками подій.
Отже, як і раніше, скористаємось зразком проєкту, щоб продемонструвати подійно-орієнтований підхід у взаємодії користувача з вебсторінкою.
Розглянемо ситуацію, в якій вебпереглядач друкує в консолі сповіщення про те, що відбулася подія - натискання лівої кнопки миші на певному елементі.
Для цього використаємо наступний JavaScript
-код
const h1 = document.querySelector("h1"); (1)
h1.addEventListener("click", function() { (2)
console.log("Ви натиснули на h1!");
});
і проаналізуємо кроки його виконання:
1 | Шукаємо на вебсторінці перший елемент заголовка <h1> і зберігаємо покликання на нього з ім’ям h1 . |
2 | За допомогою метода addEventListener() реєструємо у вебпереглядачі для h1 функцію обробника події типу click (натискання лівої кнопки миші), яка викликається, коли подія вказаного типу настає. |
Метод addEventListener() ще називають слухачем подій.
|
Функцію обробника події можна також записати у формі стрілочної функції:
h1.addEventListener("click", () => {
console.log("Ви натиснули на h1!");
});
У підсумку, в консолі буде друкуватися повідомлення
Ви натиснули на h1!
разом зі значенням разів натискань лівої кнопки миші на заголовку <h1>
.
Розглянемо приклад використання іншого типу події - copy
:
const h1 = document.querySelector("h1");
h1.addEventListener("copy", function() {
console.log("Ви скопіювали заголовок h1!");
});
Коли у вебпереглядачі скопіювати текст заголовка JavaScript, p5.js і DOM
, настане подія copy
і слухач подій addEventListener()
, який зареєстрований у вебпереглядачі для h1
, викличе функцію обробника події copy
:
Ви скопіювали заголовок h1!
Більше детально про інші типи подій читайте у довідкових матеріалах сайту MDN Web Docs .
|
Тепер розглянемо приклад, коли необхідно відстежувати події на кількох елементах:
const paragraphs = document.querySelectorAll("p"); (1)
paragraphs.forEach((paragraph) => { (2)
paragraph.addEventListener("click", function() { (3)
console.log(paragraph.textContent); (4)
});
});
Проаналізуємо наведений код:
1 | Шукаємо на вебсторінці усі елементи абзаців <p> і зберігаємо покликання на колекцію знайдених елементів з ім’ям paragraphs . |
2 | Проходимо по колекції за допомогою методу forEach . |
3 | Для кожного абзацу із колекції реєструємо функцію обробника події click . |
4 | При події click на кожному абзаців, функція обробника події друкує в консолі текстовий вміст поточного абзацу. |
Якщо після запуску застосунку натискати лівою кнопкою миші на рядки абзаців у вебпереглядачі, в консолі будуть з’являтись текстові значення цих абзаців.
Коли відбувається подія натискання абзацу, вебпереглядач створює об’єкт події, в який записує деталі події та передає його як аргумент функції обробника події.
Змінимо наш попередній код, врахувавши об’єкт події, для якого оберемо ім’я event
(англ. event
- подія), хоча можна обрати й іншу назву:
const paragraphs = document.querySelectorAll("p");
paragraphs.forEach((paragraph) => {
paragraph.addEventListener("click", function(event) {
console.log(event);
});
});
Тепер, коли запустити застосунок і натиснути, наприклад, на абзаці Малюнок кола - зразок простого застосунку.
, в консолі отримаємо об’єкт події PointerEvent
, збережений з ім’ям event
.
Якщо звернутися до властивості target
об’єкта event
, можна отримати ціль події - об’єкт, для якого ця подія настала:
...
console.log(event.target);
...
Найглибший елемент, який викликає подію, називається цільовим елементом і він доступний через event.target .
|
У підсумку, застосуємо стильове оформлення кольором Tiffany Blue
до тих абзаців, які натискаємо:
const paragraphs = document.querySelectorAll("p");
paragraphs.forEach((paragraph) => {
paragraph.addEventListener("click", function(event) {
event.target.style.backgroundColor = "#2ec4b6";
});
});
Тепер, натискаючи на абзацах тексту, вони виокремлюються кольором, а елементи <p>
в HTML
-розмітці динамічно отримують вбудовані стилі у вигляді style="background-color: rgb(46, 196, 182);"
.
Розглянемо ще один приклад, який ілюструє спосіб роботи з DOM
, що має назву делегування подій.
Для першого елемента <div>
на вебсторінці за допомогою метода addEventListener()
зареєструємо функцію обробника події click
, яка у вебпереглядачі навколо цього елемента <div>
, в разі натискання по ньому, створить межу у вигляді червоної штрихової лінії:
const divFirst = document.querySelector("div");
divFirst.addEventListener("click", function(event) {
event.target.style.border = "1px dashed red";
console.log(event.target);
});
Виконавши застосунок, і натискаючи в області першого елемента <div>
, у вебпереглядачі та в консолі з’явиться наступний результат:
<p style="border: 1px dashed red;">p5.js - бібліотека JavaScript для креативного кодування.</p>
<div style="border: 1px dashed red;">…</div>
Як бачимо, обробник події, зареєстрований для <div>
, викликається, якщо навіть натискати на будь-якому з елементів <p>
, які лежать у <div>
.
У цьому разі використовують делегування подій, алгоритм якого можна описати так:
-
Зареєструвати функцію обробника події для батьківського елемента.
-
У функції обробнику події перевірити наявність цільового елемента за допомогою
event.target
. -
Якщо подія виникла на потрібному елементі, то використовувати його.
У коді цей алгоритм може бути записаний так:
const main = document.querySelector("main");
main.addEventListener("click", function(event) { (1)
if (event.target.tagName == "DIV") { (2)
event.target.style.border = "1px dashed red"; (3)
console.log("target", event.target, "- елемент, який ініціював подію");
} else {
console.log("currentTarget", event.currentTarget, "- елемент, до якого приєднано обробник події"); (4)
}
});
1 | Реєструємо функцію обробника події click для батьківського елемента <main> . |
2 | Перевіряємо за допомогою event.target.tagName чи цільовий елемент, на якому натискаємо, має тег <div> . |
3 | Якщо це <div> , встановлюємо для нього стильове оформлення у вигляді межі та друкуємо елемент, який викликав подію, тобто <div> . |
4 | Інакше - друкуємо значення властивості event.currentTarget . |
В залежності від місця натискань у вебпереглядачі, в консолі отримуємо:
currentTarget <main id="content">…</main> - елемент, до якого приєднано обробник події
target <div style="border: 1px dashed red;">…</div> - елемент, який ініціював подію
Пояснимо отримані результати. Якщо натискати лівою кнопкою миші на вебсторінці застосунку в області першого елемента <div>
, в консолі буде друкуватись значення властивості target
- елемент <div>
, який ініціював подію.
Натискання лівої кнопки миші в інших місцях області перегляду застосунку у вебпереглядачі, друкує значення властивості currentTarget
- елемент <main>
, до якого приєднано обробник події click
.
Ідея підходу делегування події в тому, що якщо ми маємо багато елементів, події на яких потрібно обробляти подібно, то замість того, щоб призначати обробник індивідуально кожному елементу, ми ставимо один обробник на їхнього спільного батька.
У цьому разі можна отримати цільовий елемент event.target
, зрозуміти на якому саме нащадку сталася подія та обробити її.
Бібліотека p5.js
також має низку функцій, які можна використовувати для приєднання обробників подій до елементів. Такі функції бібліотеки є слухачами різних подій, за аналогією з методом addEventListener()
у JavaScript
.
Одна із таких функцій - mousePressed() - викликається один раз після кожного натискання будь-якої кнопки миші на елементі.
Щоб продемонструвати роботу цієї функції, змінимо код у файлі sketch.js
:
let d; (1)
function setup() {
createCanvas(200, 200);
d = 75; (2)
}
function draw() {
background(220);
fill("#2a9d8f");
noStroke();
circle(100, 100, d); (4)
}
function mousePressed() { (3)
d = d + 5;
}
1 | Описуємо глобальну змінну d , яка буде вказувати на значення діаметра кола. |
2 | Присвоюємо початкове значення 75 пікселів для d . |
3 | Описуємо функцію mousePressed() , яка щоразу при натисканні будь-якою кнопкою у вікні перегляду застосунку буде запускатися і збільшувати значення діаметра кола на 5 пікселів. |
4 | Малюємо коло із поточним значенням діаметра d . |
Після запуску застосунку, натискання будь-якою кнопкою миші у будь-якому місці вікна перегляду буде збільшувати діаметр кола.
У цьому разі функція mousePressed()
виконує ролі як слухача події (натискання кнопок миші), так і обробника події (у тілі функції виконується збільшення значення діаметра кола).
Оголосимо ще дві глобальні змінні: cnv
- міститиме покликання на полотно застосунку і g
- міститиме покликання на значення кольору для полотна:
let d, g, cnv;
function setup() {
cnv = createCanvas(200, 200);
cnv.mousePressed(changeColor); (3)
d = 75;
g = 220; (1)
}
function draw() {
background(g); (4)
fill("#2a9d8f");
noStroke();
circle(100, 100, d);
}
function mousePressed() {
d = d + 5;
}
function changeColor() { (2)
g = random(0, 255);
}
1 | Присвоюємо початкове значення кольору для полотна з ім’ям g . |
2 | Оголошуємо функцію з назвою changeColor() , яка за допомогою вбудованої функції random() буде надавати випадкове значення кольору і зберігати його з ім’ям g . |
3 | Функція mousePressed() , яка є слухачем події натискання кнопки миші на елементі полотна cnv , отримує як аргумент функцію changeColor() , яка є обробником цієї події. |
4 | Колір полотна змінюється відповідно значення g . |
Коли запустити застосунок, щоразове натискання кнопки миші у будь-якому місці вікна перегляду буде збільшувати розмір зафарбованого кола, а при натисканні на полотні - ще й змінювати колір полотна.
Розглянемо ще кілька методів, які можна приєднати як слухачі події переміщення вказівника миші:
-
mouseOver() - викликається один раз після кожного переміщення вказівника миші до елемента.
-
mouseOut() - викликається один раз після кожного переміщення вказівника миші від елемента.
Щоб подивитись на ці методи в дії, створимо застосунок для вивчення назв об’єктів англійською.
Ідея наступна: при наведенні вказівника миші на певне зображення буде з’являтися його назва і водночас зображення будe збільшуватись у своїх розмірах.
Спочатку у застосунку буде один об’єкт - одне зображення. Отже, напишемо код, щоб реалізувати нашу ідею:
function setup() {
createCanvas(200, 200);
let img = createImg( (1)
"https://res.cloudinary.com/gtstack/image/upload/v1656015129/p5js/amanita_rxsrqo.png",
"amanita"
);
img.size(50, 50); (2)
img.position(random(150), random(250, 400)); (3)
let amanita = select("img"); (4)
amanita.mouseOver(selectObj); (5)
amanita.mouseOut(unselectObj); (6)
}
function draw() {
background("#5C374C"); // Dark Byzantium
}
function selectObj() { (7)
this.size(60, 60);
let altText = this.attribute("alt");
this.attribute("title", altText);
}
function unselectObj() { (8)
this.size(50, 50);
}
1 | Створюємо за допомогою функції createImg() елемент зображення <img> з двома атрибутами src (URL -адреса файлу, що містить зображення) і alt (альтернативний текст для зображення) і зберігаємо з ім’ям img . |
2 | За допомогою метода size() встановлюємо для img розміри, тобто надаємо значення 50 пікселів атрибутам width і height . |
3 | За допомогою метода position() розміщуємо зображення img у точці з випадковими значеннями координат, які одержуються з функції random() . Аргументи для функції random() обрані таким способом, щоб випадкова поява зображення була над полотном застосунку. |
4 | Вибираємо перший і єдиний елемент зображення <img> на вебсторінці застосунку за допомогою функції select() і зберігаємо з ім’ям amanita . |
5 | Приєднуємо до amanita функцію mouseOver() - слухач події наведення вказівника миші на зображення, яка отримує як аргумент користувацьку функцію selectObj() , що є обробником вказаної події. |
6 | Приєднуємо до amanita функцію mouseOut() - слухач події відведення вказівника миші від зображення, яка отримує як аргумент користувацьку функцію unselectObj() , що є обробником вказаної події. |
7 | Описуємо користувацьку функцію selectObj() , яка буде обробником події наведення вказівника миші на зображення. У тілі функції використовуємо зарезервоване слово this , яке вказує на поточний об’єкт - amanita , для якого викликається користувацька функція. Далі, за допомогою метода size() збільшуємо розміри зображення. Потім, використовуючи метод attribute() , отримуємо значення атрибута alt і зберігаємо з ім’ям altText та використовуємо altText як значення для нового атрибута title для amanita . Завдяки title над зображенням буде з’являтись назва. |
8 | Описуємо користувацьку функцію unselectObj() , яка буде обробником події відведення вказівника миші від зображення. У тілі функції повертаємо розміри зображення до значень, встановлених у пункті 2. |
Який результат отримаємо при запуску застосунку?
У випадковому місці над тлом полотна кольору Dark Byzantium
з’являється наше зображення.

При наведенні вказівника миші на зображення - зображення збільшується у розмірах і над ним виникає текст з назвою, що спливає. Коли вказівник із зображення відводимо, розміри зображення повертаються до попередніх значень і текстовий напис зникає.
Ускладнимо наш застосунок у разі, коли є кілька зображень і при наведенні на одному із зображень це зображення зникає, а інші зображення наслідують поведінку як у попередньому прикладі.
Розглянемо код, що реалізує наш задум:
const fruitsImages = { (1)
apple:
"https://res.cloudinary.com/gtstack/image/upload/v1656012839/p5js/apple_xwllwv.png",
banana:
"https://res.cloudinary.com/gtstack/image/upload/v1656012839/p5js/banana_gf06eh.png",
pear:
"https://res.cloudinary.com/gtstack/image/upload/v1656014918/p5js/pear_yy7wsf.png",
amanita:
"https://res.cloudinary.com/gtstack/image/upload/v1656015129/p5js/amanita_rxsrqo.png",
};
function setup() {
createCanvas(200, 200);
for (const fruit in fruitsImages) { (2)
let img = createImg(fruitsImages[fruit], fruit); (3)
img.size(50, 50);
img.position(random(150), random(250, 400));
}
let images = selectAll("img"); (4)
for (let i = 0; i < images.length; i++) { (5)
images[i].mouseOver(selectObj);
images[i].mouseOut(unselectObj);
}
}
function draw() {
background("#5C374C"); // Dark Byzantium
}
function selectObj() {
this.size(60, 60);
let altText = this.attribute("alt");
this.attribute("title", altText);
if (altText == "amanita") { (6)
this.hide();
}
}
function unselectObj() {
this.size(50, 50);
}
1 | Оголошуємо об’єкт fruitsImages , який міститиме пари ключ:значення, в яких ключами будуть назви, а значеннями - URL -адреси файлів зображень. Якщо треба, зображення можна завантажити в певний каталог проєкту і вказати відносні шляхи до файлів зображень. |
2 | За допомогою циклу for..in , який використовується для проходу через властивості об’єкта, створюємо елементи зображення <img> з атрибутом src , що містить URL -адреси файлів зображень, які отримуємо з об’єкта fruitsImages за допомогою fruitsImages[fruit] , і атрибутом alt - значення назви беремо з fruit . Задаємо розмір зображення і випадкову позицію в межах полотна. |
3 | Шукаємо усі елементи <img> на вебсторінці застосунку і зберігаємо у масив з ім’ям images . |
4 | Використовуємо цикл for для проходження по кожному елементу масиву images , додаючи до елементів зображень слухачі подій mouseOver() і mouseOut() , аргументами для яких є обробники подій selectObj() і unselectObj() відповідно. |
5 | Якщо для елемента зображення встановлений атрибут альтернативного тексту alt зі значенням amanita , використовуємо зарезервоване слово this , що вказує на поточний об’єкт, і метод hide() , що приховує поточний елемент. |

У підсумку, у нас вийшов простий інтерактивний застосунок для вивчення не лише назв об’єктів англійською, але й властивостей цих об’єктів.
Розглянемо ще два методи, які можна приєднати як слухачі події перетягування файлів за допомогою вказівника миші:
-
dragOver() - викликається один раз після кожного перетягування файлу над елементом.
-
dragLeave() - викликається один раз щоразу, коли файл, що перетягується, залишає область елемента.
Використовуючи вищезгадані методи, реалізуємо простий застосунок для завантаження файлу на вебсторінку застосунку та отримання інформації про файл: ім’я, тип тощо.
Розпочнемо із такого коду:
function setup() {
cnv = createCanvas(200, 200);
let upload = createP("відвантажити файл"); (1)
upload.style("background-color", "#fefae0"); // Cornsilk
upload.style("text-align", "center");
upload.style("padding", "15px");
upload.style("border", "2px dashed pink"); // Pink
upload.size(140);
upload.dragOver(over); (2)
upload.dragLeave(outside); (3)
}
function draw() {
background(220);
fill("#2a9d8f");
noStroke();
circle(100, 100, 75);
}
function over() { (4)
this.style("background-color", "#ffe5d9"); // Unbleached Silk
}
function outside() { (4)
this.style("background-color", "#fefae0"); // Cornsilk
}
1 | Створюємо елемент <p> з ім’ям upload і додаємо стильове оформлення: колір тла, вирівнювання тексту, внутрішні відступи, стиль межі та ширину в пікселях для елемента. |
2 | Приєднуємо до upload два методи dragOver() і dragLeave() , які є слухачами подій перетягування файлів на область елемента чи поза нею відповідно. Аргументами методів є користувацькі функції over() і outside() - обробники цих подій відповідно. |
3 | Описуємо функцію over() - обробник події перетягування файлу на область елемента. У тілі функції застосовується стиль для області елемента, як тільки функція буде викликана. |
4 | Описуємо функцію outside() - обробник події перетягування файлу від області елемента. У тілі функції застосовується стиль для області елемента, як тільки функція буде викликана. |
Після запуску застосунку, якщо захопити вказівником миші будь-який файл і перетягнути на елемент <p>
, оформлення елемента зміниться відповідно наших налаштувань, описаних в коді.
Коли відпустити вказівник миші у вікні вебпереглядача - файл буде відкритий у вкладці вебпереглядача або вебпереглядач запропонує зберегти його. Це типова поведінка вебпереглядача при перетягуванні файлів у його вікно.
Для нас цікава зовсім інша поведінка - відображення файлу на вебсторінці застосунку з інформацією про сам файл.
Використаємо для наших цілей метод drop() - слухач події завантаження файлу перетягуванням й відпускання на елементі.
function setup() {
...
upload.dragOver(over);
upload.dragLeave(outside);
upload.drop(gotFile); (1)
}
function draw() {
...
}
function over() {
...
}
function outside() {
...
}
function gotFile(file) { (2)
createP("Ім'я: " + file.name);
createP("Обсяг у байтах: " + file.size);
createP("Тип: " + file.subtype);
createImg(file.data, file.name.slice(0, -4));
}
1 | Для елемента з ім’ям upload приєднуємо метод drop() - слухач події завантаження файлу на елементі, який як аргумент отримує користувацьку функцію gotFile() - обробник цієї події. |
2 | Описуємо користувацьку функцію gotFile() , яка отримує об’єкт file класу p5.File , до властивостей якого ми звертаємось через крапкову нотацію, а самі значення використовуємо як аргументи для функцій створення елементів абзацу і зображення. |
У нашому коді було використано JavaScript -метод slice() , який повертає частину рядка у вигляді нового рядка без зміни оригінального рядка. Наприклад, якщо до оригінального рядка "apple.png" застосувати slice(0, -4) , то утвориться ще один новий рядок зі значенням "apple" .
|
Тепер, якщо перетягувати файли на елемент upload
і відпускати їх над ним, на вебсторінці застосунку один під одним будуть створюватись зображення із цих файлів й відображатися інформація про кожен файл.
Об’єкт file
класу p5.File
має низку властивостей:
-
type
- зображення, текст тощо; -
subtype
- розширення файлу; -
name
- повне ім’я файлу; -
size
- обсяг файлу у байтах; -
data
- рядокURL
-адреси, що містить дані зображення, текстовий вміст файлу або об’єкт, якщо файл має форматJSON
.
Для відвантаження файлів на вебсторінку застосунку також можна використовувати функцію createFileInput() , яка створює елемент <input> у DOM типу file і дозволяє вибирати локальні файли для використання в ескізі.
|
Решту функцій, які використовуються для приєднання обробників подій для елементів, можна знайти на сторінці базового класу p5.Element , на основі якого створюються усі об’єкти ескізу. |
Цікавимось
B.8. Порівняльна таблиця методів/функцій JavaScript і p5.js
У довідці по функціях p5.js міститься окремий розділ , присвячений роботі з DOM .
|
Дія для |
JavaScript |
p5.js |
Знайти перший |
|
|
Знайти усі |
|
|
Додати атрибут |
|
|
Видалити атрибут |
|
|
Отримати вміст |
|
|
Перезаписати вміст |
|
|
Доповнити вміст |
|
|
Очистити вміст |
|
|
Створити із вмістом і вставити у документ перед |
|
|
Стати дитиною для |
|
|
|
||
Стати батьком для |
|
|
|
||
Видалити |
|
|
Додати вбудований стиль |
|
|
|
|
|
Отримати класи |
|
|
Перевірити клас |
|
|
Додати клас |
|
|
Видалити клас |
|
|
Перемкнути клас |
|
|
Додаток C: Черепашача графіка
У цьому розділі представлений огляд середовища JavaScript Turtle Graphics Library , яке використовує можливості p5.js і JavaScript для створення Черепашачої графіки (Turtle Graphics ).
|
C.1. Передмова
JavaScript Turtle Graphics Library
- це середовище програмування, до складу якого входять:
-
JavaScript
-бібліотекаTurtleGL.js
, яка містить базовий набір інструментів для програмування Черепашачої2D
-графіки (Turtle Graphics
) у вебпереглядачі, використовуючи об’єктоорієнтований підхід; -
застосунок Інтерактивна мапа, який є зручним інструментом для створення прототипів зображень.
Мета створення середовища JavaScript Turtle Graphics Library
- навчання основам програмування на прикладі роботи з Черепашачою 2D
-графікою у вебпереглядачі, використовуючи мову програмування JavaScript
і можливості бібліотеки p5.js
.
Особливості використання середовища JavaScript Turtle Graphics Library у формі інтерактивного покрокового підручника .
|
C.2. Приєднання бібліотеки TurtleGL.js
Щоб використовувати бібліотеку TurtleGL.js
, її спочатку необхідно приєднати у свій проєкт. Для цього оберіть один зі способів:
-
В онлайн-редакторі p5.js Web Editor :
-
відвантажити у каталог проєкту файл бібліотеки
TurtleGL.js
; -
приєднати файл бібліотеки
TurtleGL.js
у файліindex.html
перед файлом ескізуsketch.js
.
-
-
У разі локальної розробки необхідно завантажити зразок проєкту
p5.js
, у який приєднати файл бібліотекиTurtleGL.js
у файліindex.html
перед файлом ескізуsketch.js
. -
В онлайн-редакторі CodePen :
-
перейти у вкладку
JS
; -
натиснути на зображення шестерні;
-
у розділі
Add External Scripts/Pens
у рядку пошуку знайти й обратиp5.js
; -
у розділі
Add External Scripts/Pens
натиснути на кнопку+add another resource
і додати посилання на файл бібліотекиTurtleGL.js
; -
зберегти зміни, натиснувши кнопку
Save & Close
(для зареєстрованих користувачів), інакше - кнопкуClose
.
-
-
В онлайн-середовищі Replit :
-
створити новий
Repl
на основі шаблонуp5.js
; -
відвантажити файл бібліотеки
TurtleGL.js
у створенийRepl
; -
приєднати файл бібліотеки у файлі
index.html
перед файлом ескізуscript.js
.
-
-
В онлайн-середовищі OpenProcessing :
-
у розділі
FILES
ескізу відвантажити у проєкт файл бібліотекиTurtleGL.js
; -
перейти у розділ
SKETCH
ескізу та обрати режим (MODE
)HTML/CSS/JS
; -
приєднати відвантажений файл бібліотеки
TurtleGL.js
у файліindex.html
перед файлом ескізуmySketch.js
.
-
-
Використати p5.js Widget Editor - простий онлайн-редактор, створений на основі p5.js-widget - інструменту для вбудовування на сторінки сайтів редактора для запуску і редагування ескізів
p5.js
. У цей редактор бібліотекаTurtleGL.js
вже інтегрована і готова до використання.
Для належної роботи застосунків, які запускаються локально, необхідно використовувати локальний вебсервер . Вебсервер запускається із каталогу, у який був розпакований завантажений архів зі зразком проєкту. У цьому разі, щоб переглянути свої ескізи, необхідно перейти у вебпереглядачі за адресою http://localhost:port/empty-example/index.html , де port - номер порту.
|
Бібліотека Бібліотеку |
C.3. Застосунок "Інтерактивна мапа"
Для зручності роботи із бібліотекою TurtleGL.js
створений інтерактивний застосунок, за допомогою якого можна:
-
відстежувати, фіксувати на полотні та зберігати координати Черепашки у текстовий файл;
-
зберігати у файл полотно з малюнком у форматі
.png
; -
змінювати властивості Черепашки/олівця (колір, форму, розмір, кутову орієнтацію Черепашки, товщину і колір олівця);
-
відвантажувати на полотно зображення у форматах
PNG
таJPG
й регулювати прозорість зображень.
Застосунок Інтерактивна мапа опублікований:
-
у форматі окремої вебсторінки ;
-
у форматі ескізу (за потреби зробити
FORK
ескізу).
C.4. Використання бібліотеки TurtleGL.js
C.4.1. Початкові налаштування
У блоці setup()
записуємо функцію createCanvas(windowWidth, windowHeight)
для створення полотна, де windowWidth
і windowHeight
- це системні змінні, що зберігають значення ширини і висоти внутрішнього вікна (тобто вікна перегляду, у якому вебпереглядач «малює» вебсторінку) відповідно, і викликаємо myCode()
- це назва блоку, в якому записані інструкції для Черепашки.
let t;
function setup() {
createCanvas(windowWidth, windowHeight);
myCode();
}
Значення розмірів полотна, які за стандартним налаштуванням використовує функція createCanvas()
(коли викликається без аргументів) - 200x200
пікселів. За потреби можна створити полотно будь-якого розміру, зазначивши у createCanvas()
відповідні значення розмірів ширини й висоти.
Також на початку у коді оголошуємо ім’я для майбутньої Черепашки - t
.
Ім’я для Черепашки можна обрати на свій задум. |
Блок myCode()
має таку структуру:
function myCode(){
// інструкції для Черепашки
}
Усі інструкції для керування Черепашкою записуємо у блоці myCode() . Назву цього блоку можна змінити на свій задум.
|
Завжди першою інструкцією у блоці myCode()
є інструкція зі створення об’єкта Черепашки з певним ім’ям (наприклад, t
), оголошеним раніше:
function myCode(){
t = new Turtle();
}
Отож, Черепашка отримала своє ім’я t
. Усі наступні інструкції необхідно записувати у форматі t.інструкція
.
C.4.2. Координатна сітка
Якщо необхідно створити координатну сітку на полотні, у блоці myCode()
до Черепашки з ім’ям t
застосовуємо інструкцію grid()
:
function myCode(){
t = new Turtle();
t.grid();
}
Інструкція grid()
використовує три параметри із такими значеннями за стандартним налаштуванням:
Значення кольору записується як рядок в одному із форматів: "red" (назва), "#fdfd90" (шістнадцяткове значення), "rgb(11, 156, 78)" (значення червоної, зеленої, синьої складових), "rgba(45, 145, 67, 0.5)" (значення червоної, зеленої, синьої складових і прозорості).
|
Значення за стандартним налаштуванням використовуються тоді, коли grid()
викликається без аргументів, як у прикладі вище. За потреби grid()
можна викликати із користувацькими значеннями, зазначивши їх у дужках у вказаному порядку. Наприклад:
function myCode(){
t = new Turtle();
t.grid(30, "rgba(43, 41, 70, 0.5)", "#ff9800"); // Space cadet, Orange peel
}
Використовуючи інструкцію setStepGrid(step)
перед малюванням сітки можна окремо встановити крок сітки step
, а за допомогою інструкції getStepGrid()
можна отримати поточне значення кроку сітки.
C.4.3. Відображення Черепашки, інформаційна панель та компас
За стандартним налаштуванням на полотні відображається інформаційна панель з даними про:
-
стан Черепашки (кутову орієнтацію, поточні координати);
-
стан олівця (на полотні чи піднятий);
-
координати вказівника миші.
А у правому нижньому куті полотна увімкнений компас, який вказує напрямок і значення кутової орієнтації Черепашки.
За відображення вищезгаданих елементів інтерфейсу відповідають інструкції, які записуються у блоці draw()
function draw() {
t.place();
t.compass();
t.dashboard();
}
і використовуються для таких цілей:
-
place()
- показати Черепашку на полотні у її поточних координатах (цю інструкцію рекомендується завжди використовувати); -
compass()
- показати компас; -
dashboard()
- показати інформаційну панель.
Також на екрані можна відобразити ім’я розробника: у блоці draw() розмістити інструкцію t.creator() .
|
C.4.4. Черепашка і p5.js
На полотні поруч з Черепашкою можна створювати зображення, використовуючи інструменти бібліотеки p5.js
. Виклики функцій p5.js
для цих цілей записуються у тілі функцій draw()
чи setup()
. Зверніть увагу, що за стандартним налаштуванням початок координат (0, 0)
розташований у лівому верхньому куті полотна.
Початок координат (0, 0)
для Черепашки міститься в центрі полотна. За таких умов, одночасно відстежувати координати Черепашки та координати для побудови фігур складно.
У цьому разі код для побудови фігур за допомогою інструментів бібліотеки p5.js
можна записувати у блоці myDraw()
(за потреби назву блоку можна змінити на іншу) між коментарями // початок коду для фігур
і // кінець коду для фігур
, а виклик myDraw()
помістити у блоці draw()
:
function draw() {
myDraw();
t.place();
t.compass();
t.dashboard();
}
function myDraw(){
push();
translate(width / 2, height / 2);
scale(1, -1);
// початок коду для фігур
let [x, y] = t.getPosition();
stroke(208, 85, 163); // Mulberry
fill(255, 0);
circle(int(x), int(y), 100);
// кінець коду для фігур
pop();
}
За допомогою виразу let [x, y] = t.getPosition();
можна отримати поточні координати Черепашки з ім’ям t
в координатній сітці, початок координат якої міститься в центрі полотна, і, за потреби, використати для побудови зображень фігур та створення анімаційних ефектів за допомогою інструментів бібліотеки p5.js
.
У разі використання функції background()
із бібліотеки p5.js
, варто правильно зазначити місце її виклику у коді, щоб уникнути небажаного зафарбовування всього полотна. Таким місцем розташування виклику функції background()
може бути тіло функції myDraw()
.
Між коментарями // початок коду для сітки і тла полотна
і // кінець коду для сітки і тла полотна
також можна розмістити виклик інструкції для малювання сітки на полотні, коли необхідно одночасно використовувати й кольорове полотно, і координатну сітку.
function myDraw(){
// початок коду для сітки і тла полотна
background(70, 77, 119); // YInMn Blue
t.grid(30, "rgba(43, 41, 70, 0.5)", "#ff9800"); // Space cadet, Orange peel
// кінець коду для сітки і тла полотна
push();
translate(width / 2, height / 2);
scale(1, -1);
// початок коду для фігур
let [x, y] = t.getPosition();
stroke(208, 85, 163); // Mulberry
fill(255, 0);
circle(int(x), int(y), 100);
// кінець коду для фігур
pop();
}
Завдяки тому, що виклик функції background()
у тілі функції myDraw()
розміщений найпершим, зафарбовування полотна не буде впливати на результати викликів інших функцій для малювання фігур.
C.4.5. Багато Черепашок
Для створення двох (або більше) Черепашок, необхідно оголосити їхні імена та створити їх із цими іменами:
let t1, t2;
function setup() {
createCanvas(windowWidth, windowHeight);
myCode();
}
function myCode(){
t1 = new Turtle();
t2 = new Turtle();
}
Відображення на полотні створених Черепашок відбувається за допомогою виклику інструкцій place()
для кожної із них у блоці draw()
:
function draw() {
t1.place();
t2.place();
}
Відповідно інструкції для різних Черепашок записуються у форматі t1.інструкція
, t2.інструкція
і т. д.
У режимі кількох Черепашок інформаційна панель і компас показують дані для одного екземпляра Черепашки, для якого вони викликаються. |
C.4.6. Зміна розмірів вікна полотна/вебпереглядача
При зміні розмірів вікна полотна/вебпереглядача усі елементи інтерфейсу залишаються на своїх місцях і з’являються смуги прокручування. Щоб ці елементи налаштувалася відповідно до нових розмірів, необхідно оновити вебсторінку. За потреби, перед цим збережіть свій код.
Якщо ви працюєте з інтерактивною мапою, спочатку встановіть розміри вікна вебпереглядача, а потім оновіть вебсторінку, щоб елементи інтерфейсу налаштувались відповідно до нових розмірів.
C.5. Інструкції
C.5.1. Рух
Рух Черепашки відбувається без анімації, тобто виконуються усі записані інструкції і Черепашка відразу розташовується на полотні у точці з кінцевими координатами. |
Після запуску Черепашка з’являється на полотні:
-
у точці з координатами
(0, 0)
(центр полотна); -
дивиться праворуч;
-
має початкову кутову орієнтацію, що вимірюється у градусах,
0°
.
Інструкція |
Опис |
|
Перемістити Черепашку вперед на відстань |
|
Перемістити Черепашку назад на відстань |
|
Перемістити Черепашку в точку з координатами |
|
Перемістити Черепашку в точку з координатами |
|
Повернути Черепашку ліворуч на кут |
|
Повернути Черепашку праворуч на кут |
|
Встановити кутову орієнтацію Черепашки на кут |
C.5.2. Черепашка
Інструкція |
Опис |
|
Отримати поточні координати Черепашки у вигляді списку |
|
Отримати поточну кутову орієнтацію Черепашки. |
|
Встановити форму |
|
Встановити розмір |
|
Зробити Черепашку видимою. |
|
Зробити Черепашку невидимою. |
|
Очистити полотно. Черепашка залишається у поточній точці, її кутова орієнтація зберігається. |
C.5.3. Олівець
Якщо олівець на полотні, при переміщенні Черепашка малюватиме лінію. |
Інструкція |
Опис |
|
Підняти олівець. |
|
Опустити олівець на полотно. За стандартним налаштуванням олівець на полотні. |
|
Встановити у пікселях товщину лінії олівця на |
C.5.4. Колір
Значення кольору записується як рядок в одному із форматів: "red" (назва), "#fdfd90" (шістнадцяткове значення), "rgb(11, 156, 78)" (значення червоної, зеленої, синьої складових), "rgba(45, 145, 67, 0.5)" (значення червоної, зеленої, синьої складових і прозорості).
|
Інструкція |
Опис |
|
Встановити колір |
|
Встановити колір |
|
Встановити колір заливки |
|
Увімкнути зафарбовування фігури поточним кольором заливки. |
|
Вимкнути зафарбовування фігури поточним кольором заливки. |
C.5.5. Фігури
Інструкція |
Опис |
|
Намалювати коло чи еліпс. Якщо параметри |
|
Намалювати багатокутник за координатами вершин |
C.5.6. Текст
Інструкція |
Опис |
|
Написати текст Об’єкт Об’єкт |
За стандартним налаштуванням текст є порожнім рядком і має такі параметри: вирівнювання {horizontal: CENTER, vertical: CENTER} , шрифту {font: "sans-serif", size: 12, style: NORMAL} .
|
C.6. Розробка
Для розробки середовища JavaScript Turtle Graphics Library
використовувались мова програмування JavaScript
та інструменти бібліотеки p5.js
.
Розроблено з ❤️. Автор: Олександр Мізюк. |
C.7. Початковий код бібліотеки TurtleGL.js
Початковий код бібліотеки TurtleGL.js
зберігається в єдиному файлі, який можна завантажити тут .
C.8. Ліцензія
Використання бібліотеки TurtleGL.js
визначається умовами ліцензії GNU General Public License (GPL) version 3
.
Код бібліотеки TurtleGL.js можна вільно і безплатно копіювати, розповсюджувати й змінювати на свій задум.
|