Spec-Zone.ru › Web APIs

Рисование фигур с помощью canvas

  • Предыдущая
  • Следующая

Теперь, когда мы настроили нашу среду canvas, мы можем перейти к деталям рисования на холсте. К концу этой статьи вы научитесь рисовать прямоугольники, треугольники, линии, дуги и кривые, освоив некоторые из основных фигур. Работа с путями необходима при рисовании объектов на холсте, и мы увидим, как это можно сделать.

Сетка

Прежде чем начать рисовать, нам нужно поговорить о сетке холста или пространстве координат. Наш HTML-скелет с предыдущей страницы имел элемент canvas шириной 150 пикселей и высотой 150 пикселей.

Canvas grid with a blue square demonstrating coordinates and axes.

Обычно 1 единица в сетке соответствует 1 пикселю на холсте. Начало этой сетки расположено в верхнем левом углу в координатах (0,0). Все элементы размещаются относительно этого начала. Таким образом, положение верхнего левого угла синего квадрата становится x пикселей от левого края и y пикселей от верхнего края, в координатах (x,y). Позже в этом руководстве мы увидим, как можно изменить начало в другую точку, повернуть сетку и даже масштабировать её, но пока мы будем использовать настройки по умолчанию.

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

В отличие от SVG, <canvas> поддерживает только две примитивные фигуры: прямоугольники и пути (списки точек, соединённых линиями). Все остальные фигуры должны быть созданы путём объединения одного или нескольких путей. К счастью, у нас есть набор функций рисования путей, которые позволяют создавать очень сложные фигуры.

Сначала давайте рассмотрим прямоугольник. Есть три функции, которые рисуют прямоугольники на холсте:

fillRect(x, y, width, height)

Рисует заполненный прямоугольник.

strokeRect(x, y, width, height)

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

clearRect(x, y, width, height)

Очищает указанную прямоугольную область, делая её полностью прозрачной.

Каждая из этих трёх функций принимает одни и те же параметры. x и y задают положение на холсте (относительно начала координат) верхнего левого угла прямоугольника. width и height задают размер прямоугольника.

Ниже приведена функция draw() с предыдущей страницы, но теперь она использует эти три функции.

Пример прямоугольной фигуры

function draw() {
  const canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    const ctx = canvas.getContext("2d");

    ctx.fillRect(25, 25, 100, 100);
    ctx.clearRect(45, 45, 60, 60);
    ctx.strokeRect(50, 50, 50, 50);
  }
}

Вывод этого примера показан ниже.

Функция fillRect() рисует большой чёрный квадрат со стороной 100 пикселей. Функция clearRect() затем стирает квадрат размером 60x60 пикселей из центра, а затем функция strokeRect() вызывается для создания прямоугольного контура размером 50x50 пикселей внутри очищенного квадрата.

На следующих страницах мы рассмотрим два альтернативных метода для clearRect(), а также то, как изменить цвет и стиль обводки отрисованных фигур.

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

Рисование путей

Теперь давайте рассмотрим пути. Путь — это список точек, соединённых отрезками линий, которые могут иметь различную форму, быть кривыми или прямыми, различной ширины и цвета. Путь, или даже подпуть, может быть замкнутым. Чтобы создать фигуры с помощью путей, нам нужно выполнить несколько дополнительных шагов:

  1. Сначала вы создаёте путь.
  2. Затем вы используете команды рисования, чтобы нарисовать на пути.
  3. После создания пути вы можете обвести или заполнить путь, чтобы отобразить его.

Вот функции, используемые для выполнения этих шагов:

beginPath()

Создаёт новый путь. После создания, все последующие команды рисования будут направлены на этот путь и будут использоваться для построения пути.

Методы путей

Методы для задания различных путей для объектов.

closePath()

Добавляет прямую линию на путь, ведущую к началу текущего подпути.

stroke()

Рисует фигуру, обводя её контур.

fill()

Рисует сплошную фигуру, заполняя область внутри пути.

Первый шаг для создания пути — вызвать beginPath(). Внутренне пути хранятся как список подпутей (линии, дуги и т. д.), которые вместе образуют фигуру. Каждый раз при вызове этого метода список сбрасывается, и мы можем начать рисовать новые фигуры.

Примечание: Когда текущий путь пуст, например, сразу после вызова beginPath(), или на только что созданном холсте, первая команда построения пути всегда обрабатывается как moveTo(), независимо от того, что она собой представляет. По этой причине вы практически всегда захотите установить начальную позицию после сброса пути.

Второй шаг — вызов методов, которые фактически определяют пути, которые нужно нарисовать. Мы увидим их вскоре.

Третий, и необязательный шаг, — вызвать closePath(). Этот метод пытается закрыть фигуру, нарисовав прямую линию от текущей точки к начальной. Если фигура уже закрыта или в списке только одна точка, эта функция ничего не делает.

Примечание: Когда вы вызываете fill(), все открытые фигуры автоматически закрываются, поэтому вам не нужно вызывать closePath(). Это не относится к случаю, когда вы вызываете stroke().

Рисование треугольника

Например, код для рисования треугольника будет выглядеть примерно так:

function draw() {
  const canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    const ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.moveTo(75, 50);
    ctx.lineTo(100, 75);
    ctx.lineTo(100, 25);
    ctx.fill();
  }
}

Результат выглядит так:

Перемещение пера

Очень полезная функция, которая на самом деле ничего не рисует, но становится частью списка путей, описанного выше, — это функция moveTo(). Вы можете представить это как поднятие пера или карандаша с одного места на листе бумаги и его перемещение на следующее.

moveTo(x, y)

Перемещает перо в координаты, заданные значениями x и y.

При инициализации холста или вызове beginPath() обычно необходимо использовать функцию moveTo() для размещения начальной точки в другом месте. Мы также можем использовать moveTo() для рисования несвязанных путей. Посмотрите на смайлик ниже.

Чтобы попробовать это самостоятельно, вы можете использовать фрагмент кода ниже. Просто вставьте его в функцию draw() , которую мы видели ранее.

function draw() {
  const canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    const ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // Outer circle
    ctx.moveTo(110, 75);
    ctx.arc(75, 75, 35, 0, Math.PI, false); // Mouth (clockwise)
    ctx.moveTo(65, 65);
    ctx.arc(60, 65, 5, 0, Math.PI * 2, true); // Left eye
    ctx.moveTo(95, 65);
    ctx.arc(90, 65, 5, 0, Math.PI * 2, true); // Right eye
    ctx.stroke();
  }
}

Результат выглядит так:

Если вы хотите увидеть соединительные линии, вы можете удалить строки, которые вызывают moveTo().

Примечание: Чтобы узнать больше о функции arc() , см. раздел Дуги ниже.

Линии

Для рисования прямых линий используйте метод lineTo().

lineTo(x, y)

Рисует линию от текущей позиции рисования до позиции, заданной x и y.

Этот метод принимает два аргумента, x и y, которые представляют собой координаты конечной точки линии. Начальная точка зависит от ранее нарисованных путей, где конечная точка предыдущего пути является начальной точкой для следующего и так далее. Начальную точку также можно изменить, используя метод moveTo().

Ниже приведён пример, демонстрирующий рисование двух треугольников: одного залитого и одного контурного.

function draw() {
  const canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    const ctx = canvas.getContext("2d");

    // Filled triangle
    ctx.beginPath();
    ctx.moveTo(25, 25);
    ctx.lineTo(105, 25);
    ctx.lineTo(25, 105);
    ctx.fill();

    // Stroked triangle
    ctx.beginPath();
    ctx.moveTo(125, 125);
    ctx.lineTo(125, 45);
    ctx.lineTo(45, 125);
    ctx.closePath();
    ctx.stroke();
  }
}

Этот пример начинается с вызова beginPath() для начала нового пути фигуры. Затем мы используем метод moveTo() для перемещения начальной точки в нужное положение. Ниже этого рисуются две линии, образующие две стороны треугольника.

Вы заметите разницу между залитым и контурным треугольником. Это связано с тем, что фигуры автоматически закрываются при заливке, но не при рисовании контура. Если бы мы убрали closePath() для контурного треугольника, были бы нарисованы только две линии, а не целый треугольник.

Дуги

Для рисования дуг или окружностей мы используем методы arc() или arcTo().

arc(x, y, radius, startAngle, endAngle, counterclockwise)

Рисует дугу, которая центрирована в позиции (x, y) с радиусом r, начиная с startAngle и заканчивая endAngle в заданном направлении, указанном параметром counterclockwise (по умолчанию по часовой стрелке).

arcTo(x1, y1, x2, y2, radius)

Рисует дугу с заданными контрольными точками и радиусом, соединенную с предыдущей точкой прямой линией.

Давайте более подробно рассмотрим метод arc, который принимает шесть параметров: x и y — координаты центра окружности, на которой должна быть нарисована дуга. radius понятен из названия. Параметры startAngle и endAngle определяют начальную и конечную точки дуги в радианах, вдоль кривой окружности. Они измеряются от оси x. Параметр counterclockwise — булевое значение, которое, когда true, рисует дугу против часовой стрелки; в противном случае дуга рисуется по часовой стрелке.

Примечание: Углы в функции arc измеряются в радианах, а не в градусах. Для преобразования градусов в радианы можно использовать следующее выражение JavaScript: radians = (Math.PI/180)*degrees.

Следующий пример немного сложнее, чем предыдущие. Он рисует 12 различных дуг с разными углами и заливкой.

Два for цикла предназначены для перебора строк и столбцов дуг. Для каждой дуги мы начинаем новый путь, вызывая beginPath(). В коде каждый из параметров для дуги находится в переменной для ясности, но в реальной жизни это не обязательно.

Координаты x и y должны быть достаточно понятны. radius и startAngle фиксированы. endAngle начинается с 180 градусов (половина окружности) в первом столбце и увеличивается на 90 градусов, достигая полной окружности в последнем столбце.

Выражение для параметра clockwise приводит к тому, что первая и третья строка рисуются как дуги по часовой стрелке, а вторая и четвертая — против часовой стрелки. Наконец, выражение if делает верхнюю половину дуг контурными, а нижнюю — заполненными.

Примечание: Данный пример требует немного большего холста, чем другие на этой странице: 150 x 200 пикселей.

function draw() {
  const canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    const ctx = canvas.getContext("2d");

    for (let i = 0; i < 4; i++) {
      for (let j = 0; j < 3; j++) {
        ctx.beginPath();
        const x = 25 + j * 50; // x coordinate
        const y = 25 + i * 50; // y coordinate
        const radius = 20; // Arc radius
        const startAngle = 0; // Starting point on circle
        const endAngle = Math.PI + (Math.PI * j) / 2; // End point on circle
        const counterclockwise = i % 2 !== 0; // clockwise or counterclockwise

        ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise);

        if (i > 1) {
          ctx.fill();
        } else {
          ctx.stroke();
        }
      }
    }
  }
}

Кривые Безье и квадратичные кривые

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

quadraticCurveTo(cp1x, cp1y, x, y)

Рисует квадратичную кривую Безье от текущей позиции пера до конечной точки, заданной x и y, используя контрольную точку, заданную cp1x и cp1y.

bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

Рисует кубическую кривую Безье от текущей позиции пера до конечной точки, заданной x и y, используя контрольные точки (cp1x, cp1y) и (cp2x, cp2y).

Разница заключается в том, что квадратичная кривая Безье имеет начальную и конечную точку (синие точки) и только одну **контрольную точку** (обозначенную красной точкой), а кубическая кривая Безье использует две контрольные точки. Quadratic and Bezier curve comparison.

Параметры x и y в обоих этих методах представляют координаты конечной точки. cp1x и cp1y — координаты первой контрольной точки, а cp2x и cp2y — координаты второй контрольной точки.

Использование квадратичных и кубических кривых Безье может быть довольно сложным, поскольку, в отличие от программ векторной графики, таких как Adobe Illustrator, у нас нет прямой визуальной обратной связи о том, что мы делаем. Это затрудняет рисование сложных форм. В следующем примере мы будем рисовать простые органические формы, но если у вас есть время и, самое главное, терпение, можно создать гораздо более сложные формы.

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

Квадратичные кривые Безье

В этом примере для отрисовки облачка используется несколько квадратичных кривых Безье.

function draw() {
  const canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    const ctx = canvas.getContext("2d");

    // Quadratic curves example
    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(125, 100, 125, 62.5);
    ctx.quadraticCurveTo(125, 25, 75, 25);
    ctx.stroke();
  }
}

Кубические кривые Безье

В этом примере с помощью кубических кривых Безье рисуется сердце.

function draw() {
  const canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    const ctx = canvas.getContext("2d");

    // Cubic curves example
    ctx.beginPath();
    ctx.moveTo(75, 40);
    ctx.bezierCurveTo(75, 37, 70, 25, 50, 25);
    ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
    ctx.bezierCurveTo(20, 80, 40, 102, 75, 120);
    ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
    ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
    ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
    ctx.fill();
  }
}

Прямоугольники

В дополнение к трём методам, которые мы видели в Рисовании прямоугольников, которые рисуют прямоугольные фигуры непосредственно на холсте, также есть метод rect(), который добавляет прямоугольный путь к текущему открытому пути.

rect(x, y, width, height)

Рисует прямоугольник, верхний левый угол которого задан координатами (x, y) с указанной width и height.

Перед выполнением этого метода автоматически вызывается метод moveTo() с параметрами (x,y). Другими словами, текущая позиция пера автоматически сбрасывается до стандартных координат.

Создание комбинаций

До сих пор каждый пример на этой странице использовал только один тип функции пути для каждой фигуры. Однако нет ограничений на количество или типы путей, которые можно использовать для создания фигуры. Поэтому в этом заключительном примере давайте объединим все функции пути, чтобы создать набор очень известных игровых персонажей.

function draw() {
  const canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    const ctx = canvas.getContext("2d");

    roundedRect(ctx, 12, 12, 184, 168, 15);
    roundedRect(ctx, 19, 19, 170, 154, 9);
    roundedRect(ctx, 53, 53, 49, 33, 10);
    roundedRect(ctx, 53, 119, 49, 16, 6);
    roundedRect(ctx, 135, 53, 49, 33, 10);
    roundedRect(ctx, 135, 119, 25, 49, 10);

    ctx.beginPath();
    ctx.arc(37, 37, 13, Math.PI / 7, -Math.PI / 7, false);
    ctx.lineTo(31, 37);
    ctx.fill();

    for (let i = 0; i < 8; i++) {
      ctx.fillRect(51 + i * 16, 35, 4, 4);
    }

    for (let i = 0; i < 6; i++) {
      ctx.fillRect(115, 51 + i * 16, 4, 4);
    }

    for (let i = 0; i < 8; i++) {
      ctx.fillRect(51 + i * 16, 99, 4, 4);
    }

    ctx.beginPath();
    ctx.moveTo(83, 116);
    ctx.lineTo(83, 102);
    ctx.bezierCurveTo(83, 94, 89, 88, 97, 88);
    ctx.bezierCurveTo(105, 88, 111, 94, 111, 102);
    ctx.lineTo(111, 116);
    ctx.lineTo(106.333, 111.333);
    ctx.lineTo(101.666, 116);
    ctx.lineTo(97, 111.333);
    ctx.lineTo(92.333, 116);
    ctx.lineTo(87.666, 111.333);
    ctx.lineTo(83, 116);
    ctx.fill();

    ctx.fillStyle = "white";
    ctx.beginPath();
    ctx.moveTo(91, 96);
    ctx.bezierCurveTo(88, 96, 87, 99, 87, 101);
    ctx.bezierCurveTo(87, 103, 88, 106, 91, 106);
    ctx.bezierCurveTo(94, 106, 95, 103, 95, 101);
    ctx.bezierCurveTo(95, 99, 94, 96, 91, 96);
    ctx.moveTo(103, 96);
    ctx.bezierCurveTo(100, 96, 99, 99, 99, 101);
    ctx.bezierCurveTo(99, 103, 100, 106, 103, 106);
    ctx.bezierCurveTo(106, 106, 107, 103, 107, 101);
    ctx.bezierCurveTo(107, 99, 106, 96, 103, 96);
    ctx.fill();

    ctx.fillStyle = "black";
    ctx.beginPath();
    ctx.arc(101, 102, 2, 0, Math.PI * 2, true);
    ctx.fill();

    ctx.beginPath();
    ctx.arc(89, 102, 2, 0, Math.PI * 2, true);
    ctx.fill();
  }
}

// A utility function to draw a rectangle with rounded corners.

function roundedRect(ctx, x, y, width, height, radius) {
  ctx.beginPath();
  ctx.moveTo(x, y + radius);
  ctx.arcTo(x, y + height, x + radius, y + height, radius);
  ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
  ctx.arcTo(x + width, y, x + width - radius, y, radius);
  ctx.arcTo(x, y, x, y + radius, radius);
  ctx.stroke();
}

Полученное изображение выглядит так:

Мы не будем подробно рассматривать это, поскольку это на самом деле довольно просто. Самое важное, что следует отметить, — это использование свойства fillStyle в контексте рисования и использование вспомогательной функции (в данном случае roundedRect()). Использование вспомогательных функций для часто используемых элементов рисования может быть очень полезным и сократить количество необходимых кодов, а также их сложность.

Мы еще раз рассмотрим fillStyle, более подробно, позже в этом руководстве. Здесь мы просто используем его для изменения цвета заливки путей с основного чёрного на белый и обратно.

Фигуры с отверстиями

Для создания фигуры с отверстием необходимо рисовать отверстие в другом направлении, чем внешнюю фигуру. Мы либо рисуем внешнюю фигуру по часовой стрелке, а внутреннюю — против часовой, либо внешнюю — против часовой, а внутреннюю — по часовой.

function draw() {
  const canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    const ctx = canvas.getContext("2d");

    ctx.beginPath();

    // Outer shape clockwise ⟳
    ctx.moveTo(0, 0);
    ctx.lineTo(150, 0);
    ctx.lineTo(75, 129.9);

    // Inner shape anticlockwise ↺
    ctx.moveTo(75, 20);
    ctx.lineTo(50, 60);
    ctx.lineTo(100, 60);

    ctx.fill();
  }
}

В примере выше внешний треугольник рисуется по часовой стрелке (перемещение в верхний левый угол, затем линия в верхний правый угол и завершение в нижней точке), а внутренний треугольник — против часовой стрелки (перемещение в верхнюю точку, затем линия в нижний левый угол и завершение в нижнем правом угле).

Объекты Path2D

Как мы видели в предыдущем примере, для отрисовки объектов на канвасе может потребоваться серия путей и команд рисования. Чтобы упростить код и улучшить производительность, объект Path2D, доступный в последних версиях браузеров, позволяет кэшировать или записывать эти команды рисования. Вы сможете быстро воспроизводить свои пути. Давайте посмотрим, как можно создать объект Path2D.

Path2D()

Конструктор Path2D() возвращает новый экземпляр объекта Path2D, необязательно с другим путем в качестве аргумента (создаёт копию), или необязательно со строкой, содержащей данные SVG-пути.

new Path2D(); // empty path object
new Path2D(path); // copy from another Path2D object
new Path2D(d); // path from SVG path data

Все методы путей, такие как moveTo, rect, arc или quadraticCurveTo, и т.д., о которых мы узнали выше, доступны для объектов Path2D.

API Path2D также добавляет способ объединения путей с помощью метода addPath. Это может быть полезно, когда нужно создать объекты из нескольких компонентов.

Path2D.addPath(path [, transform])

Добавляет путь к текущему пути с необязательной матрицей преобразования.

Пример Path2D

В этом примере мы создаём прямоугольник и круг. Оба они хранятся как объект Path2D, чтобы их можно было использовать позднее. С новым API Path2D, несколько методов были обновлены, чтобы опционально принимать объект Path2D, который можно использовать вместо текущего пути. Здесь stroke и fill используются с аргументом пути для отрисовки обоих объектов на холсте, например.

function draw() {
  const canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    const ctx = canvas.getContext("2d");

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

    const circle = new Path2D();
    circle.arc(100, 35, 25, 0, 2 * Math.PI);

    ctx.stroke(rectangle);
    ctx.fill(circle);
  }
}

Использование SVG путей

Ещё одна мощная функция нового API холста Path2D — использование данных SVG пути для инициализации путей на вашем холсте. Это может позволить вам передавать данные пути и повторно использовать их как в SVG, так и на холсте.

Путь будет перемещён в точку (M10 10) и затем перемещён горизонтально на 80 пунктов вправо (h 80), затем на 80 пунктов вниз (v 80), затем на 80 пунктов влево (h -80), и затем обратно к началу (z). Вы можете увидеть этот пример на странице Path2D конструктора.

const p = new Path2D("M10 10 h 80 v 80 h -80 Z");
  • Предыдущий
  • Следующий

© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API