## Графика в браузере
Слайды доступны по ссылке
frontend.tech-mail.ru

## Графика в браузере

- HTML + CSS
- SVG (Scalable Vector Graphics) графика — можно встраивать как часть документа
- Картинки (`<img src="image.png">`)
- Медиа объекты (`<video src="video.mp4"></video>`)
- canvas

## Элемент `<canvas>`

> Элемент `<canvas>` (англ. canvas — «холст», рус. канва́с) — это HTML5 элемент, предназначенный для создания растрового двухмерного изображения на web-страницах с помощью программирования на JavaScript

Подробный туториал по canvas — на MDN

`<canvas id="canvas" width="300" height="150"></canvas>`

## Контекст рендеринга

    		const canvas = document.createElement( 'canvas' );
    		document.appendChild(canvas);

    		// Объект двумерного контекста
    		const context = canvas.getContext( '2d' );
    		// Объект WebGL (3D) контекста
    		const webgl = canvas.getContext( 'webgl' );

## Рисование примитивов

## Система координат

- `По умолчанию:`
- Начало координат — точка `(0;0)` в верхнем левом углу
- Ось Х — вправо
- Ось Y — вниз
- Систему координат можно и нужно менять для удобства

## Рисование примитивов:
прямоугольник

    		// обводит прямоугольную область
    		ctx.strokeRect(x, y, width, height);
    		// заливает прямоугольную область
    		ctx.fillRect(x, y, width, height);
    		// очищает прямоугольную область
    		ctx.clearRect(x, y, width, height);

## Рисование примитивов:
прямоугольник

    		// толстая рамка
    		ctx.fillRect(25, 25, 250, 250);
    		ctx.clearRect(100, 100, 100, 100);

    		// квадраты в центре
    		ctx.strokeRect(110, 110, 80, 80);
    		ctx.strokeRect(120, 120, 60, 60);
    		ctx.strokeRect(130, 130, 40, 40);
    		ctx.strokeRect(140, 140, 20, 20);

## Рисование примитивов: контуры

`Контур` — точки, соединенные линиями разной формы

Алгоритм создания фигур с помощью контуров:

1. Создать контур
2. Использовать методы для рисования в созданном контуре
3. Отрендерить нарисованный контур

## Рисование примитивов: контуры

    		ctx.beginPath();     // создаёт новый контур

    		ctx.moveTo(x1, y1);  // передвигает перо в нужную точку
    		ctx.lineTo(x2, y2);  // проводит линию до другой точки

    		ctx.stroke();        // обводит созданный контур
    		ctx.fill();          // заливает область, обведённую контуром
    		                     // "nonzero" or "evenodd"

    		ctx.closePath();     // закрывает контур

## Рисование примитивов: контуры

    		// верхний треугольник
    		ctx.beginPath();
    		ctx.moveTo(50, 50);
    		ctx.lineTo(50, 200); ctx.lineTo(200, 50); ctx.lineTo(50, 50);
    		ctx.closePath();
    		ctx.fill();

    		// нижний треугольник
    		ctx.beginPath();
    		ctx.moveTo(250, 250);
    		ctx.lineTo(250, 100); ctx.lineTo(100, 250); ctx.lineTo(250, 250);
    		ctx.closePath();
    		ctx.stroke();

## Рисование примитивов: дуги

    		// создаёт путь-дугу
    		ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
    		// x, y - центр дуги
    		// radius - радиус дуги (в радианах)
    		// startAngle - начальный угол
    		// endAngle - конечный угол
    		// anticlockwise - проводить против часовой стрелки

    		radians = (Math.PI/180) * degrees

## Рисование примитивов: дуги

    		ctx.beginPath();
    		ctx.arc(100, 100, 50, -Math.PI, 0);
    		ctx.closePath(); ctx.fill();

    		ctx.beginPath();
    		ctx.arc(100, 110, 40, 0, Math.PI);
    		ctx.closePath(); ctx.fill();

    		ctx.beginPath();
    		ctx.arc(200, 200, 50, -Math.PI, 0);
    		ctx.arc(220, 200, 30, 0, Math.PI);
    		ctx.closePath(); ctx.stroke();

## Рисование примитивов:
кривые-Безье

    		// квадратичная кривая
    		ctx.quadraticCurveTo(cp1x, cp1y, x, y);
    		// кубическая кривая
    		ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
    		// cpx, cpy - опорные точки
    		// x, y - куда вести

## Рисование примитивов:
кривые-Безье

    		ctx.beginPath();
    		ctx.moveTo(75, 25);

    		ctx.quadraticCurveTo(25, 25, 25, 62.5);
    		ctx.quadraticCurveTo(25, 100, 50, 100);
    		ctx.quadraticCurveTo(50, 120, 30, 125);
    		ctx.quadraticCurveTo(60, 120, 65, 100);
    		ctx.quadraticCurveTo(25, 25, 25, 62.5);
    		ctx.quadraticCurveTo(125, 100, 125, 62.5);
    		ctx.quadraticCurveTo(125, 25, 75, 25);

    		ctx.stroke();

## Рисование примитивов:
прямоугольные пути

    		// добавляет прямоугольный путь к текущему пути (не рисует)
    		ctx.rect(x, y, width, height);
    		// x, y - координаты левого верхнего угла

## Рисование примитивов: текст

    		ctx.fillText(text, x, y, maxWidth);
    		ctx.strokeText(text, x, y, maxWidth);

    		// text - текст
    		// x, y - координаты точки начала
    		// maxWidth - опциональное ограничение по ширине

## Рисование примитивов: Path2D

    		new Path2D('M10 10 h 80 v 80 h -80 Z');
    		// - создать из SVG

    		Path2D.addPath(path [, transform])
    		// path - добавить путь

    		const rectangle = new Path2D();
    		rectangle.rect(10, 10, 50, 50);
    		ctx.stroke(rectangle);

## Стилизация

## Стилизация линий

    		// ширина линий
    		ctx.lineWidth = value;

    		// пунктир
    		ctx.setLineDash(segments);  // шаблон (массив чисел)
    		ctx.getLineDash();          // получить текущий шаблон
    		ctx.lineDashOffset = value; // установить смещение (число)

## Стилизация линий

    			ctx.beginPath();
    			[1, 5, 10, 20, 50].forEach((v, i) => {
    			    ctx.beginPath();
    			    ctx.lineWidth = v; ctx.setLineDash([5, 5, 50, 5, 5, v]);
    			    ctx.moveTo(20, 30 + 55 * i); ctx.lineTo(280, 30 + 55 * i);
    			    ctx.stroke();
    			});

## Стилизация линий

    		// окончание линий
    		ctx.lineCap = 'butt';    // обрезанные концы
    		ctx.lineCap = 'round';   // скруглённые концы
    		ctx.lineCap = 'square';  // квадраты

    		// оформление углов
    		ctx.lineJoin = 'bevel';  // срезанные углы
    		ctx.lineJoin = 'round';  // скруглённые углы
    		ctx.lineJoin = 'miter';  // острые углы

## Стилизация линий

    		const lineJoin = ['round', 'bevel', 'miter'];
    		ctx.lineWidth = 30;
    		lineJoin.forEach((v, i) => {
    		    ctx.lineJoin = v; ctx.beginPath();
    		    ctx.moveTo(70, 110 + i * 80);
    		    ctx.lineTo(150, 30 + i * 80);
    		    ctx.lineTo(230, 110 + i * 80);
    		    ctx.stroke();
    		});

## Стилизация линий

    		ctx.lineWidth = 20;
    		const lineCap = ['butt', 'round', 'square'];
    		// Рисуем линии
    		lineCap.forEach((v, i) => {
    		    ctx.lineCap = v; ctx.beginPath();
    		    ctx.moveTo(50 + i * 100, 20);
    		    ctx.lineTo(50 + i * 100, 280);
    		    ctx.stroke();
    		});

## Использование цветов

    		ctx.fillStyle = color;   // задаёт стиль заливки
    		ctx.strokeStyle = color; // задаёт стиль обводки
    		ctx.globalAlpha = value; // задаёт уровень прозрачности (0.0 .. 1.0)

    		ctx.fillStyle = 'orange';
    		ctx.fillStyle = '#FFA500';
    		ctx.fillStyle = 'rgb(255, 165, 0)';
    		ctx.fillStyle = 'rgba(255, 165, 0, 1)';

## Использование цветов

    		ctx.fillStyle = 'yellow';        ctx.fillRect(0, 0, 150, 150);
    		ctx.fillStyle = '#6C0';          ctx.fillRect(150, 0, 150, 150);
    		ctx.fillStyle = '#0099FF';       ctx.fillRect(0, 150, 150, 150);
    		ctx.fillStyle = 'rgb(255,48,0)'; ctx.fillRect(150, 150, 150, 150);
    		ctx.fillStyle = 'white';

    		for (let i = 0; i < 7; i++) {
    		    ctx.beginPath();
    		    ctx.fillStyle = 'rgba(255,255,255,' + (i + 1) / 12 + ')';
    		    ctx.arc(150, 150, 10 + 20 * i, 0, Math.PI * 2, true);
    		    ctx.fill();
    		}

## Использование градиентов

    		// создание линейного градиента
    		let gradient = ctx.createLinearGradient(x1, y1, x2, y2);
    		// создание радиального градиента
    		gradient = ctx.createRadialGradient(x1, y1, r1, x2, y2, r2);
    		// добавление контрольной точки
    		gradient.addColorStop(position, color);

    		ctx.fillStyle = gradient;
    		ctx.strokeStyle = gradient;

## Использование градиентов

    		const linear = ctx.createLinearGradient(0, 150, 0, 0);
    		const radial = ctx.createRadialGradient(150, 150, 0, 150, 150, 151);
    		[linear, radial].forEach(grad => {
    		    grad.addColorStop(0, 'yellow');
    		    grad.addColorStop(0.5, 'green');
    		    grad.addColorStop(0.99, 'blue');
    		    grad.addColorStop(1, 'white');
    		});

    		ctx.fillStyle = linear;
    		ctx.fillRect(0, 0, 300, 150);
    		ctx.fillStyle = radial;
    		ctx.fillRect(0, 150, 300, 150);

## Прочее

    		// создание паттерна
    		let pattern = ctx.createPattern(image, type);

    		// тени
    		ctx.shadowOffsetX = float;
    		ctx.shadowOffsetY = float;
    		ctx.shadowBlur = float;
    		ctx.shadowColor = color;

## Трансформации

## Сохранение данных

    		// сохранить все настройки, в том числе трансформации!
    		ctx.save();

    		// вернуть предыдущие из стека
    		ctx.restore();

## Простые трансформации

    		// перенос точки отсчёта
    		ctx.translate(x, y);

    		// поворот осей
    		ctx.rotate(angle);

    		// масштабирование по осям
    		ctx.scale(x, y);

## Комплексные трансформации

    		// перенос точки отсчёта
    		ctx.transform(a, b, c, d, e, f);
    		// a - горизонтальный масштаб
    		// b - горизонтальный скос
    		// c - вертикальный скос
    		// d - вертикальный масштаб
    		// e - горизонтальное смещение
    		// f - вертикальное смещение

## Комплексные трансформации

    		// сброс текущей трансформации
    		ctx.resetTransform();

    		// сброс текущей и установка новой трансформации
    		ctx.setTransform(a, b, c, d, e, f);

## А так же

- Работа с изображениями: `ctx.drawImage` для img, svg, video, canvas
- Режимы наложения и обрезка
- Работа с пикселями: `ctx.getImageData`
- ...

## Практика!

## Анимации

## Алгоритм работы с анимациями

- Рисуем кадр
- Стираем
- Рисуем новый кадр
- Стираем
- ???????
- PROFIT

ПРИМЕР

## Правильный алгоритм работы
с анимациями

- Рисуем кадр
- Ждём
- Стираем
- Рисуем новый кадр
- Ждём
- Стираем
- ...

ПРИМЕР

## Как ждать?

- `window.sleep(...)` — нет никакого window.sleep, но есть другие крутые функции
- `window.setInterval(function, delay)`
- `window.setTimeout(function, delay)`
- `window.requestAnimationFrame(callback)`

## `setInterval`

    		function animation() {
    		    redraw(); // перерисовываем кадр
    		}

    		const id = window.setInterval(animation, 1000 / 60); // 60 fps

    		// хотим прервать анимацию
    		window.clearInterval(id);

## `setTimeout`

    		let id = null;

    		function animation() {
    		    redraw(); // перерисовываем кадр
    		    id = window.setTimeout(animation, 1000 / 60); // 60 fps
    		}

    		animation();

    		// хотим прервать анимацию
    		window.clearTimeout(id);

## `requestAnimationFrame`

    		let animationFrameId = null;

    		function animation() {
    		    redraw(); // перерисовываем кадр
    		    animationFrameId = window.requestAnimationFrame(animation);
    		}

    		animation();

    		// хотим прервать анимацию
    		window.cancelAnimationFrame(animationFrameId)

## PRO TIPs хорошей анимации

- Замеряйте время, проходящее между двумя последовательными кадрами (`dt`)
- Используйте полученное значение для рассчёта изменений между двумя кадрами (`dx = Vx * dt`)
- ...

## `requestAnimationFrame`

    		let last = perfomance.now(); // точное время типа DOMHighResTimeStamp

    		function animation(now) { // передаётся текущее perfomance.now()
    		    const delay = now - last;
    		    last = now;
    		    redraw(delay); // перерисовываем кадр

    		    window.requestAnimationFrame(animation);
    		}

    		animation(perfomance.now());

## Оптимизации производительности

- Оптимизация анимации с помощью `requestAnimationFrame`
- Рендеринг только разницы, а не нового состояния целиком
- Предварительный рендеринг во внеэкранный элемент `canvas`
- Группировка вызовов `canvas`
- Используйте многослойные элементы `canvas` для сложных сцен
- Используйте CSS для фонового изображения и анимаций, HTML для элементов интерфейса
- Отказ от координат с плавающей запятой
- Отказ от рендеринга текста, больших изображений с `canvas`
- Отказ от использования "тяжёлых" свойств: например, `shadowBlur`
- ...

## "Пишем физику на JavaScript" open_in_new
доклад с конференции

## Практика!

## WAAPI, JS Worklets

## Web Animations API (WAAPI)

> WAAPI — браузерное API, позволяющее разработчикам управлять движком анимаций с помощью JavaScript.

Подробно про WAAPI — на MDN

## Animation Worklets

> Worklet интерфейс — это легковесная версия Web Worker-ов, которая дает разработчикам доступ к управлению процессом рендеринга на низком уровне. С его помощью можно исполнять JavaScript и WebAssembly код для высокопроизводительного рендеринга графики.

Подробно про интерфейс Worklet — на MDN

Статья про использование Animation Worklet-ов

## WebGl

> WebGL (Web Graphics Library) — программная библиотека для языка JavaScript предназначенная для визуализации интерактивной трехмерной графики и двухмерной графики в пределах совместимости веб-браузера без использования плагинов. WebGL приносит в веб трехмерную графику, вводя API, который построен на основе OpenGL ES 2.0 , что позволяет его использовать в элементах `canvas` HTML5

## Преимущества WebGl

- Производительность за счет GPU
- Отсутствие компиляции
- Кроссплатформенность
- Автоматическое управление памятью
- Открытый стандарт

## Шейдеры

> Шейдеры — программы, которые работают на GPU. Шейдеры пишутся на специальном языке: OpenGL ES Shader Language (известный как ES SL). ES SL имеет переменные своих собственных типов данных и свои специфические встроенные функции. В свою очередь ES SL основан на C++. ES SL также называют GLSL, что означает Graphics Library Shader Language (язык программирования шейдеров графической библиотеки)

## Практическое применение WebGL

- Визуализации растровой графики
- Редактирование и анализ изображений
- Фильтрация видео
- Игры
- Интерактивая графика
- Статистика в виде графиков

## Примеры использования шейдеров

- madebyevan.com
- webgl_shaders_ocean
- webgl_shaders_ocean2
- webgl_shaders_sky
- bongiovi.tw
- cubeslam
- asmallgame
- TheCar

## Где брать вдохновение

- awwwards.com

## Перерыв

## Frontend Oops
Ссылка на презентацию Frontend Oops.pdf