|
|
/* * Snap.js * * Copyright 2013, Jacob Kelley - http://jakiestfu.com/
* Released under the MIT Licence * http://opensource.org/licenses/MIT
* * Github: http://github.com/jakiestfu/Snap.js/
* Version: 1.7.10 */ /*jslint browser: true*/ /*global define, module, ender*/ (function(win, doc) { 'use strict'; var Snap = Snap || function(userOpts) { var settings = { element: null, disable: 'none', addBodyClasses: true, resistance: 0.5, flickThreshold: 50, transitionSpeed: 0.3, easing: 'ease', maxPosition: 266, minPosition: -266, tapToClose: true, touchToDrag: true, slideIntent: 40, // degrees
minDragDistance: 5 }, cache = { simpleStates: { opening: null, towards: null, hyperExtending: null, halfway: null, flick: null, translation: { absolute: 0, relative: 0, sinceDirectionChange: 0, percentage: 0 } } }, eventList = {}, utils = { hasTouch: (doc.ontouchstart === null), eventType: function(action) { var eventTypes = { down: (utils.hasTouch ? 'touchstart' : 'mousedown'), move: (utils.hasTouch ? 'touchmove' : 'mousemove'), up: (utils.hasTouch ? 'touchend' : 'mouseup'), out: (utils.hasTouch ? 'touchcancel' : 'mouseout') }; return eventTypes[action]; }, page: function(t, e){ return (utils.hasTouch && e.touches.length && e.touches[0]) ? e.touches[0]['page'+t] : e['page'+t]; }, klass: { has: function(el, name){ return (el.className).indexOf(name) !== -1; }, add: function(el, name){ if(!utils.klass.has(el, name)){ el.className += " "+name; } }, remove: function(el, name){ el.className = (el.className).replace(" "+name, ""); } }, dispatchEvent: function(type) { if (typeof eventList[type] === 'function') { return eventList[type].call(); } }, vendor: function(){ var tmp = doc.createElement("div"), prefixes = 'webkit Moz O ms'.split(' '), i; for (i in prefixes) { if (typeof tmp.style[prefixes[i] + 'Transition'] !== 'undefined') { return prefixes[i]; } } }, transitionCallback: function(){ return (cache.vendor==='Moz' || cache.vendor=='ms') ? 'transitionend' : cache.vendor+'TransitionEnd'; }, canTransform: function(){ return typeof settings.element.style[cache.vendor+'Transform'] !== 'undefined'; }, deepExtend: function(destination, source) { var property; for (property in source) { if (source[property] && source[property].constructor && source[property].constructor === Object) { destination[property] = destination[property] || {}; utils.deepExtend(destination[property], source[property]); } else { destination[property] = source[property]; } } return destination; }, angleOfDrag: function(x, y) { var degrees, theta; // Calc Theta
theta = Math.atan2(-(cache.startDragY - y), (cache.startDragX - x)); if (theta < 0) { theta += 2 * Math.PI; } // Calc Degrees
degrees = Math.floor(theta * (180 / Math.PI) - 180); if (degrees < 0 && degrees > -180) { degrees = 360 - Math.abs(degrees); } return Math.abs(degrees); }, events: { addEvent: function addEvent(element, eventName, func) { if (element.addEventListener) { return element.addEventListener(eventName, func, false); } else if (element.attachEvent) { return element.attachEvent("on" + eventName, func); } }, removeEvent: function addEvent(element, eventName, func) { if (element.addEventListener) { return element.removeEventListener(eventName, func, false); } else if (element.attachEvent) { return element.detachEvent("on" + eventName, func); } }, prevent: function(e) { if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } } }, parentUntil: function(el, attr) { while (el.parentNode) { if (el.getAttribute && el.getAttribute(attr)){ return el; } el = el.parentNode; } return null; } }, action = { translate: { get: { matrix: function(index) {
if( !utils.canTransform() ){ return parseInt(settings.element.style.left, 10); } else { var matrix = win.getComputedStyle(settings.element)[cache.vendor+'Transform'].match(/\((.*)\)/), ieOffset = 8; if (matrix) { matrix = matrix[1].split(','); if(matrix.length==16){ index+=ieOffset; } return parseInt(matrix[index], 10); } return 0; } } }, easeCallback: function(){ settings.element.style[cache.vendor+'Transition'] = ''; cache.translation = action.translate.get.matrix(4); cache.easing = false; clearInterval(cache.animatingInterval);
if(cache.easingTo===0){ utils.klass.remove(doc.body, 'snapjs-right'); utils.klass.remove(doc.body, 'snapjs-left'); }
utils.dispatchEvent('animated'); utils.events.removeEvent(settings.element, utils.transitionCallback(), action.translate.easeCallback); }, easeTo: function(n) {
if( !utils.canTransform() ){ cache.translation = n; action.translate.x(n); } else { cache.easing = true; cache.easingTo = n; settings.element.style[cache.vendor+'Transition'] = 'all ' + settings.transitionSpeed + 's ' + settings.easing; cache.animatingInterval = setInterval(function() { utils.dispatchEvent('animating'); }, 1);
utils.events.addEvent(settings.element, utils.transitionCallback(), action.translate.easeCallback); action.translate.x(n); } }, x: function(n) { if( (settings.disable=='left' && n>0) || (settings.disable=='right' && n<0) ){ return; }
n = parseInt(n, 10); if(isNaN(n)){ n = 0; }
if( utils.canTransform() ){ var theTranslate = 'translate3d(' + n + 'px, 0,0)'; settings.element.style[cache.vendor+'Transform'] = theTranslate; } else { settings.element.style.width = (win.innerWidth || doc.documentElement.clientWidth)+'px';
settings.element.style.left = n+'px'; settings.element.style.right = ''; } } }, drag: { listen: function() { cache.translation = 0; cache.easing = false; utils.events.addEvent(settings.element, utils.eventType('down'), action.drag.startDrag); utils.events.addEvent(settings.element, utils.eventType('move'), action.drag.dragging); utils.events.addEvent(settings.element, utils.eventType('up'), action.drag.endDrag); }, stopListening: function() { utils.events.removeEvent(settings.element, utils.eventType('down'), action.drag.startDrag); utils.events.removeEvent(settings.element, utils.eventType('move'), action.drag.dragging); utils.events.removeEvent(settings.element, utils.eventType('up'), action.drag.endDrag); }, startDrag: function(e) { // No drag on ignored elements
var ignoreParent = utils.parentUntil(e.target ? e.target : e.srcElement, 'data-snap-ignore'); if (ignoreParent) { utils.dispatchEvent('ignore'); return; }
utils.dispatchEvent('start'); settings.element.style[cache.vendor+'Transition'] = ''; cache.isDragging = true; cache.hasIntent = null; cache.intentChecked = false; cache.startDragX = utils.page('X', e); cache.startDragY = utils.page('Y', e); cache.dragWatchers = { current: 0, last: 0, hold: 0, state: '' }; cache.simpleStates = { opening: null, towards: null, hyperExtending: null, halfway: null, flick: null, translation: { absolute: 0, relative: 0, sinceDirectionChange: 0, percentage: 0 } }; }, dragging: function(e) { if (cache.isDragging && settings.touchToDrag) {
var thePageX = utils.page('X', e), thePageY = utils.page('Y', e), translated = cache.translation, absoluteTranslation = action.translate.get.matrix(4), whileDragX = thePageX - cache.startDragX, openingLeft = absoluteTranslation > 0, translateTo = whileDragX, diff;
// Shown no intent already
if((cache.intentChecked && !cache.hasIntent)){ return; }
if(settings.addBodyClasses){ if((absoluteTranslation)>0){ utils.klass.add(doc.body, 'snapjs-left'); utils.klass.remove(doc.body, 'snapjs-right'); } else if((absoluteTranslation)<0){ utils.klass.add(doc.body, 'snapjs-right'); utils.klass.remove(doc.body, 'snapjs-left'); } }
if (cache.hasIntent === false || cache.hasIntent === null) { var deg = utils.angleOfDrag(thePageX, thePageY), inRightRange = (deg >= 0 && deg <= settings.slideIntent) || (deg <= 360 && deg > (360 - settings.slideIntent)), inLeftRange = (deg >= 180 && deg <= (180 + settings.slideIntent)) || (deg <= 180 && deg >= (180 - settings.slideIntent)); if (!inLeftRange && !inRightRange) { cache.hasIntent = false; } else { cache.hasIntent = true; } cache.intentChecked = true; }
if ( (settings.minDragDistance>=Math.abs(thePageX-cache.startDragX)) && // Has user met minimum drag distance?
(cache.hasIntent === false) ) { return; }
utils.events.prevent(e); utils.dispatchEvent('drag');
cache.dragWatchers.current = thePageX; // Determine which direction we are going
if (cache.dragWatchers.last > thePageX) { if (cache.dragWatchers.state !== 'left') { cache.dragWatchers.state = 'left'; cache.dragWatchers.hold = thePageX; } cache.dragWatchers.last = thePageX; } else if (cache.dragWatchers.last < thePageX) { if (cache.dragWatchers.state !== 'right') { cache.dragWatchers.state = 'right'; cache.dragWatchers.hold = thePageX; } cache.dragWatchers.last = thePageX; } if (openingLeft) { // Pulling too far to the right
if (settings.maxPosition < absoluteTranslation) { diff = (absoluteTranslation - settings.maxPosition) * settings.resistance; translateTo = whileDragX - diff; } cache.simpleStates = { opening: 'left', towards: cache.dragWatchers.state, hyperExtending: settings.maxPosition < absoluteTranslation, halfway: absoluteTranslation > (settings.maxPosition / 2), flick: Math.abs(cache.dragWatchers.current - cache.dragWatchers.hold) > settings.flickThreshold, translation: { absolute: absoluteTranslation, relative: whileDragX, sinceDirectionChange: (cache.dragWatchers.current - cache.dragWatchers.hold), percentage: (absoluteTranslation/settings.maxPosition)*100 } }; } else { // Pulling too far to the left
if (settings.minPosition > absoluteTranslation) { diff = (absoluteTranslation - settings.minPosition) * settings.resistance; translateTo = whileDragX - diff; } cache.simpleStates = { opening: 'right', towards: cache.dragWatchers.state, hyperExtending: settings.minPosition > absoluteTranslation, halfway: absoluteTranslation < (settings.minPosition / 2), flick: Math.abs(cache.dragWatchers.current - cache.dragWatchers.hold) > settings.flickThreshold, translation: { absolute: absoluteTranslation, relative: whileDragX, sinceDirectionChange: (cache.dragWatchers.current - cache.dragWatchers.hold), percentage: (absoluteTranslation/settings.minPosition)*100 } }; } action.translate.x(translateTo + translated); } }, endDrag: function(e) { if (cache.isDragging) { utils.dispatchEvent('end'); var translated = action.translate.get.matrix(4);
// Tap Close
if (cache.dragWatchers.current === 0 && translated !== 0 && settings.tapToClose) { utils.events.prevent(e); action.translate.easeTo(0); cache.isDragging = false; cache.startDragX = 0; return; }
// Revealing Left
if (cache.simpleStates.opening === 'left') { // Halfway, Flicking, or Too Far Out
if ((cache.simpleStates.halfway || cache.simpleStates.hyperExtending || cache.simpleStates.flick)) { if (cache.simpleStates.flick && cache.simpleStates.towards === 'left') { // Flicking Closed
action.translate.easeTo(0); } else if ( (cache.simpleStates.flick && cache.simpleStates.towards === 'right') || // Flicking Open OR
(cache.simpleStates.halfway || cache.simpleStates.hyperExtending) // At least halfway open OR hyperextending
) { action.translate.easeTo(settings.maxPosition); // Open Left
} } else { action.translate.easeTo(0); // Close Left
} // Revealing Right
} else if (cache.simpleStates.opening === 'right') { // Halfway, Flicking, or Too Far Out
if ((cache.simpleStates.halfway || cache.simpleStates.hyperExtending || cache.simpleStates.flick)) { if (cache.simpleStates.flick && cache.simpleStates.towards === 'right') { // Flicking Closed
action.translate.easeTo(0); } else if ( (cache.simpleStates.flick && cache.simpleStates.towards === 'left') || // Flicking Open OR
(cache.simpleStates.halfway || cache.simpleStates.hyperExtending) // At least halfway open OR hyperextending
) { action.translate.easeTo(settings.minPosition); // Open Right
} } else { action.translate.easeTo(0); // Close Right
} } cache.isDragging = false; cache.startDragX = utils.page('X', e); } } } }, init = function(opts) { if (opts.element) { utils.deepExtend(settings, opts); cache.vendor = utils.vendor(); action.drag.listen(); } }; /* * Public */ this.open = function(side) {
utils.klass.remove(doc.body, 'snapjs-expand-left'); utils.klass.remove(doc.body, 'snapjs-expand-right');
if (side === 'left') { cache.simpleStates.opening = 'left'; cache.simpleStates.towards = 'right'; utils.klass.add(doc.body, 'snapjs-left'); utils.klass.remove(doc.body, 'snapjs-right'); action.translate.easeTo(settings.maxPosition); } else if (side === 'right') { cache.simpleStates.opening = 'right'; cache.simpleStates.towards = 'left'; utils.klass.remove(doc.body, 'snapjs-left'); utils.klass.add(doc.body, 'snapjs-right'); action.translate.easeTo(settings.minPosition); } }; this.close = function() { action.translate.easeTo(0); }; this.expand = function(side){ var to = win.innerWidth || doc.documentElement.clientWidth;
if(side==='left'){ utils.klass.add(doc.body, 'snapjs-expand-left'); utils.klass.remove(doc.body, 'snapjs-expand-right'); } else { utils.klass.add(doc.body, 'snapjs-expand-right'); utils.klass.remove(doc.body, 'snapjs-expand-left'); to *= -1; } action.translate.easeTo(to); };
this.on = function(evt, fn) { eventList[evt] = fn; return this; }; this.off = function(evt) { if (eventList[evt]) { eventList[evt] = false; } };
this.enable = function() { action.drag.listen(); }; this.disable = function() { action.drag.stopListening(); };
this.settings = function(opts){ utils.deepExtend(settings, opts); };
this.state = function() { var state, fromLeft = action.translate.get.matrix(4); if (fromLeft === settings.maxPosition) { state = 'left'; } else if (fromLeft === settings.minPosition) { state = 'right'; } else { state = 'closed'; } return { state: state, info: cache.simpleStates }; }; init(userOpts); }; if ((typeof module !== 'undefined') && module.exports) { module.exports = Snap; } if (typeof ender === 'undefined') { this.Snap = Snap; } if ((typeof define === "function") && define.amd) { define("snap", [], function() { return Snap; }); } }).call(this, window, document);
|