Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
В данном уроке будет описано создание и подключение модуля рисования paintSprites для редактора Colllage_n и за одно будет рассмотрен принцип написания модулей на фреймворке Htmlix. Данный модуль позволяет создавать кисти на основе какого либо спрайта и рисовать ею как в обычном редакторе paint. Таким образом можно отредактировать и вырезать кусочек изображения какой небудь картинки, создать из нее спрайт и рисовать им как обычным карандашом или кистью, а также вращать узор кисти по оси или масштабировать ее колесиком мыши. Например вырезать цветок из какого либо изображения отформатировать его и рисовать.
![](https://habrastorage.org/getpro/habr/upload_files/bec/53f/90d/bec53f90da6c72b1ee59f38c07f83c91.png)
Редактор работает онлайн, для запуска на локальном сервере необходим node.js, необходимо скопировать файлы редактора, файл модуля paintSprites.js в папку modules, в командной строке из корневой папки запустить node app
и прейти по ссылке http://localhost:3000/.
После загрузки редактора модуль нужно будет включить во вкладке Загрузить модуль, изменить настройки прописав адрес модуля "./js/modules/paintSprites.js"
и нажать кнопку загрузить модуль. Можно также сохранить его в текстовых настройках для автозагрузки в дальнейшем.
Краткий обзор корневого приложения
Collage_n написан на фреймворке htmlix
это позволяет загружать модули динамически в процессе работы для удобства. Для начала давайте разберемся с базовыми принципами создания модуля для редактора. В файле interface.js находятся обработчики для кнопок корневого приложения, общие переменные, методы и пользовательские события для всего приложения (в конце файла).
["emiter-create-sprite"] : {prop: ""}, //событие создания спрайта
["emiter-mousemove-canvas"] : {prop: ["x", "y"]}, //событие движения курсора по канвас и массив с координатами точки
["emiter-mousedown-canvas"] : {prop: ["x", "y"]}, //событие клика по канвас с координатами
["emiter-mouseup-canvas"] : {prop: ["x", "y"]}, //событие окончания клика с координатами
["emiter-operation-with"] : { //событие смены выполняемой операции и метод для сохранения названия текущей операции в глобальную переменную operationWith нную
prop: "common",
behavior: function(){
this.$props().operationWith = this.prop;
}
},
Пользовательские события нужны чтобы сообщить всем компонентам которые на него подписаны о каком либо событии корневого приложения, их можно вызвать из любой точки приложения или в модуле this.$$("emiter-имя-события").set("данные для подписчиков");
Компонент canvas имеет три метода обработчика событий: mousedown - обработка клика по канвасу для фоновой картинки if(this.$props("operationWith") == "common")
либо для спрайта if(this.$props().sprites[this.props("operationWith")])
два других mousemove и mouseup работают аналогично в них мы проверяем значение глобальной переменной "operationWith"
если это операция со спрайтом то в данной пременной будет его id.
["emiter-create-sprite"] : {prop: ""}, //событие создания спрайта
["emiter-mousemove-canvas"] : {prop: ["x", "y"]}, //событие движения курсора по канвас и массив с координатами точки
["emiter-mousedown-canvas"] : {prop: ["x", "y"]}, //событие клика по канвас с координатами
["emiter-mouseup-canvas"] : {prop: ["x", "y"]}, //событие окончания клика с координатами
["emiter-operation-with"] : { //событие смены выполняемой операции и метод для сохранения названия текущей операции в глобальную переменную operationWith нную
prop: "common",
behavior: function(){
this.$props().operationWith = this.prop;
}
},
Остальные файлы: sprites.js оъект для создания и сохранения спрайтов CollageSprite на компьютере, в интерфейсе они представлены массивом sprites и связаны с обьектами CollageSprite через глобальную пременную - объект $props().sprites
через id, draw_functions.js - просто глобальные функции для работы с canvas: попиксельная обработка, масштабирование, вращение области выделения и изображения и т.д.
Создание модуля
Для начала создадим разметку модуля: кнопка включения, два поля ввода для размера и цвета окружности которой будем рисовать, кнопка переключения на рисование спрайтами.
//создаем модуль
//onloadModules - глобальный объект список загружаемых модулей и скриптов main.js
(function(){
if(onloadModules.paintSprites != undefined)return;
var html = `
<div data-paint_sprites_panel="container" class="form-group" name="paint_sprites_panel">
<label for="exampleFormControlInput1" style="font-size: 15px; color: red; font-weight: bold;">Рисовать</label>
<div class="form-row">
<div class="form-group col-md-3">
<button type="button" style="" name="paint_btn" class="btn btn-danger btn-sm" title="Для рисования нужно выбрать спрайт нажать кнопку и рисовать с нажатой кнопкой мыши">paint</button>
</div>
<div class="form-group col-md-3">
<input name="draw_sircle_radius" type="text" class="form-control form-control-sm" placeholder="радиус" title="" value="5">
</div>
<div class="form-group col-md-3">
<input name="draw_sircle_color" type="text" class="form-control form-control-sm" placeholder="цвет" title="" value="red">
</div>
<div class="form-group col-md-3">
<button type="button" style="" name="paint_from_spraites_btn" class="btn btn-danger btn-sm" title="Рисовать текущим спрайтом">спрайтом</button>
</div>
</div>
</div>
`;
//добавление разметки модуля в общюю разметку обычными функциями javascript в любое место
//создали div нашли куда вставить "[data-main-form]" и и перед кем "[name='common_btn_class']"
var div = document.createElement("div");
div.innerHTML = html;
div = div.querySelector("div");
var parent = document.querySelector("[data-main_form]");
var insert_before = document.querySelector("[name='common_btns_class']")
var insertedElement = parent.insertBefore(div, insert_before);
//объект описания модуля
var paint_sprites_panel = {
container: "paint_sprites_panel",
props: [ ],
methods: {}
}
//добавили описание модуля в описание всего приложения.
HM.description.paint_sprites_panel = paint_sprites_panel;
//загрузили модуль
HM.containerInit(div , HM.description, "paint_sprites_panel");
HM.eventProps["emiter-operation-with"].emit(); //вызываем пустым (без параметра) чтобы отключить слушателей canvas событий при старте модуля другими модулями
//поставили флаг для избежания случайной двойной загрузки модуля
onloadModules.paintSprites = true;
})()
Для того чтобы в неактивном состоянии наш модуль не прослушивал глобальные события клика по канвас и перемещения по нему курсора - добавим слушатель события смены текущей операции operation_with
и если модуль не активен != paint-sprites
, будем отключать обработчики.
props: [
["canvas_click", "emiter-mousedown-canvas", ""],
["canvas_move", "emiter-mousemove-canvas", ""],
["mouse_up", "emiter-mouseup-canvas", ""],
//Отключать и включать мы их будем в слушателе события
["operation_with", "emiter-operation-with", ""],
]
methods: {
operation_with: function(){ //отключает слушателей canvas событий ( mousedown) если модуль находится в пассивном состоянии
if(this.emiter.prop != "paint-sprites" ){
this.parent.props.canvas_click.disableEvent();// отключаем события
this.parent.props.canvas_move.disableEvent();
this.parent.props.mouse_up.disableEvent();
}else{
this.parent.props.canvas_click.enableEvent();
this.parent.props.canvas_move.enableEvent();
this.parent.props.mouse_ap.enableEvent();
}
}
}
Далее добавим обработчик кнопке рисовать
props: [
["paint_btn", "click", "[name='paint_btn']"], //находим кнопку в разметке по ее имени (будет искать относительно контейнера в котором она находится)
paint_btn: function(){ //добавляем обработчик клику по кнопке
//вызываем пользовательское событие с именем операции(имя модуля) чтобы включить обработчики канвас и сказать другим модулям в разных частях приложения отключить свои обработчики или выполнить другие действия (скрыть лишние кнопки и т.д.)ия о
this.$$("emiter-operation-with").set("paint-sprites");
} ,
Теперь давайте создадим три обработчика события нажатия клавиши мыши, движения курсора и поднятия клавиши мыши, события у нас из корневого приложения (мы их уже подключили), поэтому останется таперь создать к ним методы, также еще создадим два свойства для размера и цвета кисти.
props:[
//добавили два свойства для размера и цвета кисти
//...........................
["draw_sircle_radius", "inputvalue", "[name='draw_sircle_radius']"],
["draw_sircle_color", "inputvalue", "[name='draw_sircle_color']"],
]
canvas_click: function(){//нажатие кнопки
//сохранить фоновое изображение до рисования, чтобы можно было отменить functions.js
saveStep(saveImg, this.$props().commonProps.area_1);
ctx.save();
ctx.putImageData(saveImg, 0, 0);
////////////////////////
//рисуем круг данные берем из формы ввода цвета и размера
//this.emiter.prop доступ к параметру пользовательского события- координаты клика по x и у осям вычисляется в корневом приложении
ctx.beginPath();
ctx.arc(this.emiter.prop[0], this.emiter.prop[1], this.parent.props.draw_sircle_radius.getProp(), 0, 2*Math.PI, false);
ctx.fillStyle = this.parent.props.draw_sircle_color.getProp();
ctx.fill();
ctx.lineWidth = 0.1;
ctx.strokeStyle = this.parent.props.draw_sircle_color.getProp();
ctx.stroke();
///сохраняем данные для метода ниже чтобы не брать их постоянно из формы ввода при движении курсора
this.parent.props.canvas_move.prop = {
color: this.parent.props.draw_sircle_color.getProp(),
radius: this.parent.props.draw_sircle_radius.getProp(),
}
},
canvas_move: function(){
//тоже самое и при движении курсора мыши
if(this.prop != null){ //проверяем наличие параметров чтобы не рисовать с когда кнопка поднята
ctx.beginPath();
ctx.arc(this.emiter.prop[0], this.emiter.prop[1], this.prop.radius, 0, 2*Math.PI, false);
ctx.fillStyle = this.prop.color;
ctx.fill();
ctx.lineWidth = 0.1;
ctx.strokeStyle = this.prop.color;
ctx.stroke();
}
},
mouse_up: function(){
//удаляем данные для отмены рисования
this.parent.props.canvas_move.prop = null;
//сохраняем изображение в глобальную переменную saveImage чтобы можно было нарисовать сверху спрайты, контур и т.д.
//Для будущих преобразований картинки фона данные берутся из нее (main.js)
saveImg = ctx.getImageData(0,0, srcWidth, srcHeight);
ctx.restore();
},
Теперь модуль готов для рисования обычным карандашем, цвета можно задавать любыми удобными форматами "rgba(32, 45, 21, 0.3)", red, #6610f2
и т.д. Полный код модуля вместе с рисованием спрайтами можно посмотреть в здесь.