example11.php HOME Examples overview Download Ask a question

AJAX-ZOOM (for developers)
tagging images with hotspots, adding title and description;
$.fn.axZm.createNewHotspot() API tutorial;

Ver. 4.2.1 - the old example got obsolete and has been replaced with a tutorial for developers of how to add "tags" to the images and let the users add title and description.

Click somewhere on the image. A hotspot is created. You can drag & drop it to adjust the position. Click on the hotspot to add / read title and description. You can right click on the hotspot to remove it (can be disabled).

HTML output console

Loading, please wait...
>> HTML console


This snippet is not ment to represent a final or complete solution! So the output of tagging is displayed in the "HTML console". Normally you would be saving the resulted JSON to a file, database, push to another persons display or perform some other fancy stuff with it. For example in a Digital-Asset-Management system the marketing manager could assign a task to edit the image in a certain way... If you need cross-origin communication please google for "window.postMessage";

As mentioned above this AJAX-ZOOM snippet is for developers who would like to extend and finish this code customizing it for their own needs. If you have a distinguished idea but do not want or do not have time to complete, you can ask AJAX-ZOOM team for a quote to do it for you.

JavaScript ans CSS in head

<!-- Include jQuery core into head section if not already present -->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>

<!-- JSON -->
<script type="text/javascript" src="../axZm/plugins/JSON/jquery.json-2.3.min.js"></script>

<!--  AJAX-ZOOM javascript -->
<script type="text/javascript" src="../axZm/jquery.axZm.js"></script>
<link type="text/css" href="../axZm/axZm.css" rel="stylesheet" />


/* cursor in tagging mode */
.azTagging {cursor: crosshair !important;}

/* title textfield */
.azTextField {width: 100%; margin-bottom: 5px; box-sizing: border-box !important; padding: 5px; font-family: arial; font-size: 12px; border: 1px solid #999999; border-radius: 3px;}

/* description textarea */
.azTextArea {width: 100%; height: 100px; box-sizing: border-box !important; padding: 5px; background-color: #FFF; font-family: arial; font-size: 12px; border: 1px solid #999999; border-radius: 3px;}

/* save, delete button */
.azButton {margin-top: 5px;}

/* message about click to place a hotspot */
.azTaggingMsg {position: absolute; background-color: #B50904; width: 290px; margin-left: -140px; top: -1px; left: 50%; border: #000 1px solid; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; font-size: 11px; color: #FFF; padding: 1px 5px 1px 5px; z-index: 1; pointer-events: none;}

/* parent container for navigation (mNavi) */
.azCustomNavi{background-color: #AAA; box-sizing: border-box; height: 58px; padding: 4px 0px 4px 0px; border-left: #000 1px solid; border-bottom: #000 1px solid; border-right: #000 1px solid;}

/* html console */
.azTaggingResults{background-color: #101010; color: #3cc628;padding: 5px; margin-top: 7px; height: 200px; overflow: hidden; overflow-y: auto;}
.azPre{tab-size: 2; -moz-tab-size: 2; margin: 0; font-size: 11px; font-family: monospace;}

/* Overwrite css from /axZm/jquery.axZm.js */
.axZmToolTipInner {background-color: #c5d8e1; border-color: #5583b4; border-width: 3px;}

.axZmToolTipTitle {color: #FFF; /* #1a4a7a*/ font-size: 16px; line-height: 18px; min-height: 24px; text-shadow: 0px 0px 2px rgba(150, 150, 150, 0.75);}

.axZm_zoomCustomNaviParentID {margin: 0 auto;}

HTML in body

azTaggingResults shows JSON created after placing the hotspots, so it is not needed in your final code

<!-- Div where AJAX-ZOOM is loaded into -->
<div id="azParentContainer" style="min-height: 462px;">Loading, please wait...</div>

<!-- Parent container for "mNavi" -->
<div id="azCustomNavi" class="azCustomNavi"></div>

<!-- "console" to display results which could / should be saved -->
<div id="azTaggingResults" class="azTaggingResults">>> HTML console</div>


Every line is commented... The whole code could be ofcourse wraped into external JS. If after all you have any questions regarding AJAX-ZOOM - do not hesitate to contact us please.

// some var to hold everyting else
window.ajaxZoom = {};

// the ID of the div element where ajax-zoom has to be inserted into
ajaxZoom.divID = "azParentContainer";	

// path to the axZm folder
ajaxZoom.path = "../axZm/"; 

// zoomData - defines which image should be loaded
ajaxZoom.parameter = "zoomData=/pic/zoom/furniture/furniture_005.jpg";

// example - defines which "options set" is taken
// options in /axZm/zoomConfig.inc.php are overriden in /axZm/zoomConfigCustom.inc.php  
// after elseif ($_GET['example'] == 'tagging'){
ajaxZoom.parameter += "&example=tagging";

// Local time ahead server time (PHP solution), not needed, you can set timeDiff to 0
ajaxZoom.timeDiff = (new Date()).getTime();

// switch for tagging mode
ajaxZoom.taggingMode = true;

// some var where we will store user defined title and description
ajaxZoom.myTags = {};

// define some text strings
ajaxZoom.taggingTxt = {
	"msg": "&raquo; click somewhere to place a hotspot; right-click to remove",
	"disable": "Disable tagging mode",
	"enable": "Enable tagging mode",
	"title": "Title",
	"description": "Description",
	"confirmDelete": "Really delete?",
	"notes": "Notes",
	"noDescr": "No notes left"

// add tagging message and manage other tasks when tagging mode is enabled
ajaxZoom.setTaggingMsg = function(){
	// append tagging message 
	if (!$("#azTaggingMsg").length){
		$("<div />").attr("id", "azTaggingMsg").addClass("azTaggingMsg")
		$("#azTaggingMsg").css("display", "block");
	// change cursor
	// change src and description of the custom button which changes states
	.data("btnSrc", $.axZm.icon + $.axZm.buttonSet + "/button_iPad_tag_switched.png")
	.data("bAlt", ajaxZoom.taggingTxt.disable)
	.attr("src", $.axZm.icon + $.axZm.buttonSet + "/button_iPad_tag_switched.png");
	// make hotspots draggable

// hide tagging message and manage other tasks when tagging mode is disabled
ajaxZoom.removeTaggingMsg = function(){
	// hide tagging message
	$("#azTaggingMsg").css("display", "none");
	// remove class which changed the cursor
	// change src and description of the custom button which changes states
	.data("btnSrc", $.axZm.icon + $.axZm.buttonSet + "/button_iPad_tag.png")
	.data("bAlt", ajaxZoom.taggingTxt.enable)
	.attr("src", $.axZm.icon + $.axZm.buttonSet + "/button_iPad_tag.png");
	// make hotspots not traggable any more
	// close all opened tooltips

// function which will be executed in onZoomInClickStart AJAX-ZOOM callback
// important is that when you do not want AJAX-ZOOM to zoom, this function should return false;
ajaxZoom.evaluateClick = function(info){
	// do not do anything if ajaxZoom.taggingMode (a switch var) is false
	if (!ajaxZoom.taggingMode){return;}
	// position of the click related to its original size
	var xClick = info.viewport.x;
	var yClick = info.viewport.y;
	// file (image) name
	var currentImageName = $.axZm.zoomGA[$.axZm.zoomID].img;
	// position of the hotspot in this file
	// there could be same hotspot on an image in the gallery, 
	// this is why we need image name here and it is defined this way
	var posObj = {}; posObj[currentImageName] = {left: xClick, top: yClick};
	// show hotspots in case they are disabled
	// after we know the position create hotspot "on-the-fly"
		// generate some image name 
		// image name could contain the creation date and time
		// you might also want to get it from server before creating hotspot 
		// or calculate the difference between server time and client time as it is done here
		// prepend the hotspot name with some random string anyway
		name: Math.random().toString(36).substring(2) + "_" + ((new Date()).getTime() - ajaxZoom.timeDiff), 
		autoTitle: false, // we do not want alt title to be like hotspot name
		autoPos: false, // we know at wich positions to put hotspot at (posObj)
		noInit: false, // we want that the hotspot is added right away
		draggable: true, // and it should be draggable
		noRightClickRemove: false, // could be removed with right mouse click
		posObj: posObj, // our coordinates
		settings: {
			shape: "point", // shape is point (not rect)
			altTitle: false, // mouseover title is disabled
			labelGravity: "bottom", // position of the title shown as label
			labelOffsetY: -2, // vertical offset of the label
			hotspotImage: "hotspot64_map_red.png", // default image from /axZm/icons
			gravity: "top", // important - display hotspot image above the click point
			width: 32, // width of the icon
			height: 32, // height of the icon
			/* tooltip settings */
			toolTipWidth: 300, // width of the tooltip
			toolTipHeight: 120, // min-height of the tooltip
			toolTipGravity: "left", // show tooltip left to the hotspot
			toolTipAutoFlip: true, // but also right depending on position
			toolTipAdjustX: 25, // horizontal space between hotspot and toolTip
			toolTipCloseIcon: "close_blue_16.png", // close button icon from /axZm/icons
			toolTipCloseIconOffset: {right: 5, top: 5}, // position of the close button
			toolTipOverlayShow: false, // do not show overlayes
			// toolTipTitle can be also a JS function 
			toolTipTitle: ajaxZoom.toolTipTitle,
			// toolTipHtml can be also a JS function which returns something
			toolTipHtml: ajaxZoom.toolTipHtml
		// callback after hotspot is added
		callback: function(info){
			// we have created the hotspot so update ajaxZoom.myTags
			ajaxZoom.myTags[info.name] = {};
			// save / update console
			// trigger tooltip after it has been added
			// if you remove the line below the user would need to extra click on the hotspot
	// important to return false; otherwise AJAX-ZOOM will zoom
	return false; 

// delete hotspot wrapper
ajaxZoom.deleteHotspot = function(name){
	// ask if the user wants to delete the hotspot
	var sureDelete = window.confirm(ajaxZoom.taggingTxt.confirmDelete);
	if (sureDelete){
		// delete hotspot
		// close all tooltips

// title which is shown in the popup when the user clicks on the hotspot
ajaxZoom.toolTipTitle = function(info){
	// we simply return a string but it could be extended
	return ajaxZoom.taggingTxt.notes;

// html which is shown in the popup when the user clicks on the hotspot
ajaxZoom.toolTipHtml = function(info){
	// get already prsent information sored in ajaxZoom.myTags
	var myTags = ajaxZoom.myTags[info.name] || {},
		ret = ""; // empty string
	// if tagging mode return form fields
	if (ajaxZoom.taggingMode){
		// simple form
		ret = "<div>";
			// title
			ret += "<input type=\"text\" id=\"azTextField\" class=\"azTextField\" value=\"" + (info.labelTitle || ajaxZoom.taggingTxt.title) + "\">";
			// description
			ret += "<textarea id=\"azTextArea\" class=\"azTextArea\">" + (myTags.descr || ajaxZoom.taggingTxt.description) + "</textarea>";
			// name of the hotspot
			ret += "<input id=\"azTextHotspotName\" type=\"hidden\" value=\"" +info.name + "\">";
			// save and delete buttons
			ret += "<div style=\"text-align: right\">";
				ret += "<input type=\"button\" class=\"azButton\" value=\"Delete\" onclick=\"ajaxZoom.deleteHotspot('" + info.name + "');\">";
				ret += "<input type=\"button\" class=\"azButton\" value=\"Save\" onclick=\"ajaxZoom.saveInfo()\">";
			ret += "</div>";
		ret += "</div>";
			// Prevent bubbling when clicked on the textarea
			$("#azTextArea, #azTextField").on("mousedown touchstart", function(e){e.stopPropagation();});
			// Show names of the form fields within formfields and remove them on focus
			$("#azTextField").on("focus", function(e){
				if ($(this).val() == ajaxZoom.taggingTxt.title){$(this).val("");}
			}).on("blur", function(e){
				if ($(this).val() == ""){$(this).val(ajaxZoom.taggingTxt.title);}
			$("#azTextArea").on("focus", function(e){
				if ($(this).val() == ajaxZoom.taggingTxt.description){$(this).val("");}
			}).on("blur", function(e){
				if ($(this).val() == ""){$(this).val(ajaxZoom.taggingTxt.description);}
		}, 100);
	// return when tagging mode is disabled
	else {
		// Calculate time from hotspot name (if not changed)
		var time = info.name.split("_")[1];
		// Date from Unix timestamp
		var date = new Date(parseInt(time));
		// Return what is stored in ajaxZoom.myTags.descr
		ret = "<div class='azTextArea'>"+(myTags.descr || ajaxZoom.taggingTxt.noDescr)+"</div>\
		Created: " + date + "\
	return ret;

// store description and title under ajaxZoom.myTags 
ajaxZoom.saveInfo = function(){
	// read values from formfields 
	var name = $("#azTextHotspotName").val();
	var title = $("#azTextField").val();
	var descr = $("#azTextArea").val();
	// calculate date and time
	var time = (new Date()).getTime() - ajaxZoom.timeDiff;
	// create new / emtty object under ajaxZoom.myTags
	ajaxZoom.myTags[name] = {};
	// store description from formfields
	if (descr != ajaxZoom.taggingTxt.description){
		ajaxZoom.myTags[name].descr = descr;
		ajaxZoom.myTags[name].lastChanged = time;
	// store title from formfields
	if (title != ajaxZoom.taggingTxt.title){
		ajaxZoom.myTags[name].title = title;
		ajaxZoom.myTags[name].lastChanged = time;
		// Update label title
		$.axZm.hotspots[name].labelTitle = title;
		// Redraw hotspot with $.fn.axZm.initHotspots and make them draggable again
		$.fn.axZm.initHotspots(null, $.fn.axZm.hotspotsDraggable);
	// close tooltip
	// save / update console
// save / update console function 
// this is the function which you would need to extend
ajaxZoom.updateConsole = function(){
	var json = {};
	// eterate over ajaxZoom.myTags and gather information you would like to store
	$.each(ajaxZoom.myTags, function(name, obj){
		if ($.axZm.hotspots[name] 
			&& $.axZm.hotspots[name]["position"] 
			&& !$.isEmptyObject($.axZm.hotspots[name]["position"])){
			json[name] = {
				title: obj.title,
				descr: obj.descr,
				timestamp: obj.lastChanged,
				position: $.fn.axZm.convertHotspotPositionToPx($.axZm.hotspots[name]["position"][$.axZm.zoomID])
	// here we simply visually show that created json without any other action
	$("#azTaggingResults").html("<pre class='azPre'>" + JSON.stringify(json, null, "\t")+"</pre>");
	// todo: for example save it to session and restore when loaded	

// AJAX-ZOOM callbacks
ajaxZoom.opt = {
	// some (not all) options from /axZm/zoomConfig.inc.php and 
	// from /axZm/zoomConfigCustom.inc.php 
	// could be set in this "onBeforeStart" callback
	onBeforeStart: function(){
		// Remove hotspot entirely when right clicked on it
		// normally it is only disabled on the current image
		$.axZm.hsRightClickDel = true;
		// Do not zoom out at 100% on click
		$.axZm.zoomOutClick = false;
		// enable and configure the "mNavi" option
		// which is the toolbar below the player or in the player
		$.axZm.mNavi.enabled = true; // enable it
		$.axZm.mNavi.parentID = "azCustomNavi"; // set ID where it has to be appended to
		$.axZm.mNavi.buttonDescr = true; // enable description of the buttons
		$.axZm.mNavi.alt.enabled = false; // disable description simmilar to alt
		$.axZm.mNavi.fullScreenShow = true; // also show "mNavi" at fullscreen mode
		$.axZm.mNavi.offsetVertFS = 10; // vertical offset of mNavi at fullscreen mode
		// this is a list of buttons which we want to show in the toolbar
		// number value is margin to the next button
		$.axZm.mNavi.order = {
			mZoomOut: 5, // zoom out button
			mZoomIn: 20, // zoom in button
			mReset: 20, // reset button
			mPan: 5, // pan mode button
			mCrop: 20, // crop mode button
			mHotspots: 5, // show / hide hotspots button
			mCustomBtn1: 0 // our "whatever" button
		// there can be as many "whatever" (custom) buttons as you want
		// call them mCustomBtn1, mCustomBtn2, ...
		// now we define how this mCustomBtn1 should look like
		$.axZm.icons.mCustomBtn1 = {file: $.axZm.buttonSet + "/button_iPad_tag", ext: "png", w: 50, h: 50};
		// and the title of mCustomBtn1
		$.axZm.mapButTitle.customBtn1 = ajaxZoom.taggingTxt.disable; // description of the button
		// attach a JS function to the mCustomBtn1
		$.axZm.mNavi.mCustomBtn1 = function(){
			// when tagging mode is already on, disable it
			if (ajaxZoom.taggingMode == true){
				// Update state of the tagging mode
				ajaxZoom.taggingMode = false;
				// Do other things
			// enable tagging mode
			else {
				// Update state of the tagging mode
				ajaxZoom.taggingMode = true;
				// Do other things

	// when image loads
	onLoad: function(){
		// Add message that tagging mode is activated and activate it
	// callback executed on any hotspot deletion over API
	onHotspotDelete: function(name){
		// Save / update console
	// callback triggered after hotspot is moved
	onHotspotsDragEnd: function(){
		// Save / update console
	// callback triggered when the user clicks on the image
	onZoomInClickStart: function(info){
		return ajaxZoom.evaluateClick(info);

// load AJAX-ZOOM not responsive
		opt: ajaxZoom.opt,
		path: ajaxZoom.path,
		parameter: ajaxZoom.parameter,
		divID: ajaxZoom.divID

// open AJAX-ZOOM responsive
// Documentation - http://www.ajax-zoom.com/index.php?cid=docs#api_openFullScreen
	ajaxZoom.path, // Absolute path to AJAX-ZOOM directory, e.g. "/axZm/"
	ajaxZoom.parameter, // Defines which images and which options set to load
	ajaxZoom.opt, // callbacks
	ajaxZoom.divID, // target - container ID (default "window" - fullscreen)
	false, // apiFullscreen- use browser fullscreen mode if available
	true, // disableEsc - prevent closing with Esc key
	false // postMode - use POST instead of GET

Comments (0)

Leave a Comment

Name (required):
Email (required):
Your comment (no html):
Looking for a place to add a personal image? Visit www.gravatar.com to get Your own gravatar, a globally-recognized avatar. After you're all setup, Your personal image will be attached every time you comment.