Графика и FrontendOops (Инфраструктура разработки)

Технопарк, весна, 2024 г.

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

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

Элемент <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' );
		 
	

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

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

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

		// обводит прямоугольную область
		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);
		 
	

А так же

Практика!

Анимации

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

ПРИМЕР

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

ПРИМЕР

Как ждать?

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 хорошей анимации

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());
		 
	

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

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

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

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

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

Перерыв

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