1/*! 2 * jquery.okayNav.js 2.0.1 (https://github.com/VPenkov/okayNav) 3 * Author: Vergil Penkov (http://vergilpenkov.com/) 4 * MIT license: https://opensource.org/licenses/MIT 5 */ 6 7; 8(function($, window, document, undefined) { 9 10 // Defaults 11 var okayNav = 'okayNav', 12 defaults = { 13 parent: '', // will call nav's parent() by default 14 toggle_icon_class: 'okayNav__menu-toggle', 15 toggle_icon_content: '<span /><span /><span />', 16 align_right: true, // If false, the menu and the kebab icon will be on the left 17 swipe_enabled: true, // If true, you'll be able to swipe left/right to open the navigation 18 threshold: 50, // Nav will auto open/close if swiped >= this many percent 19 beforeOpen: function() {}, // Will trigger before the nav gets opened 20 afterOpen: function() {}, // Will trigger after the nav gets opened 21 beforeClose: function() {}, // Will trigger before the nav gets closed 22 afterClose: function() {}, // Will trigger after the nav gets closed 23 itemHidden: function() {}, 24 itemDisplayed: function() {} 25 }; 26 27 // Begin 28 function Plugin(element, options) { 29 self = this; 30 this.options = $.extend({}, defaults, options); 31 _options = this.options; 32 33 $navigation = $(element); 34 $document = $(document); 35 $window = $(window); 36 37 this.options.parent == '' ? this.options.parent = $navigation.parent() : ''; 38 39 _nav_visible = false; // Store the state of the hidden nav 40 _nav_full_width = 0; 41 _parent_full_width = 0; 42 43 // Swipe stuff 44 radCoef = 180 / Math.PI; 45 _sTouch = { 46 x: 0, 47 y: 0 48 }; 49 _cTouch = { 50 x: 0, 51 y: 0 52 }; 53 _sTime = 0; 54 _nav_position = 0; 55 _percent_open = 0; 56 _nav_moving = false; 57 58 59 self.init(); 60 } 61 62 Plugin.prototype = { 63 64 init: function() { 65 66 $('body').addClass('okayNav-loaded'); 67 68 // Add classes 69 $navigation 70 .addClass('okayNav loaded') 71 .children('ul').addClass('okayNav__nav--visible'); 72 73 // Append elements 74 if (self.options.align_right) { 75 $navigation 76 .append('<ul class="okayNav__nav--invisible transition-enabled nav-right" />') 77 .append('<a href="#" class="' + _options.toggle_icon_class + ' okay-invisible">' + _options.toggle_icon_content + '</a>') 78 } else { 79 $navigation 80 .prepend('<ul class="okayNav__nav--invisible transition-enabled nav-left" />') 81 .prepend('<a href="#" class="' + _options.toggle_icon_class + ' okay-invisible">' + _options.toggle_icon_content + '</a>') 82 } 83 84 // Cache new elements for further use 85 $nav_visible = $navigation.children('.okayNav__nav--visible'); 86 $nav_invisible = $navigation.children('.okayNav__nav--invisible'); 87 $nav_toggle_icon = $navigation.children('.' + _options.toggle_icon_class); 88 89 _toggle_icon_width = $nav_toggle_icon.outerWidth(true); 90 _nav_default_width = self.getChildrenWidth($navigation); 91 _parent_full_width = $(_options.parent).outerWidth(true); 92 _last_visible_child_width = 0; // We'll define this later 93 94 // Events are up once everything is set 95 self.initEvents(); 96 if (_options.swipe_enabled == true) self.initSwipeEvents(); 97 }, 98 99 initEvents: function() { 100 // Toggle hidden nav when hamburger icon is clicked and 101 // Collapse hidden nav on click outside the header 102 $document.on('click.okayNav', function(e) { 103 var _target = $(e.target); 104 105 if (_nav_visible === true && _target.closest('.okayNav').length == 0) 106 self.closeInvisibleNav(); 107 108 if (_target.hasClass(_options.toggle_icon_class)) { 109 e.preventDefault(); 110 self.toggleInvisibleNav(); 111 } 112 }); 113 114 $window.on('load.okayNav resize.okayNav', function(e) { 115 self.recalcNav(); 116 }); 117 }, 118 119 initSwipeEvents: function() { 120 $document 121 .on('touchstart.okayNav', function(e) {
122 $nav_invisible.removeClass('transition-enabled'); 123 124 //Trigger only on touch with one finger 125 if (e.originalEvent.touches.length == 1) { 126 var touch = e.originalEvent.touches[0]; 127 if ( 128 ((touch.pageX < 25 && self.options.align_right == false) || 129 (touch.pageX > ($(_options.parent).outerWidth(true) - 25) && self.options.align_right == true)) || 130 _nav_visible === true) { 131 132 _sTouch.x = _cTouch.x = touch.pageX; 133 _sTouch.y = _cTouch.y = touch.pageY; 134 _sTime = Date.now(); 135 } 136 137 } 138 }) 139 .on('touchmove.okayNav', function(e) { 140 var touch = e.originalEvent.touches[0]; 141 self._triggerMove(touch.pageX, touch.pageY); 142 _nav_moving = true; 143 }) 144 .on('touchend.okayNav', function(e) { 145 _sTouch = { 146 x: 0, 147 y: 0 148 }; 149 _cTouch = { 150 x: 0, 151 y: 0 152 }; 153 _sTime = 0; 154 155 //Close menu if not swiped enough 156 if (_percent_open > (100 - self.options.threshold)) { 157 _nav_position = 0; 158 self.closeInvisibleNav(); 159 160 } else if (_nav_moving == true) { 161 _nav_position = $nav_invisible.width(); 162 self.openInvisibleNav(); 163 } 164 165 _nav_moving = false; 166 167 $nav_invisible.addClass('transition-enabled'); 168 }); 169 }, 170 171 _getDirection: function(dx) { 172 if (self.options.align_right) { 173 return (dx > 0) ? -1 : 1; 174 } else { 175 return (dx < 0) ? -1 : 1; 176 } 177 }, 178 179 _triggerMove: function(x, y) { 180 _cTouch.x = x; 181 _cTouch.y = y; 182 183 var currentTime = Date.now(); 184 var dx = (_cTouch.x - _sTouch.x); 185 var dy = (_cTouch.y - _sTouch.y); 186 187 var opposing = dy * dy; 188 var distance = Math.sqrt(dx * dx + opposing); 189 //Length of the opposing side of the 90deg triagle 190 var dOpposing = Math.sqrt(opposing); 191 192 var angle = Math.asin(Math.sin(dOpposing / distance)) * radCoef; 193 var speed = distance / (currentTime - _sTime); 194 195 //Set new start position 196 _sTouch.x = x; 197 _sTouch.y = y; 198 199 //Remove false swipes 200 if (angle < 20) { 201 var dir = self._getDirection(dx); 202 203 var newPos = _nav_position + dir * distance; 204 var menuWidth = $nav_invisible.width(); 205 var overflow = 0; 206 207 208 if (newPos < 0) { 209 overflow = -newPos; 210 } else if (newPos > menuWidth) { 211 overflow = menuWidth - newPos; 212 } 213 214 var size = menuWidth - (_nav_position + dir * distance + overflow); 215 var threshold = (size / menuWidth) * 100; 216 217 //Set new position and threshold 218 _nav_position += dir * distance + overflow; 219 _percent_open = threshold; 220 221 $nav_invisible.css('transform', 'translateX(' + (self.options.align_right ? 1 : -1) * threshold + '%)'); 222 } 223 224 }, 225 226 /* 227 * A few methods to allow working with elements 228 */ 229 getParent: function() { 230 return _options.parent; 231 }, 232 233 getVisibleNav: function() { // Visible navigation 234 return $nav_visible; 235 }, 236 237 getInvisibleNav: function() { // Hidden behind the kebab icon 238 return $nav_invisible; 239 }, 240 241 getNavToggleIcon: function() { // Kebab icon 242 return $nav_toggle_icon; 243 }, 244 245 /* 246 * Operations 247 */ 248 openInvisibleNav: function() { 249 !_options.enable_swipe ? _options.beforeOpen.call() : ''; 250 251 $nav_toggle_icon.addClass('icon--active');
252 $nav_invisible.addClass('nav-open'); 253 _nav_visible = true; 254 $nav_invisible.css({ 255 '-webkit-transform': 'translateX(0%)', 256 'transform': 'translateX(0%)' 257 }); 258 259 _options.afterOpen.call(); 260 }, 261 262 closeInvisibleNav: function() { 263 !_options.enable_swipe ? _options.beforeClose.call() : ''; 264 265 $nav_toggle_icon.removeClass('icon--active'); 266 $nav_invisible.removeClass('nav-open'); 267 268 if (self.options.align_right) { 269 $nav_invisible.css({ 270 '-webkit-transform': 'translateX(100%)', 271 'transform': 'translateX(100%)' 272 }); 273 } else { 274 $nav_invisible.css({ 275 '-webkit-transform': 'translateX(-100%)', 276 'transform': 'translateX(-100%)' 277 }); 278 } 279 _nav_visible = false; 280 281 _options.afterClose.call(); 282 }, 283 284 toggleInvisibleNav: function() { 285 if (!_nav_visible) { 286 self.openInvisibleNav(); 287 } else { 288 self.closeInvisibleNav(); 289 } 290 }, 291 292 293 /* 294 * Math stuff 295 */ 296 getChildrenWidth: function(el) { 297 var children_width = 0; 298 var children = $(el).children(); 299 for (var i = 0; i < children.length; i++) { 300 children_width += $(children[i]).outerWidth(true); 301 }; 302 303 return children_width; 304 }, 305 306 getVisibleItemCount: function() { 307 return $('li', $nav_visible).length; 308 }, 309 getHiddenItemCount: function() { 310 return $('li', $nav_invisible).length; 311 }, 312 313 recalcNav: function() { 314 var wrapper_width = $(_options.parent).outerWidth(true), 315 space_taken = self.getChildrenWidth(_options.parent), 316 nav_full_width = $navigation.outerWidth(true), 317 visible_nav_items = self.getVisibleItemCount(), 318 collapse_width = $nav_visible.outerWidth(true) + _toggle_icon_width, 319 expand_width = space_taken + _last_visible_child_width + _toggle_icon_width, 320 expandAll_width = space_taken - nav_full_width + _nav_default_width; 321 322 if (wrapper_width > expandAll_width) { 323 self._expandAllItems(); 324 $nav_toggle_icon.addClass('okay-invisible'); 325 return; 326 } 327 328 if (visible_nav_items > 0 && 329 nav_full_width <= collapse_width && 330 wrapper_width <= expand_width) { 331 self._collapseNavItem(); 332 } 333 334 if (wrapper_width > expand_width + _toggle_icon_width * 2) { 335 self._expandNavItem(); 336 } 337 338 // Hide the kebab icon if no items are hidden 339 self.getHiddenItemCount() == 0 ? 340 $nav_toggle_icon.addClass('okay-invisible') : 341 $nav_toggle_icon.removeClass('okay-invisible'); 342 }, 343 344 _collapseNavItem: function() { 345 var $last_child = $('li:last-child', $nav_visible); 346 _last_visible_child_width = $last_child.outerWidth(true); 347 $document.trigger('okayNav:collapseItem', $last_child); 348 $last_child.detach().prependTo($nav_invisible); 349 _options.itemHidden.call(); 350 // All nav items are visible by default 351 // so we only need recursion when collapsing 352 353 self.recalcNav(); 354 }, 355 356 _expandNavItem: function() { 357 var $first = $('li:first-child', $nav_invisible); 358 $document.trigger('okayNav:expandItem', $first); 359 $first.detach().appendTo($nav_visible); 360 _options.itemDisplayed.call(); 361 }, 362 363 _expandAllItems: function() { 364 $('li', $nav_invisible).detach().appendTo($nav_visible); 365 _options.itemDisplayed.call(); 366 }, 367 368 _collapseAllItems: function() { 369 $('li', $nav_visible).detach().appendTo($nav_invisible); 370 _options.itemHidden.call(); 371 }, 372 373 destroy: function() {
vendor: 1,361 bytes, lines 374-414
374 $('li', $nav_invisible).appendTo($nav_visible); 375 $nav_invisible.remove(); 376 $nav_visible.removeClass('okayNav__nav--visible'); 377 $nav_toggle_icon.remove(); 378 379 $document.unbind('.okayNav'); 380 $window.unbind('.okayNav'); 381 } 382 383 } 384 385 // Plugin wrapper 386 $.fn[okayNav] = function(options) { 387 var args = arguments; 388 389 if (options === undefined || typeof options === 'object') { 390 return this.each(function() { 391 if (!$.data(this, 'plugin_' + okayNav)) { 392 $.data(this, 'plugin_' + okayNav, new Plugin(this, options)); 393 } 394 }); 395 396 } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') { 397 398 var returns; 399 this.each(function() { 400 var instance = $.data(this, 'plugin_' + okayNav); 401 if (instance instanceof Plugin && typeof instance[options] === 'function') { 402 returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1)); 403 } 404 405 if (options === 'destroy') { 406 $.data(this, 'plugin_' + okayNav, null); 407 } 408 }); 409 410 return returns !== undefined ? returns : this; 411 } 412 }; 413 414}(jQuery, window, document));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.