Статьи

Использование HTML5 Canvas с Apache Wicket

Эта статья хочет дать несколько советов о том, как использовать HTML5 canvas с веб-фреймворком Apache Wicket. Внутри приложения Wicket мы хотим иметь панель с чем-то нарисованным внутри HTML5-холста. Чтобы это произошло, нам нужно подумать о следующем:

  1. Нужен ли нам HTML5?
  2. Если нам нужен HTML5, как это сделать?
  3. Что делать, если версия браузера представляет собой проблему и не поддерживает HTML5?

1. Сначала мы должны спросить, нужен ли нам HTML5

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

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

public class MyDynamicImageResource extends RenderedDynamicImageResource {
	
	private int width;
	private int height;
	private MyData data;						
	
	public MyDynamicImageResource (int width, int height, MYData data) {
		super(width, height);	
		this.width = width;
		this.height = height;
		this.data = data;
	}
	
	protected boolean render(Graphics2D g2) {
				
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);				

		// your code
	}
}

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

Затем мы можем использовать этот динамический ресурс для создания нашей панели. Мы будем использовать класс NonCachingImage от Wicket , подкласс Image, который добавляет случайный шум к URL при каждом запросе, чтобы браузер не кэшировал изображение. Если вас не волнует, что браузер кэширует ваше изображение, вам следует использовать простое изображение .

public class MyJava2DImagePanel extends Panel {
	
		
	private MyDynamicImageResource imageResource;
	
	
	public MyJava2DImagePanel(String id, final int width, final int height, final IModel<MyData> model) {
		super(id, model);		

		NonCachingImage image = new NonCachingImage("myImage", new PropertyModel(this, "imageResource")) {
			private static final long serialVersionUID = 1L;

			@Override
			protected void onBeforeRender() {				
				imageResource = new MyDynamicImageResource(width, height, model.getObject());
				super.onBeforeRender();
			}
		};
		
		add(image);		
				
	}								

}

HTML-файл разметки для MyJava2DImagePanel будет содержать изображение:

<wicket:panel>   
     <img wicket:id="myImage"></img>	         
</wicket:panel>

2. Если нам нужна анимация для нашего изображения, нам следует нарисовать ее на холсте HTML5.

Мы должны обратить внимание на то, чтобы рисовать вещи только один раз, то есть, например, если мы рисуем текст дважды в одной и той же позиции, тогда наш результат будет выглядеть некрасиво (с пиксельной лихорадкой), поскольку сглаживание для canvas нельзя установить так же, как для объекта Java2D Graphics.

Сначала нам нужно создать код Java-скрипта. Мы можем получить контекст Java 2d и использовать его для рисования нашего изображения. Я не буду говорить о контексте холста и его методах здесь.

Для анимации мы используем jquery в следующем фрагменте, но вы можете использовать все что угодно. Зная два значения (от, до), мы можем иметь, например, метод drawColor, который может рисовать различные сегменты, создавая таким образом эффект заполнения, который в этом примере принимает 1000 мс:

var myWidget = function(id, color) {	 

    var can = document.getElementById(id);
    var ctx = can.getContext('2d');   
  
    // clear canvas
    ctx.clearRect(0, 0, can.width, can.height);	 	
	
    // draw your image on ctx
    .....    

    // animate color fill
       $({ n: from }).animate({ n: to}, {
          duration: 1000,    
          step: function(now, fx) {
             drawColor(id, now);       
          } 
       });  
    }
   
}  

Во-вторых, мы должны создать нашу панель Wicket. Canvas — это просто WebMarkupContainer, и мы устанавливаем ширину и высоту с помощью некоторых AttributeAppenders:

public class MyHTML5Panel extends Panel {
	
	private final ResourceReference MY_JS = new JavaScriptResourceReference(MyHTML5Panel.class, "my.js");

	public MyHTML5Panel(String id, String width, String height, IModel<MyData> model) {
		super(id, model);
		WebMarkupContainer container = new WebMarkupContainer("canvas");
		container.setOutputMarkupId(true);
		container.add(new AttributeAppender("width", width));
		container.add(new AttributeAppender("height", height));		
		add(container);
	}
		
	@Override
    public void renderHead(IHeaderResponse response) {							
		
		response.renderOnLoadJavaScript(getJavascriptCall());
		
		//include js file
                response.renderJavaScriptReference(MY_JS);              
    }
	
	private String getJavascriptCall() {		 
		MyData data = getModel().getObject();
		StringBuilder sb = new StringBuilder();		
		sb.append("myWidget(\"").
		   append(get("canvas").getMarkupId()).
		   append("\",\"").append(data.getColor()).		   
		   append("\");");			
		return sb.toString();
	}
		

}

Метод renderHead (IHeaderResponse response) из Panel может использовать объект IHeaderResponse для рендеринга нашего вызова java-скрипта. Кроме того, на объекте ответа мы должны отобразить наш справочный файл java-скрипта.

Мы можем использовать один из следующих методов:

/**
	 * Renders javascript that is executed right after the DOM is built, before external resources
	 * (e.g. images) are loaded.
	 * 
	 * @param javascript
	 */
	public void renderOnDomReadyJavaScript(String javascript);

	/**
	 * Renders javascript that is executed after the entire page is loaded.
	 * 
	 * @param javascript
	 */
	public void renderOnLoadJavaScript(String javascript);

Бывают ситуации, когда нам нужно позвонить тому или другому в зависимости от нашего бизнеса. Например, если нам нужно предоставить наш компонент wicket внешнему iframe, мы должны вызвать onLoad вместо onDomReady, чтобы он появился внутри iframe, потому что $ (document) .ready в iframe, похоже, запускается слишком рано и содержимое iframe еще даже не загружен.

Файл разметки HTML MyHTML5Panel.html будет содержать тег canvas:

<wicket:panel>   
     <canvas wicket:id="canvas"></canvas>         
</wicket:panel>

3. Если мы решим использовать панель HTML5, но нам также придется подумать о более старом браузере, который не поддерживает тег canvas, нам нужно будет создать панель Java2D и HTML5 и посмотреть, что визуализировать самостоятельно. Решение состоит в том, чтобы иметь панель-обертку с контейнером, который изначально содержит EmptyPanel, и мы добавляем Wicket Behavior в контейнер. Это поведение будет выбирать, что визуализировать (HTML5 или простое изображение):

                .....   
                container = new WebMarkupContainer("container");
         	container.setOutputMarkupId(true);
                container.add(new EmptyPanel("image"));
		
                add(container);									           				
		add(new MyHTML5Behavior());		
                .......

Следующий код java-скрипта — это способ проверить, поддерживается ли тег canvas тегом браузера:

function isCanvasEnabled() {
	return !!document.createElement('canvas').getContext;
}

Эта функция начинается с создания фиктивного элемента <canvas>, который никогда не прикрепляется к странице,
поэтому никто его никогда не увидит. Как только мы создаем фиктивный элемент <canvas>, мы проверяем наличие метода getContext (). Этот метод будет существовать, только если браузер поддерживает API Canvas.
Наконец, мы используем двойную отрицательную уловку, чтобы привести результат к логическому значению (true или false).

Чтобы вызвать этот java-скрипт и сделать результат доступным для Wicket, мы используем javascript-метод wicketAjaxGet, как показано в следующем коде. Мы добавляем параметр результата к URL-адресу обратного вызова, и внутри метода response мы можем прочитать значение этого параметра.

class MyHTML5Behavior extends AbstractDefaultAjaxBehavior {
    	
    	private String width;
    	private String height;

        private String PARAM = "Param";

    	    			
		public MyHTML5Behavior() {
			super();
		}

		@Override
		public void renderHead(Component component, IHeaderResponse response) {			
			super.renderHead(component, response);					
			
			//include js file
	        response.renderJavaScriptReference(MY_UTIL_JS);
	        
	        response.renderOnLoadJavaScript(getJavascript());	
		}

		@Override
		protected void respond(AjaxRequestTarget target) {
			String param = this.getComponent().getRequest().getRequestParameters().getParameterValue(PARAM).toString();					
			// test if html5 canvas tag is supported
			if (Boolean.parseBoolean(param)) {
			    container.replace(new MyHTML5Panel("image", width, height, model).setOutputMarkupId(true));
			} else {					
			    container.replace(new MyImagePanel("image", width, height, model).setOutputMarkupId(true));
			}				
			target.add(container);				
						
		}
		
		
		// this javascript call will make the PARAM available to wicket and can be read in respond method
		private String getJavascript() {
			StringBuilder sb = new StringBuilder();
			sb.append("var data = isCanvasEnabled();");
			sb.append("wicketAjaxGet('" + getCallbackUrl() + "&" + PARAM + "='+ data" 
				+ ", null, null, function() { return true; })");
			return sb.toString();
		}
    }	

Это всего лишь несколько советов о том, как использовать холст HTML5 внутри платформы Apache Wicket. Я надеюсь, что это поможет другим.