;(function($, window, document, undefined){ var pluginName = 'circleMenu', defaults = { depth: 0, item_diameter: 30, circle_radius: 80, angle:{ start: 0, end: 90 }, speed: 500, delay: 1000, step_out: 20, step_in: -20, trigger: 'hover', transition_function: 'ease' }; function vendorPrefixes(items,prop,value){ ['-webkit-','-moz-','-o-','-ms-',''].forEach(function(prefix){ items.css(prefix+prop,value); }); } function CircleMenu(element, options){ this._timeouts = []; this.element = $(element); this.options = $.extend({}, defaults, options); this._defaults = defaults; this._name = pluginName; this.init(); this.hook(); } CircleMenu.prototype.init = function(){ var self = this, directions = { 'bottom-left':[180,90], 'bottom':[135,45], 'right':[-45,45], 'left':[225,135], 'top':[225,315], 'bottom-half':[180,0], 'right-half':[-90,90], 'left-half':[270,90], 'top-half':[180,360], 'top-left':[270,180], 'top-right':[270,360], 'full':[-90,270-Math.floor(360/(self.element.children('li').length - 1))], 'bottom-right':[0,90] }, dir; self._state = 'closed'; self.element.addClass(pluginName+'-closed'); if(typeof self.options.direction === 'string'){ dir = directions[self.options.direction.toLowerCase()]; if(dir){ self.options.angle.start = dir[0]; self.options.angle.end = dir[1]; } } self.menu_items = self.element.children('li:not(:first-child)'); self.initCss(); self.item_count = self.menu_items.length; self._step = (self.options.angle.end - self.options.angle.start) / (self.item_count-1); self.menu_items.each(function(index){ var $item = $(this), angle = (self.options.angle.start + (self._step * index)) * (Math.PI/180), x = Math.round(self.options.circle_radius * Math.cos(angle)), y = Math.round(self.options.circle_radius * Math.sin(angle)); $item.data('plugin_'+pluginName+'-pos-x', x); $item.data('plugin_'+pluginName+'-pos-y', y); $item.on('click', function(){ self.select(index+2); }); }); // Initialize event hooks from options ['open','close','init','select'].forEach(function(evt){ var fn; if(self.options[evt]){ fn = self.options[evt]; self.element.on(pluginName+'-'+evt, function(){ return fn.apply(self,arguments); }); delete self.options[evt]; } }); self.submenus = self.menu_items.children('ul'); self.submenus.circleMenu($.extend({},self.options,{depth:self.options.depth+1})); self.trigger('init'); }; CircleMenu.prototype.trigger = function(){ var args = [], i, len; for(i = 0, len = arguments.length; i < len; i++){ args.push(arguments[i]); } this.element.trigger(pluginName+'-'+args.shift(), args); }; CircleMenu.prototype.hook = function(){ var self = this; if(self.options.trigger === 'hover'){ self.element.on('mouseenter',function(evt){ self.open(); }).on('mouseleave',function(evt){ self.close(); }); }else if(self.options.trigger === 'click'){ self.element.children('li:first-child').on('click',function(evt){ evt.preventDefault(); if(self._state === 'closed' || self._state === 'closing'){ self.open(); }else{ self.close(true); } return false; }); }else if(self.options.trigger === 'none'){ // Do nothing } }; CircleMenu.prototype.open = function(){ var self = this, $self = this.element, start = 0, set; self.clearTimeouts(); if(self._state === 'open') return self; $self.addClass(pluginName+'-open'); $self.removeClass(pluginName+'-closed'); if(self.options.step_out >= 0){ set = self.menu_items; }else{ set = $(self.menu_items.get().reverse()); } set.each(function(index){ var $item = $(this); self._timeouts.push(setTimeout(function(){ $item.css({ left: $item.data('plugin_'+pluginName+'-pos-x')+'px', top: $item.data('plugin_'+pluginName+'-pos-y')+'px' }); vendorPrefixes($item,'transform','scale(1)'); }, start + Math.abs(self.options.step_out) * index)); }); self._timeouts.push(setTimeout(function(){ if(self._state === 'opening') self.trigger('open'); self._state = 'open'; },start+Math.abs(self.options.step_out) * set.length)); self._state = 'opening'; return self; }; CircleMenu.prototype.close = function(immediate){ var self = this, $self = this.element, do_animation = function do_animation(){ var start = 0, set; self.submenus.circleMenu('close'); self.clearTimeouts(); if(self._state === 'closed') return self; if(self.options.step_in >= 0){ set = self.menu_items; }else{ set = $(self.menu_items.get().reverse()); } set.each(function(index){ var $item = $(this); self._timeouts.push(setTimeout(function(){ $item.css({top:0,left:0}); vendorPrefixes($item,'transform','scale(.5)'); }, start + Math.abs(self.options.step_in) * index)); }); self._timeouts.push(setTimeout(function(){ if(self._state === 'closing') self.trigger('close'); self._state = 'closed'; },start+Math.abs(self.options.step_in) * set.length)); $self.removeClass(pluginName+'-open'); $self.addClass(pluginName+'-closed'); self._state = 'closing'; return self; }; if(immediate){ do_animation(); }else{ self._timeouts.push(setTimeout(do_animation,self.options.delay)); } return this; }; CircleMenu.prototype.select = function(index){ var self = this, selected, set_other; if(self._state === 'open' || self._state === 'opening'){ self.clearTimeouts(); set_other = self.element.children('li:not(:nth-child('+index+'),:first-child)'); selected = self.element.children('li:nth-child('+index+')'); self.trigger('select',selected); vendorPrefixes(selected.add(set_other), 'transition', 'all 500ms ease-out'); vendorPrefixes(selected, 'transform', 'scale(2)'); vendorPrefixes(set_other, 'transform', 'scale(0)'); selected.css('opacity','0'); set_other.css('opacity','0'); self.element.removeClass(pluginName+'-open'); setTimeout(function(){self.initCss();},500); } }; CircleMenu.prototype.clearTimeouts = function(){ var timeout; while(timeout = this._timeouts.shift()){ clearTimeout(timeout); } }; CircleMenu.prototype.initCss = function(){ var self = this, $items; self._state = 'closed'; self.element.removeClass(pluginName+'-open'); self.element.css({ 'list-style': 'none', 'margin': 0, 'padding': 0, 'width': self.options.item_diameter+'px' }); $items = self.element.children('li'); $items.attr('style',''); $items.css({ 'display': 'block', 'width': self.options.item_diameter+'px', 'height': self.options.item_diameter+'px', 'text-align': 'center', 'line-height': self.options.item_diameter+'px', 'position': 'absolute', 'z-index': 1, 'opacity': '' }); self.element.children('li:first-child').css({'z-index': 1000-self.options.depth}); self.menu_items.css({ top:0, left:0 }); vendorPrefixes($items, 'border-radius', self.options.item_diameter+'px'); vendorPrefixes(self.menu_items, 'transform', 'scale(.5)'); setTimeout(function(){ vendorPrefixes($items, 'transition', 'all '+self.options.speed+'ms '+self.options.transition_function); },0); }; $.fn[pluginName] = function(options){ return this.each(function(){ var obj = $.data(this, 'plugin_'+pluginName), commands = { 'init':function(){obj.init();}, 'open':function(){obj.open();}, 'close':function(){obj.close(true);} }; if(typeof options === 'string' && obj && commands[options]){ commands[options](); } if(!obj){ $.data(this, 'plugin_' + pluginName, new CircleMenu(this, options)); } }); }; })(jQuery, window, document);