jquery.circlemenu.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. ;(function($, window, document, undefined){
  2. var pluginName = 'circleMenu',
  3. defaults = {
  4. depth: 0,
  5. item_diameter: 30,
  6. circle_radius: 80,
  7. angle:{
  8. start: 0,
  9. end: 90
  10. },
  11. speed: 500,
  12. delay: 1000,
  13. step_out: 20,
  14. step_in: -20,
  15. trigger: 'hover',
  16. border: 'round',
  17. close_event: 'click',
  18. transition_function: 'ease'
  19. };
  20. function vendorPrefixes(items,prop,value){
  21. ['-webkit-','-moz-','-o-','-ms-',''].forEach(function(prefix){
  22. items.css(prefix+prop,value);
  23. });
  24. }
  25. function CircleMenu(element, options){
  26. this._timeouts = [];
  27. this.element = $(element);
  28. this.options = $.extend({}, defaults, options);
  29. this._defaults = defaults;
  30. this._name = pluginName;
  31. this.init();
  32. this.hook();
  33. }
  34. CircleMenu.prototype.init = function(){
  35. var self = this,
  36. directions = {
  37. 'vertical':[-400,0],
  38. 'vertical-top':[-800,0],
  39. 'horizontal':[400,0],
  40. 'bottom-left':[180,90],
  41. 'bottom':[135,45],
  42. 'right':[-45,45],
  43. 'left':[225,135],
  44. 'top':[225,315],
  45. 'bottom-half':[180,0],
  46. 'right-half':[-90,90],
  47. 'left-half':[270,90],
  48. 'top-half':[180,360],
  49. 'top-left':[270,180],
  50. 'top-right':[270,360],
  51. 'full':[-90,270-Math.floor(360/(self.element.children('li').length - 1))],
  52. 'bottom-right':[0,90]
  53. },
  54. dir;
  55. self._state = 'closed';
  56. self._locked = false;
  57. self.element.addClass(pluginName+'-closed');
  58. if(typeof self.options.direction === 'string'){
  59. dir = directions[self.options.direction.toLowerCase()];
  60. if(dir){
  61. self.options.angle.start = dir[0];
  62. self.options.angle.end = dir[1];
  63. }
  64. }
  65. self.menu_items = self.element.children('li:not(:first-child)');
  66. self.initCss();
  67. self.item_count = self.menu_items.length;
  68. self._step = (self.options.angle.end - self.options.angle.start) / (self.item_count-1);
  69. self.menu_items.each(function(index){
  70. var $item = $(this),
  71. angle = (self.options.angle.start + (self._step * index)) * (Math.PI/180),
  72. x = Math.round(self.options.circle_radius * Math.cos(angle)),
  73. y = Math.round(self.options.circle_radius * Math.sin(angle));
  74. // linear hack
  75. if (self.options.angle.start<-360) {x = 0; y = index * self.options.circle_radius +self.options.circle_radius;}
  76. if (self.options.angle.start>360) {x = index * self.options.circle_radius + self.options.circle_radius;y = 0;}
  77. if (self.options.angle.start<-790) {x = 0; y = -(index * self.options.circle_radius +self.options.circle_radius);} //vertical-top
  78. $item.data('plugin_'+pluginName+'-pos-x', x);
  79. $item.data('plugin_'+pluginName+'-pos-y', y);
  80. $item.on(self.options.close_event, function(){
  81. self.select(index+2);
  82. });
  83. });
  84. // Initialize event hooks from options
  85. ['open','close','init','select'].forEach(function(evt){
  86. var fn;
  87. if(self.options[evt]){
  88. fn = self.options[evt];
  89. self.element.on(pluginName+'-'+evt, function(){
  90. return fn.apply(self,arguments);
  91. });
  92. delete self.options[evt];
  93. }
  94. });
  95. self.submenus = self.menu_items.children('ul');
  96. self.submenus.circleMenu($.extend({},self.options,{depth:self.options.depth+1}));
  97. self.trigger('init');
  98. };
  99. CircleMenu.prototype.trigger = function(){
  100. var args = [],
  101. i, len;
  102. for(i = 0, len = arguments.length; i < len; i++){
  103. args.push(arguments[i]);
  104. }
  105. this.element.trigger(pluginName+'-'+args.shift(), args);
  106. };
  107. CircleMenu.prototype.hook = function(){
  108. var self = this;
  109. if(self.options.trigger === 'hover'){
  110. self.element.on('mouseenter',function(evt){
  111. self.open();
  112. }).on('mouseleave',function(evt){
  113. self.close();
  114. });
  115. }else if(self.options.trigger === 'click'){
  116. self.element.children('li:first-child').children().addBack()
  117. .on('clicked click',function(evt){
  118. evt.preventDefault();
  119. if(self._locked === false){
  120. self._locked = true;
  121. if(self._state === 'closed' || self._state === 'closing'){
  122. self.open();
  123. }else{
  124. self.close(true);
  125. }
  126. setTimeout( function() {
  127. self._locked = false;
  128. }, 350);
  129. }
  130. return false;
  131. });
  132. }else if(self.options.trigger === 'none'){
  133. // Do nothing
  134. }
  135. };
  136. CircleMenu.prototype.open = function(){
  137. var self = this,
  138. $self = this.element,
  139. start = 0,
  140. set;
  141. self.clearTimeouts();
  142. if(self._state === 'open') return self;
  143. $self.addClass(pluginName+'-open');
  144. $self.removeClass(pluginName+'-closed');
  145. if(self.options.step_out >= 0){
  146. set = self.menu_items;
  147. }else{
  148. set = $(self.menu_items.get().reverse());
  149. }
  150. set.each(function(index){
  151. var $item = $(this);
  152. self._timeouts.push(setTimeout(function(){
  153. $item.css({
  154. left: $item.data('plugin_'+pluginName+'-pos-x')+'px',
  155. top: $item.data('plugin_'+pluginName+'-pos-y')+'px'
  156. });
  157. vendorPrefixes($item,'transform','scale(1)');
  158. }, start + Math.abs(self.options.step_out) * index));
  159. });
  160. self._timeouts.push(setTimeout(function(){
  161. if(self._state === 'opening') self.trigger('open');
  162. self._state = 'open';
  163. },start+Math.abs(self.options.step_out) * set.length));
  164. self._state = 'opening';
  165. return self;
  166. };
  167. CircleMenu.prototype.close = function(immediate){
  168. var self = this,
  169. $self = this.element,
  170. do_animation = function do_animation(){
  171. var start = 0,
  172. set;
  173. self.submenus.circleMenu('close');
  174. self.clearTimeouts();
  175. if(self._state === 'closed') return self;
  176. if(self.options.step_in >= 0){
  177. set = self.menu_items;
  178. }else{
  179. set = $(self.menu_items.get().reverse());
  180. }
  181. set.each(function(index){
  182. var $item = $(this);
  183. self._timeouts.push(setTimeout(function(){
  184. $item.css({top:0,left:0});
  185. vendorPrefixes($item,'transform','scale(0)');
  186. }, start + Math.abs(self.options.step_in) * index));
  187. });
  188. self._timeouts.push(setTimeout(function(){
  189. if(self._state === 'closing') self.trigger('close');
  190. self._state = 'closed';
  191. },start+Math.abs(self.options.step_in) * set.length));
  192. $self.removeClass(pluginName+'-open');
  193. $self.addClass(pluginName+'-closed');
  194. self._state = 'closing';
  195. return self;
  196. };
  197. if(immediate){
  198. do_animation();
  199. }else{
  200. self._timeouts.push(setTimeout(do_animation,self.options.delay));
  201. }
  202. return this;
  203. };
  204. CircleMenu.prototype.select = function(index){
  205. var self = this,
  206. selected, set_other;
  207. if(self._state === 'open' || self._state === 'opening'){
  208. self.clearTimeouts();
  209. set_other = self.element.children('li:not(:nth-child('+index+'),:first-child)');
  210. selected = self.element.children('li:nth-child('+index+')');
  211. self.trigger('select',selected);
  212. vendorPrefixes(selected.add(set_other), 'transition', 'all 500ms ease-out');
  213. vendorPrefixes(selected, 'transform', 'scale(0)');
  214. vendorPrefixes(set_other, 'transform', 'scale(0)');
  215. selected.css('opacity','0');
  216. set_other.css('opacity','0');
  217. self.element.removeClass(pluginName+'-open');
  218. setTimeout(function(){self.initCss();},500);
  219. }
  220. };
  221. CircleMenu.prototype.clearTimeouts = function(){
  222. var timeout;
  223. while(timeout = this._timeouts.shift()){
  224. clearTimeout(timeout);
  225. }
  226. };
  227. CircleMenu.prototype.initCss = function(){
  228. var self = this,
  229. $items;
  230. var value = String(self.options.item_diameter);
  231. var unit = ( value.match(/^.*(px|%|em)$/) ) ? '' : 'px';
  232. self._state = 'closed';
  233. self.element.removeClass(pluginName+'-open');
  234. self.element.css({
  235. 'list-style': 'none',
  236. 'margin': 0,
  237. 'padding': 0,
  238. 'width': self.options.item_diameter + unit
  239. });
  240. $items = self.element.children('li');
  241. $items.attr('style','');
  242. $items.css({
  243. 'display': 'block',
  244. 'width': (self.options.item_width) ? self.options.item_width : 'auto',
  245. 'height': (self.options.item_height) ? self.options.item_height : self.options.item_diameter + unit,
  246. 'text-align': 'center',
  247. 'line-height': self.options.item_diameter + unit,
  248. 'position': 'absolute',
  249. 'z-index': 1,
  250. 'opacity': ''
  251. });
  252. self.element.children('li:first-child').css({'z-index': 1000-self.options.depth, 'background-color':'transparent' });
  253. self.menu_items.css({
  254. top:0,
  255. left:0
  256. });
  257. if ( self.options.border === 'square')
  258. vendorPrefixes($items, 'border-radius', self.options.item_diameter/5 + unit);
  259. else
  260. vendorPrefixes($items, 'border-radius', self.options.item_diameter + unit);
  261. vendorPrefixes(self.menu_items, 'transform', 'scale(0)');
  262. setTimeout(function(){
  263. vendorPrefixes($items, 'transition', 'all '+self.options.speed+'ms '+self.options.transition_function);
  264. },0);
  265. };
  266. $.fn[pluginName] = function(options){
  267. return this.each(function(){
  268. var obj = $.data(this, 'plugin_'+pluginName),
  269. commands = {
  270. 'init':function(){obj.init();},
  271. 'open':function(){obj.open();},
  272. 'close':function(){obj.close(true);}
  273. };
  274. if(typeof options === 'string' && obj && commands[options]){
  275. commands[options]();
  276. }
  277. if(!obj){
  278. $.data(this, 'plugin_' + pluginName, new CircleMenu(this, options));
  279. }
  280. });
  281. };
  282. })(jQuery, window, document);