Учебник по Electron.js

Рисование на холсте

Тег <canvas>

Тег <canvas> описывает холст на котором мы можем рисовать. Он имеет следующие параметры:

Создадим холст, укажем его размеры и зальем зеленым цветом:

<canvas id="canvas" width="400" height="300"></canvas>
<script>
   let canvas = document.getElementById('canvas');
   let ctx = canvas.getContext('2d');
   ctx.fillStyle = 'green';
   ctx.fillRect(0, 0, canvas.width, canvas.height);
</script>

Создание контекста рисования

После создания холста нужно получить его контекст рисования. Для этого используется метод getContext(), которому нужно передать значение '2d':

let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');

В результате переменная ctx будет ссылаться на экземпляр класса CanvasRenderingContext2D, который содержит свойства и методы, позволяющие рисовать на холсте.

Получить ссылку на объект холста позволяет свойство canvas. Получим размеры холста:

console.log(ctx.canvas.width);  // 400
console.log(ctx.canvas.height); // 300

Изменение характеристик заливки

Управлять цветом заливки позволяет свойство fillStyle. Можно присвоить следующие значения:

let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ff0000';
ctx.fillRect(10, 10, 30, 30);
ctx.fillStyle = 'rgb(127, 127, 127)';
ctx.fillRect(50, 10, 30, 30);
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(90, 10, 30, 30);
let lg = ctx.createLinearGradient(0, 0, 40, 0);
lg.addColorStop(0, 'black');
lg.addColorStop(1, 'white');
ctx.fillStyle = lg;
ctx.fillRect(10, 50, 30, 30);
let rg = ctx.createRadialGradient(110, 110, 30, 150, 150, 170);
rg.addColorStop(0, 'black');
rg.addColorStop(1, 'white');
ctx.fillStyle = rg;
ctx.fillRect(50, 50, 30, 30);

Если характеристики заливки не заданы, то используется черный цвет.

Изменение характеристик обводки

Управлять цветом обводки позволяет свойство strokeStyle. Можно присвоить следующие значения:

let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
ctx.strokeStyle = 'green';
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#ff0000';
ctx.strokeRect(10, 10, 30, 30);
ctx.strokeStyle = 'rgb(127, 127, 127)';
ctx.strokeRect(50, 10, 30, 30);
ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)';
ctx.strokeRect(90, 10, 30, 30);

Если цвет обводки не задан, то используется черный цвет.

Дополнительные характеристики задаются с помощью следующих свойств:

ctx.strokeStyle = '#000000';
ctx.lineWidth = 5;
ctx.strokeRect(10, 90, 30, 30);

Пример:

ctx.beginPath();
ctx.strokeStyle = '#000000';
ctx.lineWidth = 15;
ctx.lineCap = 'round';
ctx.moveTo(150, 30);
ctx.lineTo(350, 30);
ctx.stroke();

Пример:

ctx.beginPath();
ctx.strokeStyle = '#000000';
ctx.lineWidth = 15;
ctx.lineJoin = 'bevel';
ctx.moveTo(270, 150);
ctx.lineTo(170, 200);
ctx.lineTo(270, 200);
ctx.stroke();

Следующие методы и свойства делают линию пунктирной:

ctx.beginPath();
ctx.strokeStyle = '#000000';
ctx.lineWidth = 8;
ctx.setLineDash([15, 10]);
ctx.moveTo(10, 250);
ctx.lineTo(390, 250);
ctx.stroke();

Чтобы сделать следующую линию опять сплошной достаточно передать в метод пустой массив;

Заливка градиентом

Итак, свойства fillStyle и strokeStyle в качестве значения могут принимать объекты линейного или радиального градиентов. Давайте научимся создавать эти объекты.

Линейный градиент

Создать объект линейного градиента позволяет метод createLinearGradient(). Формат метода:

<CanvasGradient> = createLinearGradient(<X1>, <Y1>, <X2>, <Y2>)

Далее с помощью метода addColorStop() объекта CanvasGradient нужно задать точки останова и соответствующие им цвета. Формат метода:

<CanvasGradient>.addColorStop(<Точка останова>, <Цвет>)

В первом параметре указывается значение от 0.0 до 1.0. Во втором параметре задается цвет. Пример создания линейного градиента от черного до белого цветов по горизонтали:

<canvas id="canvas" width="400" height="300"></canvas>
<script>
   let canvas = document.getElementById('canvas');
   let ctx = canvas.getContext('2d');
   let lg = ctx.createLinearGradient(0, 0, 400, 0);
   lg.addColorStop(0, 'black');
   lg.addColorStop(1, 'white');
   ctx.fillStyle = lg;
   ctx.fillRect(0, 0, canvas.width, canvas.height);
</script>

Радиальный градиент

Создать объект радиального градиента позволяет метод createRadialGradient(). Формат метода:

<CanvasGradient> = createRadialGradient(<X1>, <Y1>, <R1>,
                                        <X2>, <Y2>, <R2>)

Первые три параметра определяют координаты и радиус внутреннего круга, а последние три — координаты и радиус внешнего круга.

Далее с помощью метода addColorStop() объекта CanvasGradient нужно задать точки останова и соответствующие им цвета. Формат метода:

<CanvasGradient>.addColorStop(<Точка останова>, <Цвет>)

В первом параметре указывается значение от 0.0 (внутренний круг) до 1.0 (внешний круг). Во втором параметре задается цвет. Пример создания радиального градиента от черного до белого цветов:

<canvas id="canvas" width="400" height="300"></canvas>
<script>
   let canvas = document.getElementById('canvas');
   let ctx = canvas.getContext('2d');
   let rg = ctx.createRadialGradient(110, 110, 30, 150, 150, 150);
   rg.addColorStop(0, 'black');
   rg.addColorStop(1, 'white');
   ctx.fillStyle = rg;
   ctx.fillRect(0, 0, canvas.width, canvas.height);
</script>

Заливка текстурой

Создать объект текстуры позволяет метод createPattern(). Формат метода:

<Объект текстуры> = createPattern(<Изображение>, <Режим повтора>)

В первом параметре указывается объект изображения, например, ссылка на элемент IMG или экземпляр класса Image. Во втором параметре задается режим повтора изображения в виде строки:

Пример:

<canvas id="canvas" width="800" height="800"></canvas>
<img src="texture.jpg" alt="" id="img1">
<script>
   window.onload = function() {
      let canvas = document.getElementById('canvas');
      let ctx = canvas.getContext('2d');
      let img = document.getElementById('img1');
      ctx.fillStyle = ctx.createPattern(img, 'repeat');
      ctx.fillRect(0, 0, canvas.width, canvas.height);
   };
</script>

Если используется экземпляр класса Image, то нужно дождаться загрузки текстуры:

<canvas id="canvas" width="800" height="800"></canvas>
<script>
   let canvas = document.getElementById('canvas');
   let ctx = canvas.getContext('2d');
   let img = new Image();
   img.onload = function () {
      ctx.fillStyle = ctx.createPattern(img, 'repeat');
      ctx.fillRect(0, 0, canvas.width, canvas.height);
   };
   img.src = 'texture.jpg';
</script>

Рисование траектории

Управлять рисованием траектории позволяют следующие методы:

Пример рисования треугольника с обводкой и заливкой:

ctx.fillStyle = 'green';
ctx.strokeStyle = 'red';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.lineTo(20, 100);
ctx.lineTo(100, 100);
ctx.closePath();
ctx.stroke();
ctx.fill();
ctx.lineTo(20, 100);
arcTo(<X1>, <Y1>, <X2>, <Y2>, <radius>)

Пример рисования дуги:

ctx.strokeStyle = 'black';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(120, 120);
ctx.arcTo(200, 0, 300, 100, 100);
ctx.stroke();
arc(<centerX>, <centerY>, <radius>, <startAngle>,
    <endAngle>[, <Направление>])

Параметры <centerX> и <centerY> задают координаты центра круга, параметр <radius> — радиус круга, параметр <startAngle> — начальный угол в радианах, а параметр <endAngle> — конечный угол в радианах. Если параметр <Направление> имеет значение true, то дуга рисуется против часовой стрелки, а если false — по часовой стрелке.

Формула преобразования градусов в радианы:

radians = (Math.PI / 180) * degrees;

Пример:

ctx.strokeStyle = 'black';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(300, 150);
ctx.arc(200, 150, 100, 0, (Math.PI / 180) * 135);
ctx.stroke();
bezierCurveTo(<xc1>, <yc1>, <xc2>, <yc2>, <X1>, <Y1>)

Параметры <xc1> и <yc1> задают координаты первой опорной точки, параметры <xc2> и <yc2> — координаты второй опорной точки, а параметры <X1> и <Y1> — координаты конечной точки. Пример:

ctx.strokeStyle = 'black';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(295, 225);
ctx.bezierCurveTo(200, 390, 200, 200, 350, 300);
ctx.stroke();
quadraticCurveTo(<xc>, <yc>, <X1>, <Y1>)

Параметры <xc> и <yc> задают координаты опорной точки, а параметры <X1> и <Y1> — координаты конечной точки. Пример:

ctx.strokeStyle = 'green';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(50, 400);
ctx.quadraticCurveTo(200, 300, 350, 400);
ctx.stroke();
rect(<X>, <Y>, <Ширина>, <Высота>)

Пример:

ctx.strokeStyle = 'green';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.rect(300, 50, 80, 80);
ctx.stroke();

Иногда бывает необходимо выяснить, входит ли точка с заданными координатами в состав траектории. Сделать это можно с помощью метода isPointInPath(<X>, <Y>[, <fillRule>]). Метод возвращает true, если точка с такими координатами входит в состав траектории, и false — в противном случае.

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

Нарисовать на холсте прямоугольники позволяют следующие методы:

fillRect(<X>, <Y>, <Ширина>, <Высота>)

Параметры <X> и <Y> задают координаты левого верхнего угла прямоугольника, а параметры <Ширина> и <Высота> — ширину и высоту. Пример:

ctx.fillStyle = 'green';
ctx.fillRect(50, 50, 200, 100);
strokeRect(<X>, <Y>, <Ширина>, <Высота>)

Пример:

ctx.strokeStyle = 'blue';
ctx.lineWidth = 5;
ctx.strokeRect(50, 200, 80, 80);

Вывод текста

Управлять характеристиками текста позволяют следующие свойства:

ctx.font = '16pt Verdana, Tahoma, sans-serif';
ctx.fillStyle = 'green';
ctx.fillText('Electron', 50, 50);

Получим значение по умолчанию:

console.log(ctx.font); // 10px sans-serif
console.log(ctx.textAlign); // start

Пример:

ctx.font = '20pt Arial';
ctx.fillStyle = 'red';
ctx.textAlign = 'center';
ctx.fillText('Electron', 250, 50);
console.log(ctx.textBaseline); // alphabetic

Пример выравнивания по верху:

ctx.textBaseline = 'top';
ctx.fillText('Electron', 250, 100);

Вывести текст позволяют следующие методы:

fillText(<text>, <X>, <Y>[, <maxWidth>])

Параметр <text> задает выводимый текст, параметры <X> и <X> — координаты начальной точки, а параметр <maxWidth> — максимальную ширину поля. Пример ограничения ширины поля:

ctx.font = '16pt Verdana, Tahoma, sans-serif';
ctx.fillStyle = 'blue';
ctx.fillText('Electron', 50, 100, 50);

Если текст не помещается в указанную ширину, то он будет сжат таким образом, чтобы поместиться. В результате текст может стать абсолютно не читаемым. Если необходимо ограничить ширину и при этом чтобы текст не сжимался, а просто обрезался, то следует наложить маску;

strokeText(<text>, <X>, <Y>[, <maxWidth>])

Пример:

ctx.strokeStyle = 'blue';
ctx.lineWidth = 3;
ctx.textAlign = 'start';
ctx.font = '48pt Verdana';
ctx.strokeText('Electron', 50, 200);

Узнать информацию о характеристиках блока, в который будет вписан текст, позволяет метод measureText(<Текст>). Он возвращает объект TextMetrics. С помощью свойства width этого объекта, мы можем получить ширину текста в пикселах:

let text = 'Electron';
ctx.font = '48pt Verdana';
console.log(ctx.measureText(text).width); // 261.375

Вывод изображения

Вывести изображение на холст позволяет метод drawImage(). Форматы метода:

drawImage(<img>, <X>, <Y>)
drawImage(<img>, <X>, <Y>, <w>, <h>)
drawImage(<img>, <sx>, <sy>, <sw>, <sh>,
                 <dx>, <dy>, <dw>, <dh>)

Первый формат выводит изображение полностью в позицию с координатами <X> и <Y>. В первом параметре указывается объект изображения, например, ссылка на элемент IMG или экземпляр класса Image. Пример указания ссылки на элемент IMG:

<canvas id="canvas" width="800" height="800"></canvas>
<img src="photo.jpg" alt="" id="img1">
<script>
   window.onload = function() {
      let canvas = document.getElementById('canvas');
      let ctx = canvas.getContext('2d');
      let img = document.getElementById('img1');
      ctx.drawImage(img, 0, 0);
   };
</script>

Второй формат позволяет ограничить область вывода шириной (параметр <w>) и высотой (параметр <h>). При этом изображение может быть уменьшено или увеличено таким образом, чтобы вписаться в область. Если пропорции области не совпадают с пропорциями изображения, то изображение будет растянуто или сжато без соблюдения пропорций. Пример:

ctx.drawImage(img, 0, 400, 250, 166);

Третий формат берет прямоугольную область (<sx>, <sy>, <sw>, <sh>) с изображения и вписывает ее в прямоугольную область (<dx>, <dy>, <dw>, <dh>) на холсте. Пример указания экземпляра класса Image:

let img2 = new Image();
img2.onload = function() {
   ctx.drawImage(img2, 200,  50, 300, 300,
                       300, 400, 300, 300);
};
img2.src = 'photo.jpg';

Очистка прямоугольной области или всего холста

Стереть какую-либо прямоугольную область на холсте позволяет метод clearRect(). Формат метода:

clearRect(<X>, <Y>, <Ширина>, <Высота>)

Пример очистки всего холста:

ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

Сохранение и восстановление состояния

Значения основных характеристик можно сохранить в стек, выполнить какую-либо операцию рисования, а затем восстановить эти значения из стека. Для этого предназначены следующие методы:

Ограничим область вывода длинного текста маской:

ctx.save();
ctx.beginPath();
ctx.rect(0, 0, 270, 200);
ctx.clip();
ctx.font = '48pt Verdana';
ctx.fillText('Electron', 50, 50);
ctx.restore();

Вначале мы сохранили значения в стек, применили маску к тексту, а затем восстановили значения из стека. Если убрать последнюю инструкцию восстановления значений, то все последующие операции рисования будут отображаться только внутри маски, а не на всей поверхности холста.

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

Применить различные преобразования позволяют следующие свойства и методы:

ctx.save();
ctx.fillStyle = 'green';
ctx.globalAlpha = 0.5;
ctx.fillRect(10, 10, 20, 20);
ctx.restore();
ctx.save();
ctx.scale(2.0, 2.0);
ctx.fillRect(50, 10, 20, 20);
ctx.restore();
ctx.save();
ctx.fillStyle = 'blue';
ctx.rotate((Math.PI / 180) * 45);
ctx.fillRect(50, -20, 40, 40);
ctx.restore();

Формула преобразования градусов в радианы:

radians = (Math.PI / 180) * degrees;
ctx.save();
ctx.fillStyle = 'green';
ctx.translate(200, 200);
ctx.rotate((Math.PI / 180) * 45);
ctx.fillRect(0, 0, 40, 40);
ctx.restore();
Примечание

Существует также несколько методов, позволяющих работать с матрицей трансформации. За подробной информацией обращайтесь к документации.

Режимы наложения

При наложении фигур по умолчанию пиксели верхней фигуры перекроют пиксели нижней фигуры. Такое поведение задается режимом наложения source-over. С помощью свойства globalCompositeOperation можно указать другой режим наложения. Возможны следующие значения в виде строки: source-over, source-in, source-out, source-atop, destination-over, destination-in, destination-out, destination-atop, lighter, copy, xor, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity. Пример указания значения:

ctx.fillStyle = 'blue';
ctx.fillRect(0, 50, 400, 200);
ctx.fillStyle = 'red';
ctx.globalCompositeOperation = 'source-in';
ctx.fillRect(100, 0, 200, 300);

Создание тени

Управлять характеристиками тени позволяют следующие свойства:

Пример создания квадрата с тенью:

<canvas id="canvas" width="400" height="300"></canvas>
<script>
   let canvas = document.getElementById('canvas');
   let ctx = canvas.getContext('2d');
   ctx.shadowBlur = 15;
   ctx.shadowColor = 'black';
   ctx.shadowOffsetX = 5;
   ctx.shadowOffsetY = 5;
   
   ctx.fillStyle = 'blue';
   ctx.fillRect(50, 50, 100, 100);
</script>

Манипулирование отдельными пикселями

Выполнить манипуляции отдельными пикселами позволяют следующие методы:

getImageData(<X>, <Y>, <Ширина>, <Высота>)

Пример копирования всех данных с холста:

let data1 = ctx.getImageData(0, 0, canvas.width, canvas.height);
console.log(data1.width, data1.height);
createImageData(<Ширина>, <Высота>)
createImageData(<ImageData>)

Пример:

let data2 = ctx.createImageData(100, 100);
console.log(data2.width, data2.height);    // 100 100
console.log(data2.data[0], data2.data[1],
            data2.data[2], data2.data[3]); // 0 0 0 0

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

data2 = ctx.createImageData(data1);
console.log(data2.width, data2.height);     // 400 300
console.log(data2.data[0], data2.data[1],
            data2.data[2], data2.data[3]);  // 0 0 0 0
putImageData(<ImageData>, <X1>, <Y1>[, 
                          <X2>, <Y2>, <Ширина>, <Высота>])

Параметры <X1> и <Y1> задают начальную позицию вставки на холсте. Параметры <X2> и <Y2> определяют начальные координаты внутри объекта <ImageData>, а параметры <Ширина> и <Высота> — размеры копируемой области.

Класс ImageData содержит следующие свойства:

Создадим пустой массив, заполним его красным цветом, а затем выведем на холст:

let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
// ...
data2 = ctx.createImageData(canvas.width, canvas.height);
console.log(data2.width, data2.height); // 400 300
console.log(data2.data[0], data2.data[1], data2.data[2], data2.data[3]);
// 0 0 0 0
for (let i = 0; i < data2.data.length; i += 4) {
   data2.data[i + 0] = 255; // R
   data2.data[i + 1] = 0;   // G
   data2.data[i + 2] = 0;   // B
   data2.data[i + 3] = 255; // A
}
let canvas2 = document.getElementById('canvas2');
let ctx2 = canvas2.getContext('2d');
ctx2.putImageData(data2, 0, 0);

Метод toDataURL()

Метод toDataURL() объекта холста возвращает URL с закодированными данными изображения в указанном формате. Формат метода:

toDataURL([<Тип>[, <Качество JPEG>]])

В первом параметре можно указать MIME-тип изображения. Если параметр не указан, то используется значение image/png:

let dataPNG = canvas.toDataURL();
console.log(dataPNG);
// data:image/png;base64,iVBORw0KGgoAAAANSUhEU...

Во втором параметре можно указать качество JPEG-изображения в виде вещественного числа от 0 до 1. Значение по умолчанию: 0.92. Пример:

let dataJPEG = canvas.toDataURL('image/jpeg', 0.92);
console.log(dataJPEG);
// data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAA...

При использовании формата JPEG следует учитывать, что сжатие выполняется с потерями, поэтому можно получить различные артефакты на изображении. Кроме того, формат JPEG не поддерживает прозрачность.

Сохранение изображения в файл

Метод toBlob() объекта холста возвращает объект Blob с данными изображения в указанном формате. Формат метода:

toBlob(<Функция>[, <Тип>[, <Качество JPEG>]])

В первом параметре указывается ссылка на функцию, которая будет вызвана при завершении операции. Через параметр внутри функции будет доступен объект Blob. Во втором параметре можно указать MIME-тип изображения. Если параметр не указан, то используется значение image/png. В третьем параметре можно указать качество JPEG-изображения в виде вещественного числа от 0 до 1. Пример:

canvas.toBlob( function(blob) {
   console.log(blob);
   // Blob {size: 2826, type: "image/jpeg"}
}, 'image/jpeg', 0.92);

Для преобразования объекта Blob в объект ArrayBuffer используется метод arrayBuffer(), который возвращает объект Promise<ArrayBuffer>. Чтобы из объекта ArrayBuffer получить объект Buffer, следует воспользоваться статическим методом from(). Пример сохранения изображения с холста в файл при нажатии кнопки:

let btn1 = document.getElementById('btn1');
btn1.addEventListener('click', (e) => {
   const fs = require('fs');
   const path = require('path');
   canvas.toBlob( async function(blob) {
      let buf = Buffer.from(await blob.arrayBuffer());
      let p = path.join(__dirname, 'test.png');
      try {
         fs.writeFileSync(p, buf, {encoding: null});
         console.log('Сохранено');
      } catch (e) {
         console.log(e);
      }
   }, 'image/png');
});

При необходимости мы можем загрузить изображение из файла и нарисовать его на холсте, используя метод drawImage(), например, при нажатии кнопки:

let btn3 = document.getElementById('btn3');
btn3.addEventListener('click', (e) => {
   const fs = require('fs');
   const path = require('path');
   let p = path.join(__dirname, 'test.png');
   if ( !fs.existsSync(p) ) {
      console.log('Файл не найден');
      return;
   }
   let img = new Image();
   img.onload = function() {
      ctx.drawImage(img, 0, 0);
   };
   img.src = p;
});