1/* 2 * Snap.js 3 * 4 * Copyright 2013, Jacob Kelley - http://jakiestfu.com/ 5 * Released under the MIT Licence 6 * http://opensource.org/licenses/MIT 7 * 8 * Github: http://github.com/jakiestfu/Snap.js/ 9 * Version: 1.9.3 10 */ 11/*jslint browser: true*/ 12/*global define, module, ender*/ 13(function(win, doc) { 14 'use strict'; 15 var Snap = Snap || function(userOpts) { 16 var settings = { 17 element: null, 18 dragger: null, 19 disable: 'none', 20 addBodyClasses: true, 21 hyperextensible: true, 22 resistance: 0.5, 23 flickThreshold: 50, 24 transitionSpeed: 0.3, 25 easing: 'ease', 26 maxPosition: 266, 27 minPosition: -266, 28 tapToClose: true, 29 touchToDrag: true, 30 slideIntent: 40, // degrees 31 minDragDistance: 5 32 }, 33 cache = { 34 simpleStates: { 35 opening: null, 36 towards: null, 37 hyperExtending: null, 38 halfway: null, 39 flick: null, 40 translation: { 41 absolute: 0, 42 relative: 0, 43 sinceDirectionChange: 0, 44 percentage: 0 45 } 46 } 47 }, 48 eventList = {}, 49 utils = { 50 hasTouch: ('ontouchstart' in doc.documentElement || win.navigator.msPointerEnabled), 51 eventType: function(action) { 52 var eventTypes = { 53 down: (utils.hasTouch ? 'touchstart' : 'mousedown'), 54 move: (utils.hasTouch ? 'touchmove' : 'mousemove'), 55 up: (utils.hasTouch ? 'touchend' : 'mouseup'), 56 out: (utils.hasTouch ? 'touchcancel' : 'mouseout') 57 }; 58 return eventTypes[action]; 59 }, 60 page: function(t, e){ 61 return (utils.hasTouch && e.touches.length && e.touches[0]) ? e.touches[0]['page'+t] : e['page'+t]; 62 }, 63 klass: { 64 has: function(el, name){ 65 return (el.className).indexOf(name) !== -1; 66 }, 67 add: function(el, name){ 68 if(!utils.klass.has(el, name) && settings.addBodyClasses){ 69 el.className += " "+name; 70 } 71 }, 72 remove: function(el, name){ 73 if(settings.addBodyClasses){ 74 el.className = (el.className).replace(name, "").replace(/^\s+|\s+$/g, ''); 75 } 76 } 77 }, 78 dispatchEvent: function(type) { 79 if (typeof eventList[type] === 'function') { 80 return eventList[type].call(); 81 } 82 }, 83 vendor: function(){ 84 var tmp = doc.createElement("div"), 85 prefixes = 'webkit Moz O ms'.split(' '), 86 i; 87 for (i in prefixes) { 88 if (typeof tmp.style[prefixes[i] + 'Transition'] !== 'undefined') { 89 return prefixes[i]; 90 } 91 } 92 }, 93 transitionCallback: function(){ 94 return (cache.vendor==='Moz' || cache.vendor==='ms') ? 'transitionend' : cache.vendor+'TransitionEnd'; 95 }, 96 canTransform: function(){ 97 return typeof settings.element.style[cache.vendor+'Transform'] !== 'undefined'; 98 }, 99 deepExtend: function(destination, source) { 100 var property; 101 for (property in source) { 102 if (source[property] && source[property].constructor && source[property].constructor === Object) { 103 destination[property] = destination[property] || {}; 104 utils.deepExtend(destination[property], source[property]); 105 } else { 106 destination[property] = source[property]; 107 } 108 } 109 return destination; 110 }, 111 angleOfDrag: function(x, y) { 112 var degrees, theta; 113 // Calc Theta 114 theta = Math.atan2(-(cache.startDragY - y), (cache.startDragX - x)); 115 if (theta < 0) { 116 theta += 2 * Math.PI; 117 } 118 // Calc Degrees 119 degrees = Math.floor(theta * (180 / Math.PI) - 180); 120 if (degrees < 0 && degrees > -180) { 121 degrees = 360 - Math.abs(degrees); 122 } 123 return Math.abs(degrees); 124 }, 125 events: { 126 addEvent: function addEvent(element, eventName, func) { 127 if (element.addEventListener) { 128 return element.addEventListener(eventName, func, false); 129 } else if (element.attachEvent) { 130 return element.attachEvent("on" + eventName, func); 131 } 132 }, 133 removeEvent: function addEvent(element, eventName, func) { 134 if (element.addEventListener) { 135 return element.removeEventListener(eventName, func, false); 136 } else if (element.attachEvent) { 137 return element.detachEvent("on" + eventName, func); 138 } 139 }, 140 prevent: function(e) { 141 if (e.preventDefault) { 142 e.preventDefault(); 143 } else { 144 e.returnValue = false;
145 } 146 } 147 }, 148 parentUntil: function(el, attr) { 149 var isStr = typeof attr === 'string'; 150 while (el.parentNode) { 151 if (isStr && el.getAttribute && el.getAttribute(attr)){ 152 return el; 153 } else if(!isStr && el === attr){ 154 return el; 155 } 156 el = el.parentNode; 157 } 158 return null; 159 } 160 }, 161 action = { 162 translate: { 163 get: { 164 matrix: function(index) { 165 166 if( !utils.canTransform() ){ 167 return parseInt(settings.element.style.left, 10); 168 } else { 169 var matrix = win.getComputedStyle(settings.element)[cache.vendor+'Transform'].match(/\((.*)\)/), 170 ieOffset = 8; 171 if (matrix) { 172 matrix = matrix[1].split(','); 173 if(matrix.length===16){ 174 index+=ieOffset; 175 } 176 return parseInt(matrix[index], 10); 177 } 178 return 0; 179 } 180 } 181 }, 182 easeCallback: function(){ 183 settings.element.style[cache.vendor+'Transition'] = ''; 184 cache.translation = action.translate.get.matrix(4); 185 cache.easing = false; 186 clearInterval(cache.animatingInterval); 187 188 if(cache.easingTo===0){ 189 utils.klass.remove(doc.body, 'snapjs-right'); 190 utils.klass.remove(doc.body, 'snapjs-left'); 191 } 192 193 utils.dispatchEvent('animated'); 194 utils.events.removeEvent(settings.element, utils.transitionCallback(), action.translate.easeCallback); 195 }, 196 easeTo: function(n) { 197 198 if( !utils.canTransform() ){ 199 cache.translation = n; 200 action.translate.x(n); 201 } else { 202 cache.easing = true; 203 cache.easingTo = n; 204 205 settings.element.style[cache.vendor+'Transition'] = 'all ' + settings.transitionSpeed + 's ' + settings.easing; 206 207 cache.animatingInterval = setInterval(function() { 208 utils.dispatchEvent('animating'); 209 }, 1); 210 211 utils.events.addEvent(settings.element, utils.transitionCallback(), action.translate.easeCallback); 212 action.translate.x(n); 213 } 214 if(n===0){ 215 settings.element.style[cache.vendor+'Transform'] = ''; 216 } 217 }, 218 x: function(n) { 219 if( (settings.disable==='left' && n>0) || 220 (settings.disable==='right' && n<0) 221 ){ return; } 222 223 if( !settings.hyperextensible ){ 224 if( n===settings.maxPosition || n>settings.maxPosition ){ 225 n=settings.maxPosition; 226 } else if( n===settings.minPosition || n<settings.minPosition ){ 227 n=settings.minPosition; 228 } 229 } 230 231 n = parseInt(n, 10); 232 if(isNaN(n)){ 233 n = 0; 234 } 235 236 if( utils.canTransform() ){ 237 var theTranslate = 'translate3d(' + n + 'px, 0,0)'; 238 settings.element.style[cache.vendor+'Transform'] = theTranslate; 239 } else { 240 settings.element.style.width = (win.innerWidth || doc.documentElement.clientWidth)+'px'; 241 242 settings.element.style.left = n+'px'; 243 settings.element.style.right = ''; 244 } 245 } 246 }, 247 drag: { 248 listen: function() { 249 cache.translation = 0; 250 cache.easing = false;
251 utils.events.addEvent(settings.element, utils.eventType('down'), action.drag.startDrag); 252 utils.events.addEvent(settings.element, utils.eventType('move'), action.drag.dragging); 253 utils.events.addEvent(settings.element, utils.eventType('up'), action.drag.endDrag); 254 }, 255 stopListening: function() { 256 utils.events.removeEvent(settings.element, utils.eventType('down'), action.drag.startDrag); 257 utils.events.removeEvent(settings.element, utils.eventType('move'), action.drag.dragging); 258 utils.events.removeEvent(settings.element, utils.eventType('up'), action.drag.endDrag); 259 }, 260 startDrag: function(e) { 261 // No drag on ignored elements 262 var target = e.target ? e.target : e.srcElement, 263 ignoreParent = utils.parentUntil(target, 'data-snap-ignore'); 264 265 if (ignoreParent) { 266 utils.dispatchEvent('ignore'); 267 return; 268 } 269 270 271 if(settings.dragger){ 272 var dragParent = utils.parentUntil(target, settings.dragger); 273 274 // Only use dragger if we're in a closed state 275 if( !dragParent && 276 (cache.translation !== settings.minPosition && 277 cache.translation !== settings.maxPosition 278 )){ 279 return; 280 } 281 } 282 283 utils.dispatchEvent('start'); 284 settings.element.style[cache.vendor+'Transition'] = ''; 285 cache.isDragging = true; 286 cache.hasIntent = null; 287 cache.intentChecked = false; 288 cache.startDragX = utils.page('X', e); 289 cache.startDragY = utils.page('Y', e); 290 cache.dragWatchers = { 291 current: 0, 292 last: 0, 293 hold: 0, 294 state: '' 295 }; 296 cache.simpleStates = { 297 opening: null, 298 towards: null, 299 hyperExtending: null, 300 halfway: null, 301 flick: null, 302 translation: { 303 absolute: 0, 304 relative: 0, 305 sinceDirectionChange: 0, 306 percentage: 0 307 } 308 }; 309 }, 310 dragging: function(e) { 311 if (cache.isDragging && settings.touchToDrag) { 312 313 var thePageX = utils.page('X', e), 314 thePageY = utils.page('Y', e), 315 translated = cache.translation, 316 absoluteTranslation = action.translate.get.matrix(4), 317 whileDragX = thePageX - cache.startDragX, 318 openingLeft = absoluteTranslation > 0, 319 translateTo = whileDragX, 320 diff; 321 322 // Shown no intent already 323 if((cache.intentChecked && !cache.hasIntent)){ 324 return; 325 } 326 327 if(settings.addBodyClasses){ 328 if((absoluteTranslation)>0){ 329 utils.klass.add(doc.body, 'snapjs-left'); 330 utils.klass.remove(doc.body, 'snapjs-right'); 331 } else if((absoluteTranslation)<0){ 332 utils.klass.add(doc.body, 'snapjs-right'); 333 utils.klass.remove(doc.body, 'snapjs-left'); 334 } 335 } 336 337 if (cache.hasIntent === false || cache.hasIntent === null) { 338 var deg = utils.angleOfDrag(thePageX, thePageY), 339 inRightRange = (deg >= 0 && deg <= settings.slideIntent) || (deg <= 360 && deg > (360 - settings.slideIntent)), 340 inLeftRange = (deg >= 180 && deg <= (180 + settings.slideIntent)) || (deg <= 180 && deg >= (180 - settings.slideIntent)); 341 if (!inLeftRange && !inRightRange) { 342 cache.hasIntent = false;
343 } else { 344 cache.hasIntent = true; 345 } 346 cache.intentChecked = true; 347 } 348 349 if ( 350 (settings.minDragDistance>=Math.abs(thePageX-cache.startDragX)) || // Has user met minimum drag distance? 351 (cache.hasIntent === false) 352 ) { 353 return; 354 } 355 356 utils.events.prevent(e); 357 utils.dispatchEvent('drag'); 358 359 cache.dragWatchers.current = thePageX; 360 // Determine which direction we are going 361 if (cache.dragWatchers.last > thePageX) { 362 if (cache.dragWatchers.state !== 'left') { 363 cache.dragWatchers.state = 'left'; 364 cache.dragWatchers.hold = thePageX; 365 } 366 cache.dragWatchers.last = thePageX; 367 } else if (cache.dragWatchers.last < thePageX) { 368 if (cache.dragWatchers.state !== 'right') { 369 cache.dragWatchers.state = 'right'; 370 cache.dragWatchers.hold = thePageX; 371 } 372 cache.dragWatchers.last = thePageX; 373 } 374 if (openingLeft) { 375 // Pulling too far to the right 376 if (settings.maxPosition < absoluteTranslation) { 377 diff = (absoluteTranslation - settings.maxPosition) * settings.resistance; 378 translateTo = whileDragX - diff; 379 } 380 cache.simpleStates = { 381 opening: 'left', 382 towards: cache.dragWatchers.state, 383 hyperExtending: settings.maxPosition < absoluteTranslation, 384 halfway: absoluteTranslation > (settings.maxPosition / 2), 385 flick: Math.abs(cache.dragWatchers.current - cache.dragWatchers.hold) > settings.flickThreshold, 386 translation: { 387 absolute: absoluteTranslation, 388 relative: whileDragX, 389 sinceDirectionChange: (cache.dragWatchers.current - cache.dragWatchers.hold), 390 percentage: (absoluteTranslation/settings.maxPosition)*100 391 } 392 }; 393 } else { 394 // Pulling too far to the left 395 if (settings.minPosition > absoluteTranslation) { 396 diff = (absoluteTranslation - settings.minPosition) * settings.resistance; 397 translateTo = whileDragX - diff; 398 } 399 cache.simpleStates = { 400 opening: 'right', 401 towards: cache.dragWatchers.state, 402 hyperExtending: settings.minPosition > absoluteTranslation, 403 halfway: absoluteTranslation < (settings.minPosition / 2), 404 flick: Math.abs(cache.dragWatchers.current - cache.dragWatchers.hold) > settings.flickThreshold, 405 translation: { 406 absolute: absoluteTranslation, 407 relative: whileDragX, 408 sinceDirectionChange: (cache.dragWatchers.current - cache.dragWatchers.hold), 409 percentage: (absoluteTranslation/settings.minPosition)*100 410 } 411 }; 412 } 413 action.translate.x(translateTo + translated); 414 } 415 }, 416 endDrag: function(e) { 417 if (cache.isDragging) { 418 utils.dispatchEvent('end'); 419 var translated = action.translate.get.matrix(4); 420 421 // Tap Close 422 if (cache.dragWatchers.current === 0 && translated !== 0 && settings.tapToClose) { 423 utils.dispatchEvent('close'); 424 utils.events.prevent(e); 425 action.translate.easeTo(0); 426 cache.isDragging = false;
427 cache.startDragX = 0; 428 return; 429 } 430 431 // Revealing Left 432 if (cache.simpleStates.opening === 'left') { 433 // Halfway, Flicking, or Too Far Out 434 if ((cache.simpleStates.halfway || cache.simpleStates.hyperExtending || cache.simpleStates.flick)) { 435 if (cache.simpleStates.flick && cache.simpleStates.towards === 'left') { // Flicking Closed 436 action.translate.easeTo(0); 437 } else if ( 438 (cache.simpleStates.flick && cache.simpleStates.towards === 'right') || // Flicking Open OR 439 (cache.simpleStates.halfway || cache.simpleStates.hyperExtending) // At least halfway open OR hyperextending 440 ) { 441 action.translate.easeTo(settings.maxPosition); // Open Left 442 } 443 } else { 444 action.translate.easeTo(0); // Close Left 445 } 446 // Revealing Right 447 } else if (cache.simpleStates.opening === 'right') { 448 // Halfway, Flicking, or Too Far Out 449 if ((cache.simpleStates.halfway || cache.simpleStates.hyperExtending || cache.simpleStates.flick)) { 450 if (cache.simpleStates.flick && cache.simpleStates.towards === 'right') { // Flicking Closed 451 action.translate.easeTo(0); 452 } else if ( 453 (cache.simpleStates.flick && cache.simpleStates.towards === 'left') || // Flicking Open OR 454 (cache.simpleStates.halfway || cache.simpleStates.hyperExtending) // At least halfway open OR hyperextending 455 ) { 456 action.translate.easeTo(settings.minPosition); // Open Right 457 } 458 } else { 459 action.translate.easeTo(0); // Close Right 460 } 461 } 462 cache.isDragging = false; 463 cache.startDragX = utils.page('X', e); 464 } 465 } 466 } 467 }, 468 init = function(opts) { 469 if (opts.element) { 470 utils.deepExtend(settings, opts); 471 cache.vendor = utils.vendor(); 472 action.drag.listen(); 473 } 474 }; 475 /* 476 * Public 477 */ 478 this.open = function(side) { 479 utils.dispatchEvent('open'); 480 utils.klass.remove(doc.body, 'snapjs-expand-left'); 481 utils.klass.remove(doc.body, 'snapjs-expand-right'); 482 483 if (side === 'left') { 484 cache.simpleStates.opening = 'left'; 485 cache.simpleStates.towards = 'right'; 486 utils.klass.add(doc.body, 'snapjs-left'); 487 utils.klass.remove(doc.body, 'snapjs-right'); 488 action.translate.easeTo(settings.maxPosition); 489 } else if (side === 'right') { 490 cache.simpleStates.opening = 'right'; 491 cache.simpleStates.towards = 'left'; 492 utils.klass.remove(doc.body, 'snapjs-left'); 493 utils.klass.add(doc.body, 'snapjs-right'); 494 action.translate.easeTo(settings.minPosition); 495 } 496 }; 497 this.close = function() { 498 utils.dispatchEvent('close'); 499 action.translate.easeTo(0); 500 }; 501 this.expand = function(side){ 502 var to = win.innerWidth || doc.documentElement.clientWidth; 503 504 if(side==='left'){ 505 utils.dispatchEvent('expandLeft'); 506 utils.klass.add(doc.body, 'snapjs-expand-left'); 507 utils.klass.remove(doc.body, 'snapjs-expand-right'); 508 } else { 509 utils.dispatchEvent('expandRight'); 510 utils.klass.add(doc.body, 'snapjs-expand-right'); 511 utils.klass.remove(doc.body, 'snapjs-expand-left'); 512 to *= -1; 513 } 514 action.translate.easeTo(to); 515 }; 516 517 this.on = function(evt, fn) { 518 eventList[evt] = fn; 519 return this; 520 }; 521 this.off = function(evt) { 522 if (eventList[evt]) { 523 eventList[evt] = false;
524 } 525 }; 526 527 this.enable = function() { 528 utils.dispatchEvent('enable'); 529 action.drag.listen(); 530 }; 531 this.disable = function() { 532 utils.dispatchEvent('disable'); 533 action.drag.stopListening(); 534 }; 535 536 this.settings = function(opts){ 537 utils.deepExtend(settings, opts); 538 }; 539 540 this.state = function() { 541 var state, 542 fromLeft = action.translate.get.matrix(4); 543 if (fromLeft === settings.maxPosition) { 544 state = 'left'; 545 } else if (fromLeft === settings.minPosition) { 546 state = 'right'; 547 } else { 548 state = 'closed'; 549 } 550 return { 551 state: state, 552 info: cache.simpleStates 553 }; 554 }; 555 init(userOpts); 556 }; 557 if ((typeof module !== 'undefined') && module.exports) { 558 module.exports = Snap; 559 } 560 if (typeof ender === 'undefined') { 561 this.Snap = Snap; 562 } 563 if ((typeof define === "function") && define.amd) { 564 define("snap", [], function() { 565 return Snap; 566 }); 567 } 568}).call(this, 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.