Пишем javascript модуль wysiwyg редактор изображений Collage_n

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

В данном уроке будет описано создание и подключение модуля рисования paintSprites для редактора Colllage_n и за одно будет рассмотрен принцип написания модулей на фреймворке Htmlix. Данный модуль позволяет создавать кисти на основе какого либо спрайта и рисовать ею как в обычном редакторе paint. Таким образом можно отредактировать и вырезать кусочек изображения какой небудь картинки, создать из нее спрайт и рисовать им как обычным карандашом или кистью, а также вращать узор кисти по оси или масштабировать ее колесиком мыши. Например вырезать цветок из какого либо изображения отформатировать его и рисовать.

Редактор работает онлайн, для запуска на локальном сервере необходим 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 и т.д. Полный код модуля вместе с рисованием спрайтами можно посмотреть в здесь.

Источник: https://habr.com/ru/post/646457/


Интересные статьи

Интересные статьи

Глазами начинающего С++ программиста оцениваем удобство современных возможностей языка на примере реализации программы для лексического разбора и парсинга данных JSON.
Блокировка изображений – одна из самых серьезных проблем, с которой сталкиваются маркетологи, когда проводят email-кампании. Обычно причина кроется в настройках по умолча...
Стековые машины используются в большом множестве современных языков программирования. Они просты для понимания и при этом достаточно эффективны. Хотите попробовать одну такую в действии? Все ...
Введение Цель этого проекта — создание клона движка DOOM, использующего ресурсы, выпущенные вместе с Ultimate DOOM (версия со Steam). Он будет представлен в виде туториала — я не хочу добив...
Здравствуйте, хабраюзеры. Сегодня я хочу рассказать о том, как написать свой простенький NTP клиент. В основном, разговор зайдет о структуре пакета и способе обработки ответа с NTP сервера. Код б...