JS30. Задание 8 Fun with HTML5 Canvas


Демо https://canvas30.github.io/
Код https://github.com/canvas30/canvas30.github.io


Я рисую.
Интересное задание с использованием canvas.
Как решать пока не знаю, но это явно увлекательно и забавно

Разметка очень простая:

<canvas id="draw" width="800" height="800"></canvas>

В HTML5 определен элемент <canvas> как «растровый холст, который может быть использован для отображения диаграмм, игровой графики или изображений на лету».
Сanvas - это прямоугольная область на странице, где с помощью JavaScript можно рисовать.

Как же этот холст выглядит? В действительности, никак. У тега <canvas> нет собственного контента и рамки.

Добавила холсту пунктирную рамку и тут же появился вопрос. Мой холст имеет размер 800х800рх, а авторский растягивается на всю страницу. Но стили у нас одинаковые, то есть почти никаких:

html, body {
    margin:0;
}

Получается, что размер холста 100%х100% прописан в js. А в чём смысл выносить стилизацию в js, если css с ней прекрасно справляется? Хммм. А может и не справляется

Хорошо.
Наш js-код
1. Находим элемент canvas
У автора это выглядит так:

const canvas = document.querySelector('#draw');

Вместо const использую var вместо querySelector - getElementById
Получаю

var canvas = document.getElementById("draw");

2. Каждый холст имеет контекст рисования. Добавить этот контекст позволяет метод .getContext("2d").

Этот метод принимает одно из двух значений, 2d или 3d, описывающих, соответственно, двухмерную и трехмерную среды рисования. Пока что поддерживается только вариант 2d, однако разработка трехмерного API для холста уже идет полным ходом

var ctx = canvas.getContext("2d");

Мы добавили canvas контекст рисования и присвоили его переменной ctx. Все остальные операции с рисованием в canvas будут происходить с участием этой переменной.

3. Растягиваем canvas во всё окно браузера

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

4.

ctx.strokeStyle = "#BADA55";
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.lineWidth = 100;

ctx.strokeStyle = "#BADA55"; как несложно догадаться, задаёт цвет линии. По умолчанию он чёрный, здесь выбран салатовый

ctx.lineJoin = "round"; определяет форму вершин в которых линии сходятся Свойство может принимать три значения: round (закруглённые углы), bevel (срезанные углы) и miter (острые углы, свойство по умолчанию)

ctx.lineCap = "round"; определяет, как будут выглядеть концы нарисованных линий. Свойство может принимать 3 значения: butt, round и square. По умолчанию установленно butt.

ctx.lineWidth = 100; - толщина линии в рх

5. Закомментированное свойство

// ctx.globalCompositeOperation = 'multiply';

Оно показывает как будут взаимодействовать цвета при наложении
Варианты наложения можно посмотреть здесь

6. Здесь мы ввели несколько переменных

let isDrawing = false;
let lastX = 0;
let lastY = 0;
let hue = 0;
let direction = true;

let isDrawing = false; - находится ли мышка на холсте. Изначально, нет. Когда мышка будет двигаться над холстом, значение изменится:

canvas.addEventListener("mousedown", (e) => {
  isDrawing = true;
  [lastX, lastY] = [e.offsetX, e.offsetY];
});

Событие mousedown срабатывает, когда кнопка указывающего устройства (к примеру, мыши) нажата над элементом.

let lastX = 0;
let lastY = 0;
- координаты положения мышки 

Для того, чтобы их определить, мы запускаем функцию с аргументом (е), что даёт возможность получить значения e.offsetX, e.offsetY, которые показывают отступ курсора мыши по осям X и Y от края canvas.

let hue = 0; - угол смещения цвета
Считаем так:

hue++;
  if (hue >= 360) {
    hue = 0;
  }

При передвижении мышки на каждый пиксель (?), hue увеличивается на 1. Когда становится равным 360, сбрасывается до нуля.

При этом ctx.strokeStyle, которое, как мы помним, отвечает за цвет линии, определяется строкой:

ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`

HSL (от англ. hue, saturation, lightness) — цветовая модель, в которой цветовыми координатами являются тон, насыщенность и светлота

let direction = true это изменение толщины линии


 if (ctx.lineWidth >= 100 || ctx.lineWidth <= 1) {
    direction = !direction;
  }

if(direction) {
    ctx.lineWidth++;
  } else {
    ctx.lineWidth--;
  }

Когда толщина линии больше или равна 100рх (то есть с самого начала) direction превращается в !direction;, то есть true в false. А когда direction false, толщина линии уменьшается до того момента, пока толщина линии не становится 1рх. В этот момент значение direction меняется, становится true и линия начинает утолщаться.

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

function draw(e) {
  if (!isDrawing) return; // stop the fn from running when they are not moused down
  ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
  ctx.beginPath();
  // start from
  ctx.moveTo(lastX, lastY);
  // go to
  ctx.lineTo(e.offsetX, e.offsetY);
  ctx.stroke();
  [lastX, lastY] = [e.offsetX, e.offsetY];

  hue++;
  if (hue >= 360) {
    hue = 0;
  }
  if (ctx.lineWidth >= 100 || ctx.lineWidth <= 1) {
    direction = !direction;
  }

  if(direction) {
    ctx.lineWidth++;
  } else {
    ctx.lineWidth--;
  }

}


Смотрим что происходит.

if (!isDrawing) return; пока курсор мышки не над холстом, никакого рисования не будет, потому что изначально значение isDrawing = false

А когда курсор появляется над холстом, срабатывает код

canvas.addEventListener('mousedown', (e) => {
  isDrawing = true;
  [lastX, lastY] = [e.offsetX, e.offsetY];
});

И рисование начинается

ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`; - об этом уже говорили. Изменение цвета линии

ctx.beginPath(); метод  запускает новый путь, опуская список подпутей. Вызовите этот метод, когда хотите создать новый путь (с)

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

ctx.moveTo(lastX, lastY); метод перемещает начальную точку нового фрагмента контура в координаты (x, y). перемещает "курсор" в позицию x, y и делает её текущей

ctx.lineTo(e.offsetX, e.offsetY); - проводить линию к точкам с координатами e.offsetX, e.offsetY
ведёт линию из текущей позиции в указанную, и делает в последствии указанную текущей

ctx.stroke(); для каждого элемента применяет те стили, которые являются текущими на данный момент. Метод stroke () фактически рисует путь, который вы определили, со всеми этими методами moveTo () и lineTo ().

[lastX, lastY] = [e.offsetX, e.offsetY]; - определение положений курсора (массив) при помощи метода браузера e.offset

Остальные строки функции уже описаны выше - это изменение оттенка и изменение толщины линии


7. Последние строки кода


canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', () => isDrawing = false);
canvas.addEventListener('mouseout', () => isDrawing = false);

Отслеживаем положение мышки над canvas. Рисуем, когда мышка передвигается, не рисуем, когда мышку убрали.

Пока код кажется довольно сложным и запутанным. Завтра попробую его ещё раз повторить и написать самостоятельно.
С другой стороны, здесь всего 55 строчек кода. Здесь примерно 200 строк кода понадобились для того, чтобы рисовать линии. Прямые, черного цвета, одной толщины.

Изменила проект. Добавила палитру цветов
Демо https://canvas302.github.io/
Код https://github.com/canvas302/canvas302.github.io

var canvas = document.getElementById("draw");
var ctx = canvas.getContext("2d");
var colors = document.querySelectorAll(".color")

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

ctx.strokeStyle = "#BADA55";
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.lineWidth = 20;

var isDrawing = false;
var lastX = 0;
var LastY = 0;
var hue = 0;

colors.forEach(color => color.addEventListener("click", function() {
    hue = this.dataset.color;
}));


function draw(e) {
    if (!isDrawing) return; // stop the fn from running when they are not moused down
    ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
    ctx.beginPath();
    // start from
    ctx.moveTo(lastX, lastY);
    // go to
    ctx.lineTo(e.offsetX, e.offsetY);
    ctx.stroke();
    [lastX, lastY] = [e.offsetX, e.offsetY];
}

canvas.addEventListener("mousedown", (e) => {
    isDrawing = true;
    [lastX, lastY] = [e.offsetX, e.offsetY];
});

canvas.addEventListener("mousemove", draw);
canvas.addEventListener("mouseup", () => isDrawing = false);