Робота з Canvas
Canvas - контейнер для програмної реалізації графіки на веб-сторінці.
На відміну від формату SVG графіка на canvas виконується лише програмно, за допомогою JS.
На canvas можна реалізувати як 2D, так і 3D-графіку, можна включити підтримку OpenGL (WebGL) і навіть створювати ігри.
Основні примітиви
Вставте на веб-сторінку тег <canvas>:
<canvas></canvas>
Всередину можна вставити контент для старих браузерів (хоча наразі це навряд чи актуально):
canvas { width: 400px; height: 300px; border: 1px solid #aaa;}
<canvas id="img1">
<img src="canvas_error.png" alt="Canvas error">
Ваш браузер не підтримує роботу з динамічною графікою.
</canvas>
<canvas id="img2">
<!-- Статична картинка, графік, гіфка, відео, SVG ... -->
</canvas>
Для початку малювання потрібно взяти контекст полотна, тобто, вказати - по чому ми малюємо. Полотно - контейнер для малювання всередині тега canvas.
По-замовчуванню розмір полотна 300 x 150 px, і малюнок на ньому буде маштабуватися до розміру canvas. Тому бажано задати розмір полотна.
Цим способом довільне малювання по віконному додатку відбувається у більшості мов програмування: C++, Java, Delphi і т.п. (використовується API операційної системи).
let canvas = document.querySelector('canvas');
let ctx = canvas.getContext('2d');
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
Раджу використовувати загальноприйняту назву змінної ctx (скорочення від context).
Якщо потрібна підтримка старих браузерів - можна виконати перевірку:
let ctx = null;
if (canvas.getContext){
ctx = canvas.getContext('2d');
} else {
// браузер не підтримує canvas
}
Далі - алгоритм малювання примітивів.
Уявіть себе художником. Алгоритм дій:
1. берете в руку червоний олівець;
2. зафарбовуєте фон;
3. берете в руку синій олівець;
4. ставите його в точку [3см, 5см];
5. малюєте лінію до точки [8см, 7см];
6. ...
Подібним чином працює і алгоритм малювання по canvas:
ctx.fillStyle = "red"; // color
ctx.fillRect(10, 10, 200, 150); // x, y, width, height
ctx.fillStyle = "rgba(0,200,0,0.5)";
ctx.ellipse(140, 100, 50, 80);
ctx.fill();
ctx.strokeRect(50, 50, 200, 150); // x, y, width, height
ctx.clearRect(80, 80, 30, 20); // x, y, width, height
ctx.fillStyle = "rgb(128,128,256)";
ctx.beginPath();
ctx.moveTo(140, 120);
ctx.lineTo(260, 160);
ctx.lineTo(160, 200);
ctx.closePath();
ctx.fill();
ctx.arc(50, 50, 40, 0, Math.PI * 2); // cx, cy, r, aStart, aFinish
ctx.arc(50, 50, 40, 0, (Math.PI / 180) * 45, true);
// cx, cy, r, aStart, aFinish, anticlockwise
// ctx.rect(x, y, width, height);
// ctx.quadraticCurveTo(x1, y1, x, y);
// ctx.bezierCurveTo(x1, y1, x2, y2, x, y);
// ctx.arcTo()
Пікселі і проценти
В багатьох випадках зручніше використовувати не пікселі, а проценти: потрібно для адаптивності, маштабування і т.п.
Як правило, за 100% беруть більший розмір блока, екрана.
Примітиви
Задаємо колір лінії та колір зафарбовування:
ctx.strokeStyle = "green";
ctx.fillStyle = "red";
// інші формати кольору:
ctx.fillStyle = "#f00";
ctx.fillStyle = "rgb(255,0,0)";
ctx.fillStyle = "rgba(255,0,0,1)";
Задаємо тип ліній:
ctx.lineWidth = 10;
ctx.lineCap = 'round';
ctx.lineJoin = 'miter';
ctx.setLineDash([5, 15, 3, 10]);
Прямокутники (рамка, зафарбований, очистка області):
ctx.strokeRect(50,50,50,50);
ctx.fillRect(25,25,100,100);
ctx.clearRect(45,45,60,60);
Текст:
ctx.font = "italic 30px Arial";
ctx.fillStyle = "red";
ctx.fillText("Звичайний текст", 20, 50);
ctx.strokeStyle = "green";
ctx.strokeText("Текст у вигляді обводки", 20, 100);
Вставимо растрове зображення:
var cat = new Image();
cat.addEventListener("load", function(){
ctx.drawImage(cat, 50, 50); // image, x, y
ctx.drawImage(cat, 150, 150, 200, 120); // image, x, y, scaleX, scaleY
}, false);
cat.src = 'images/cat.jpg';
Path.
ctx.beginPath();
ctx.moveTo(30, 20);
ctx.lineTo(100, 80);
ctx.lineTo(150, 30);
ctx.closePath();
ctx.strokeStyle = "red";
ctx.stroke();
Path як об'єкт.
var p = new Path2D("M 15 30 h 50 v 80 h -20 z");
var r = Path2D(p); // copy from previous object p
var rec = new Path2D();
rec.rect(10, 10, 50, 50);
var crc = new Path2D();
// arc(x, y, radius, startAngle, endAngle, anticlockwise)
crc.arc(100, 35, 25, 0, Math.PI * 2);
ctx.stroke(rec);
ctx.strokeStyle = "rgb(100,200,255)";
ctx.stroke(p);
// ctx.arcTo(x1, y1, x2, y2, radius)
// ctx.quadraticCurveTo(x1, y1, x2, y2)
// ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3)
Стек станів, збереження та відновлення станів.
Під час малювання можна використовувати стек станів - зберігати та відновлювати стани (кольори, прозорість, тип ліній і т.п.).
Кожне збереження записує стан у стек, кожне відновлення стану зчитує стан з кінця стеку (останній запис).
function sets(fill, stroke, width, alpha){
ctx.fillStyle = fill;
ctx.strokeStyle = stroke;
ctx.lineWidth = width;
ctx.globalAlpha = alpha;
}
function drawRect(x){
ctx.beginPath();
ctx.rect(x, x / 2, 100, 100);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
ctx.fillStyle = "red";
ctx.save();
drawRect(10);
sets('green', 'navy', 3, 0.3);
ctx.save();
drawRect(60);
sets('blue', 'orange', 10, 0.8);
drawRect(110);
ctx.restore();
drawRect(160);
ctx.restore();
drawRect(210);
Трансформації
Функція translate умовно зсуває координати полотна на задані значення.
Прямокутник буде намальовано не в нульових координатах, а в [100, 100]:
ctx.translate(100, 100); // x, y
ctx.fillRect(0, 0, 50, 50);
Обертання відбувається відносно нульової точки координат, обертається також все полотно:
ctx.fillStyle = "blue";
ctx.fillRect(30, 20, 80, 50);
ctx.rotate((Math.PI/180)*30);
ctx.fillStyle = "red";
ctx.fillRect(30, 20, 80, 50);
Щоб фігура прокрутилася довкола свого центру - зсунемо канву на початок координат прямокутника і ще на половину його розмірів:
ctx.fillStyle = "blue";
ctx.fillRect(30, 20, 80, 50);
ctx.translate(70, 45); // 30 + 80/2, 20 + 50/2
ctx.rotate((Math.PI/180)*30);
ctx.fillStyle = "red";
ctx.fillRect(30, 20, 80, 50);
ctx.translate(-70, -45);
В кінці оберту не забудьте повернути канву на початкове місце.
Приклад маштабування. Маштабуванням можна віддзеркалювати об'єкти по горизонталі, вертикалі чи одночасно по обом вісям, задаючи від'ємні значення.
ctx.fillStyle = "blue";
ctx.fillRect(10, 10, 50, 50);
ctx.fillStyle = "red";
ctx.globalAlpha = 0.5;
ctx.scale(2, 3);
ctx.fillRect(10, 10, 50, 50);
ctx.scale(-1, -1);
ctx.fillText("Hello", -100, -100);
Градієнтна заливка
Градієнт створюється у вигляді окремого об'єкта. До створеного об'єкта додаються стоп-точки.
На відміну від SVG-формату, canvas-градієнт створюється на саме полотно, а не на фігури:
var grad = ctx.createLinearGradient(20, 20, 150, 100);
grad.addColorStop(0, 'red');
grad.addColorStop(0.5, 'green');
grad.addColorStop(1, '#00f');
ctx.fillStyle = grad;
ctx.fillRect(40, 40, 100, 100);
Зручно буде створити функцію для створення градієнтів:
ctx.linearGrad = function(x1, y1, x2, y2, stops){
var grad = this.createLinearGradient(x1, y1, x2, y2);
for (var i = 0; i < stops.length; i++){
grad.addColorStop(stops[i][0], stops[i],[1]);
}
this.fillStyle = grad;
}
ctx.linearGrad(20, 20, 150, 100, [
[0, 'red'],
[0.5, 'green'],
[1, '#00f']
]);
ctx.fillRect(40, 40, 100, 100);
Радіальний градієнт задається дещо складніше:
// context.createRadialGradient(x0, y0, r0, x1, y1, r1);
var rgrad = ctx.createRadialGradient(20, 20, 10, 20, 20, 100);
rgrad.addColorStop(0, 'yellow');
rgrad.addColorStop(1, 'blue');
ctx.fillStyle = rgrad;
ctx.fillRect(0, 0, 300, 200);
Анімація
На відміну від CSS- та SVG-анімації, анімація canvas відбувається покадрово.
Створюється кадр анімації, відображається на екрані певний час, затирається і малюється наступний кадр.
Проміжок часу між кадрами можна задати вручну за допомогою таймінгових функцій, або задати автоматичне перемальовування за допомогою функції window.requestAnimationFrame(callback).
В останньому випадку розрахунки координат елементів зазвичай розраховують в залежності від поточного часу.
Анімація реалізується досить складними алгоритмами, приклад алгоритмів можна дослідити на сторінці https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations.
Практика
Бізнес-графіка:
-
Згенеруйте 7 випадкових чисел в межах [5..100].
Напишіть юніт-тест, щоб визначити, чи не генеруються числа менше 5 чи більше 100.
За даними точками побудуйте графік на canvas.
Графік повинен бути респонсивним. - Додайте hover-ефект: при наведенні на точку графіка відобразіть над нею числове значення.
- Зробіть анімацію: при завантаженні сторінки лінія графіка на 0, за 1 секунду точки підіймаються вгору на свої місця.
- Анімація повинна спрацьовувати при завантаженні сторінки, але якщо графік не повністю видно на екрані - то при проскролі до нього.
- Дано 5 значень: [8, 14, 17, 22, 31]. Побудуйте кругову діаграму.
- Побудуйте кільцеву діаграму використовуючи дані з попереднього завдання.
Ефекти, розваги:
- Використайте невелике зображення котика.
- При наведенні на нього мишки застосуйте до нього "фільтр портретів Fallout".
- При кліку на кнопку завантажте ще одне зображення такого ж розміру і об'єднайте два зображення.
- Зробіть, щоб перехід від одного зображення до другого був плавний, зліва направо.
- При кліку на котика зробіть анімацію вибуха за допомогою спрайта.
- Намалюйте декілька примітивів довільної форми. При наведенні на примітив у нього повинен з'являтися контур, а курсор повинен приймати вигляд "pointer".
- Намалюйте "дорогу" - декілька послідовних ліній безьє. Намалюйте машинку примітивами і зробіть, щоб вона їхала по дорозі (колеса мають торкатися дороги). Опціонально: на спусках машинка має пришвидшуватися, на підйомах - сповільнюаватися.
- Зробіть щоб довкола мишки весь час кружляв рій точок з ефектом інерції.