32. Создаем приложение для рисования

Продолжаем изучать canvas.

В этом уроке мы с вами создадим вот такое приложение, в котором мы можем нарисовать всё что угодно. Также мы можем рисовать ровные геометрические фигуры, рисовать мы все можем любым цветом. Геометрические фигуры мы также можем заливать цветом. Еще у нас есть инструмент стёрка. С помощью этой кнопки очистить весь холст. А с помощью этой мы можем сохранить текущий рисунок. Давайте что-нибудь снова нарисую и сохраню.

Приступаем к написанию данного приложения. Сначала создадим папку DrawingApp. Копируем туда папку с картинками. Далее создаем html файл. Пишем необходимые теги.

Меняю язык и название. Приложение для рисования

Первым тегом внутри бади будет тег див с id container в котором и будет находится наше приложение.

Наше приложение будет состоять из двух разделов. Слева будет раздел с инструментами и кнопками, справа непосредственно сам холст.

Давайте за эти разделы будут отвечать теги section. Первому разделу зададим айди тул барс, а втором дравинг борард. Второй раздел в плане html будет самым простым, там внутри просто будет тег канвас. Теперь переходим к созданию первого раздела.

Первый раздел будет состоять из 4 частей. Создадим их с помощью тегов див с классом part

Давайте предпоследнему тегу добавим класс colors, а последнему buttons. Теперь первому тегу див.

Сперва будет идти тег label с названием этой части. Класс у этого тега лейбл будет тайтл. А текст Формы. Далее у нас будет идти список с необходимыми формами для рисования. Создадим конечно мы этот список с помощью тега ul. Класс у него будет опшн.

Внутри этого тега будут располагаться четыре тега ли с классами option и tool. Удалим у последнего тега li класс tool так как этот тег будет отвечать за заливку, а не за инструмент типа прямоугольник, круг или треугольник. Т.е. это опция а не инструмент, как и переводится класс тул. Теперь зададим соответствующие id инструментам формы.

Внутри каждого тега ли будет идти картинка и название инструмента.

А у последнего тега опшин будет идти чекбокс и название. У чекбокса id будет fill-color. Теперь давайте свяжем тег лейбл и чекбокс с помощью атрибута for. В значении укажем id чекбокса.

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

У слайдера id будет size-slider, минимальное значение 1, максимальное 30 и значение по умолчанию 5.

Третья часть будет как упрощенная версия предыдущих частей.

Внутри последнего тега li будет тге инпут для выбора цвета.

И сделаем второй тег ли выбранным по умолчанию с помощью класса selected. Переходим к последней части с кнопками.

HTML код готов, переходим к CSS.

CSS код готов, теперь переходим к JavaScript.

Создадим необходимый js файл и подключим к html файлу. И начинаем набирать javaScript код.

Сперва получим доступ к канвасу, и создадим 2D холст. Теперь получим доступ к инструментам, таким как прямоугольник, круг, треугольник, кисть и стерка. Далее получим доступ к чекбоксу заливка. Затем нам нужен слайдер. Теперь нам нужны теги цветов. Также нам нужен инпут для выбора цвета. И осталось получить доступ к кнопкам.

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

Теперь создадим функцию drawing

В теле функции сперва будет расставлять точки для линии с помощью метода LineTo. В качестве координат для точек будем брать положение курсора. Положение курсора по X можно взять в свойстве offsetX, а положение курсора по Y можно взять в свойстве offsetY. Далее отобразим линию с текущими значениями стилей по умолчанию с помощью метода stroke.

Проверяем и видим такое. Вроде бы все работает но как-то не так. У нас есть большое отклонение линии от курсора, и линия какая-то размытая. Дело в том, что Холст для канваса не все css свойства и их значения понимает адекватно. Давайте посмотрим css свойства для холста.

И здесь мы столкнулись с проблемой масштабирования. Т.е. холст не понимает размеры заданные в CSS, он понимает только размеры заданные с помощью атрибутов уив и хай для тега канвас. Давайте перезагрузим страницу, отключим эти стили и добавим отображение границ холста.

И видим что холст по умолчанию занимает такое место. Давайте порисуем мышью. Теперь линия рисуется корректно.

Как же нам удалить отклонение мыши и размытость линии? Для этого нам надо при загрузке нашей страницы задать атрибутам уив и хай тега канвас текущие размеры, которые учитывают css-стили. Эти размеры учитывающие css стили у объекта канвас находятся в свойствах offsetWidth и offsetHeight. Давайте напишем необходимый код.

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

Назовем её исДравин, и по умолчанию значение у этой переменной будет фолс.

Далее прикрепим слушатель к канвасу на событие mousedown т.е. когда будет нажата левая кнопка мыши.

А во втором параметре назовем нашу функцию стартДроу. Теперь приступаем к созданию этой функции.

Если пользователь нажал левой кнопкой мыши на канвасе, то переменной isDrawing зададим значение true. И с помощью метода beginPath для холста будем каждый раз при нажатии левой кнопкой мыши начинать новую линию, чтобы она не продолжала предыдущую. Теперь в методе дровинг сделаем так, что если переменная isDrawing равна фолсе то мы сразу же выходим из функции.

И нам осталось сделать так, что если левая кнопка мыши была отпущена, то мы снова переменной isDrawing задаем значение фолс. Проверяем. Всё работает корректно.

Двигаемся дальше. Давайте теперь при клике на любой инструмент сделаем его активным. Наши инструменты находятся в массиве toolBtns. Мы с помощью метода фор ич прикрепим к каждому инструменту слушатель на клик. И если пользователь кликнет по любому из инструментов, то мы добавим к этому инструменту класс active, и удалим класс эктив у того инструмента, у которого он был до этого.

и давайте чтобы мы знали какой инструмент выбран, создадим переменную selectedTool. По умолчанию зададим значение этой переменной как brush. А когда мы кликнем по другому инструменту, то поменяем значение переменной на id текущего активного инструмента. Давайте выведем еще значение этой переменной в консоль и посмотрим что она покажет. И как видите все инструменты выводятся. А при клике на заливку ничего не выводится, так как заливка не является инструментом и не имеет класс тул.

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

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

Если selectedTool равна браш или ирэзер, то мы применим одни и те же инструкции. А как написать так, чтобы если selectedTool равен brush или eraser, то выполнялся один и тот же код. Мы конечно можем продублировать инструкции для case brush и case eraser. Но есть способ проще. Мы просто сто напишем case браш, после двоеточия ничего не пишем, и затем сразу напишем кейс ирейзер. Как помните, если в теле кейс нет оператора break то выполнение сразу же перейдет к следующему кейс, в не зависимости верно ли значение этого кейс или нет.

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

А дальше пишем методы line то и строк.

Теперь давайте настроим инструмент прямоугольник. Напишем необходимый кейс. И внутри этого кейс просто запустим пока еще несуществующую функцию DrawRect и передадим ему в качестве параметра событие движение мыши.

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

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

А как нам получить изначальное положение курсора. Для этого создадим две переменные prevMouseX и prevMouseY. Которые будут отвечать за изначальную точку мыши, когда вы только нажмете на левую кнопку мышу.

Теперь в функции старт дроу зададим им текущие значения мыши, когда пользователь только нажмет на левую кнопку.

Далее в функции дроурект напишем метод строук с необходимыми значениями.

Рисуем, и видим что мы рисуем не один прямоугольник, а очень много. Так как нам метод дроуинг рисует их постоянно после того как мы нажали на левую кнопку мыши. А как нарисовать один прямоугольник нужного размера.

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

Теперь, когда мы нажмем на левую кнопку мыши, давайте сохраним текущее состояние холста.

В первом и втором параметре мы должны написать начальную точку для сохранения. Пусть будет 0 по икс и 0 по игрик. А в третьем и четвертом параметре нам надо ввести ширину и высоту сохраняемой области. Пусть будет ширина и высота нашего канваса.

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

В первом параметре нам необходимо добавить необходимый холст. Он у нас находится в переменной снепшот. А во втором и третьем параметре мы должны написать позицию по x и игрик левой верхней точки холста.

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

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

Теперь все работает хорошо. Теперь давайте добавим заливку, если мы нажмем на чекбокс заливка. Для начала зададим цвет заливки. Это можно сделать с помощью свойства филлстайл. И в методе дроурект напишем так.

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

Теперь создадим остальные варианты для свитч и соответствующие функции.

Далее напишем функцию для создания окружности. Для рисования окружности нам необходимо знать радиус между начальной точкой мыши и конечной. Здесь надо применить теорему Пифагора для расчета расстояния между предыдущими координатами мыши (prevMouseXprevMouseY) и текущими координатами мыши (e.offsetXe.offsetY).

Вся формула для подсчета радиуса будет выглядеть так:

Давайте создадим переменную radius и запишем туда формулу.

Далее чтобы создать круг, нам надо воспользоваться методом arc. Где в первых двух параметрах мы должны указать центр окружности по x и y. Далее мы указываем радиус, и в последних двух параметрах нам надо указать углы где линия окружности будет начинаться и заканчиваться. Углы указываются в радианах. Где линия будет начинаться мы укажем 0, что соответствует этой позиции. А чтобы нарисовать полную окружность нам надо указать 2Пи. Далее в зависимости от того, стоит ли галочка для заливки мы применил либо метод stoke если не стоит, или fill если стоит. Данный метод заполнит окружность указанным цветом.

Проверяем. У наша окружность ведет себя буд-то она заполненная. Но на самом деле это не так, просто при движении мыши у нас постоянно создаются связанные между собой окружности. Давайте я быстро поведу курсор мыши вправо. И вот как видите все окружности соединены такой линией. Чтобы этого избежать, надо в начале метода использовать метод beginpath, который заново будет создавать новую фигуру при движении мышкой. Для прямоугольника мы данный метод не указывали, так как он вшит в этот метод. А для линии нам это не нужно, так как нам нужны все связанные точки по которым строится линия. Укажем этот метод. И все работает корректно.

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

Далее укажем конечную точку для линии. Это мы можем сделать с помощью метода lineTo. И скопируем последнюю инструкцию у метода drawCircle. Проверяем. И первая линия треугольника готова. Теперь нам нужна это линия. Ее мы тоже можем нарисовать с помощью метода lineTo. Так как расстояние до этой точки в два раза больше этого расстояния то его можно посчитать так – prevMouseX * 2 – e.offsetX, а во втором параметре пишем текущую позицию мыши по y.

ПРоверяем. И нам осталось просто замкнуть эти две линии. Это можно сделать с помощью метода closePath, который просто делает линию фигуры непрерывной. Проверяем. Готово.

Далее давайте сделаем так, чтобы размер линий у инструментов зависел от слайдера. Для этого сперва создадим переменную brushWidth, значение по умолчанию у нее будет 5.

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

И нам осталось к тегу слайдера прикрутить слушатель.

Слайдер у нас находится в константе сайзслайдер. СОбытие мы будет отслеживать change. Вторым параметром будет стрелочная функция. Внутри которой мы будет задавать значение для переменной брашвид относительно текущей позиции слайдера. Проверяем. Все работает.

Теперь давайте реализуем изменение цвета. Для начала нам надо прикрутить слушатель к каждому тегу li с цветом.

При клике на какой либо тег li с цветом, у него должен появится класс selected. Что-то похожее мы уже делали с инструментами, поэтому здесь не будет для вас ничего нового.

И в конце мы должны для переменной selectedColor указать необходимый цвет. Это можно сделать так. Вызать метод getComputedStyle который покажет какие css свойства есть у конкретного тега. Этот конкретный тег мы напишем в качестве аргумента этого метода. Метод getComputed Style вернет объект CSSStyleDeclaratiom. И у этого объекта есть метод getProprtyStyle, в параметре которого мы можем написать интересующее нас свойство, значение которого мы хотим узнать. И вот данная цепочка методов вернет нам значение цвета выбранного тега ли.

Проверяем. Готово.

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

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

Нам осталось настроить кнопки. Сперва настроим кнопку очистить холст. Она реализуется очень легко, с помощью метода clearRect.

В первом и втором параметре указываем начальную точку, а дальше указываем ширину и высоту.

ПРоверяем. И осталось настроить сохранение результата в виде картинке. Будем мы сохранять результат в картинку точно также как мы это делали на прошлой практике

Проверяем.

Давайте проверим на всякий случай проверим консоль. И видим такую ошибку, которая говорит, что мы много пользуемся методом getImageData, и для оптимизации холста он рекомендует поместить в него этот аргумент. Так и сделаем

Проверяем. Теперь предупреждение не появляется. Сохраняем результат.

Как видим у нас осталась только одна проблема – это прозрачный фон на картинке. А это и будет вашим домашним заданием, чтобы у нас на был белый фон вместо прозрачного на сохраненной картинке.