From ba1842bd2162d4fa3c7189621825afd765bea5fa Mon Sep 17 00:00:00 2001 From: Eric Bidelman Date: Sat, 14 Apr 2012 14:39:08 -0700 Subject: Hammer.js instead. Touch working on mobile style sheets now in main .html file. Better for mobile rather than dynamically loading in js matcMedia polyfill --- js/hammer.js | 586 +++++++++++++++++++++++++++++++++++++++++++++ js/polyfills/matchMedia.js | 30 +++ js/slides.js | 52 +++- 3 files changed, 657 insertions(+), 11 deletions(-) create mode 100755 js/hammer.js create mode 100644 js/polyfills/matchMedia.js (limited to 'js') diff --git a/js/hammer.js b/js/hammer.js new file mode 100755 index 0000000..44a5802 --- /dev/null +++ b/js/hammer.js @@ -0,0 +1,586 @@ +/* + * Hammer.JS + * version 0.4 + * author: Eight Media + * https://github.com/EightMedia/hammer.js + */ +function Hammer(element, options, undefined) +{ + var self = this; + + var defaults = { + // prevent the default event or not... might be buggy when false + prevent_default : false, + css_hacks : true, + + drag : true, + drag_vertical : true, + drag_horizontal : true, + // minimum distance before the drag event starts + drag_min_distance : 20, // pixels + + // pinch zoom and rotation + transform : true, + scale_treshold : 0.1, + rotation_treshold : 15, // degrees + + tap : true, + tap_double : true, + tap_max_interval : 300, + tap_double_distance: 20, + + hold : true, + hold_timeout : 500 + }; + options = mergeObject(defaults, options); + + // some css hacks + (function() { + if(!options.css_hacks) { + return false; + } + + var vendors = ['webkit','moz','ms','o','']; + var css_props = { + "userSelect": "none", + "touchCallout": "none", + "userDrag": "none", + "tapHighlightColor": "rgba(0,0,0,0)" + }; + + var prop = ''; + for(var i = 0; i < vendors.length; i++) { + for(var p in css_props) { + prop = p; + if(vendors[i]) { + prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1); + } + element.style[ prop ] = css_props[p]; + } + } + })(); + + // holds the distance that has been moved + var _distance = 0; + + // holds the exact angle that has been moved + var _angle = 0; + + // holds the diraction that has been moved + var _direction = 0; + + // holds position movement for sliding + var _pos = { }; + + // how many fingers are on the screen + var _fingers = 0; + + var _first = false; + + var _gesture = null; + var _prev_gesture = null; + + var _touch_start_time = null; + var _prev_tap_pos = {x: 0, y: 0}; + var _prev_tap_end_time = null; + + var _hold_timer = null; + + var _offset = {}; + + // keep track of the mouse status + var _mousedown = false; + + var _event_start; + var _event_move; + var _event_end; + + + /** + * angle to direction define + * @param float angle + * @return string direction + */ + this.getDirectionFromAngle = function( angle ) + { + var directions = { + down: angle >= 45 && angle < 135, //90 + left: angle >= 135 || angle <= -135, //180 + up: angle < -45 && angle > -135, //270 + right: angle >= -45 && angle <= 45 //0 + }; + + var direction, key; + for(key in directions){ + if(directions[key]){ + direction = key; + break; + } + } + return direction; + }; + + + /** + * count the number of fingers in the event + * when no fingers are detected, one finger is returned (mouse pointer) + * @param event + * @return int fingers + */ + function countFingers( event ) + { + // there is a bug on android (until v4?) that touches is always 1, + // so no multitouch is supported, e.g. no, zoom and rotation... + return event.touches ? event.touches.length : 1; + } + + + /** + * get the x and y positions from the event object + * @param event + * @return array [{ x: int, y: int }] + */ + function getXYfromEvent( event ) + { + event = event || window.event; + + // no touches, use the event pageX and pageY + if(!event.touches) { + var doc = document, + body = doc.body; + + return [{ + x: event.pageX || event.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && doc.clientLeft || 0 ), + y: event.pageY || event.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && doc.clientTop || 0 ) + }]; + } + // multitouch, return array with positions + else { + var pos = [], src; + for(var t=0, len=event.touches.length; t options.drag_min_distance) || _gesture == 'drag') { + // calculate the angle + _angle = getAngle(_pos.start[0], _pos.move[0]); + _direction = self.getDirectionFromAngle(_angle); + + // check the movement and stop if we go in the wrong direction + var is_vertical = (_direction == 'up' || _direction == 'down'); + if(((is_vertical && !options.drag_vertical) || (!is_vertical && !options.drag_horizontal)) + && (_distance > options.drag_min_distance)) { + return; + } + + _gesture = 'drag'; + + var position = { x: _pos.move[0].x - _offset.left, + y: _pos.move[0].y - _offset.top }; + + var event_obj = { + originalEvent : event, + position : position, + direction : _direction, + distance : _distance, + distanceX : _distance_x, + distanceY : _distance_y, + angle : _angle + }; + + // on the first time trigger the start event + if(_first) { + triggerEvent("dragstart", event_obj); + + _first = false; + } + + // normal slide event + triggerEvent("drag", event_obj); + + cancelEvent(event); + } + }, + + + // transform gesture + // fired on touchmove + transform : function(event) + { + if(options.transform) { + var scale = event.scale || 1; + var rotation = event.rotation || 0; + + if(countFingers(event) != 2) { + return false; + } + + if(_gesture != 'drag' && + (_gesture == 'transform' || Math.abs(1-scale) > options.scale_treshold + || Math.abs(rotation) > options.rotation_treshold)) { + _gesture = 'transform'; + + _pos.center = { x: ((_pos.move[0].x + _pos.move[1].x) / 2) - _offset.left, + y: ((_pos.move[0].y + _pos.move[1].y) / 2) - _offset.top }; + + var event_obj = { + originalEvent : event, + position : _pos.center, + scale : scale, + rotation : rotation + }; + + // on the first time trigger the start event + if(_first) { + triggerEvent("transformstart", event_obj); + _first = false; + } + + triggerEvent("transform", event_obj); + + cancelEvent(event); + + return true; + } + } + + return false; + }, + + + // tap and double tap gesture + // fired on touchend + tap : function(event) + { + // compare the kind of gesture by time + var now = new Date().getTime(); + var touch_time = now - _touch_start_time; + + // dont fire when hold is fired + if(options.hold && !(options.hold && options.hold_timeout > touch_time)) { + return; + } + + // when previous event was tap and the tap was max_interval ms ago + var is_double_tap = (function(){ + if (_prev_tap_pos && options.tap_double && _prev_gesture == 'tap' && (_touch_start_time - _prev_tap_end_time) < options.tap_max_interval) { + var x_distance = Math.abs(_prev_tap_pos[0].x - _pos.start[0].x); + var y_distance = Math.abs(_prev_tap_pos[0].y - _pos.start[0].y); + return (_prev_tap_pos && _pos.start && Math.max(x_distance, y_distance) < options.tap_double_distance); + + } + return false; + })(); + + if(is_double_tap) { + _gesture = 'double_tap'; + _prev_tap_end_time = null; + + triggerEvent("doubletap", { + originalEvent : event, + position : _pos.start + }); + cancelEvent(event); + } + + // single tap is single touch + else { + _gesture = 'tap'; + _prev_tap_end_time = now; + _prev_tap_pos = _pos.start; + + if(options.tap) { + triggerEvent("tap", { + originalEvent : event, + position : _pos.start + }); + cancelEvent(event); + } + } + + } + + }; + + + function handleEvents(event) + { + switch(event.type) + { + case 'mousedown': + case 'touchstart': + _pos.start = getXYfromEvent(event); + _touch_start_time = new Date().getTime(); + _fingers = countFingers(event); + _first = true; + _event_start = event; + + // borrowed from jquery offset https://github.com/jquery/jquery/blob/master/src/offset.js + var box = element.getBoundingClientRect(); + var clientTop = element.clientTop || document.body.clientTop || 0; + var clientLeft = element.clientLeft || document.body.clientLeft || 0; + var scrollTop = window.pageYOffset || element.scrollTop || document.body.scrollTop; + var scrollLeft = window.pageXOffset || element.scrollLeft || document.body.scrollLeft; + + _offset = { + top: box.top + scrollTop - clientTop, + left: box.left + scrollLeft - clientLeft + }; + + _mousedown = true; + + // hold gesture + gestures.hold(event); + + if(options.prevent_default) { + cancelEvent(event); + } + break; + + case 'mousemove': + case 'touchmove': + if(!_mousedown) { + return false; + } + _event_move = event; + _pos.move = getXYfromEvent(event); + + if(!gestures.transform(event)) { + gestures.drag(event); + } + break; + + case 'mouseup': + case 'mouseout': + case 'touchcancel': + case 'touchend': + if(!_mousedown || (_gesture != 'transform' && event.touches && event.touches.length > 0)) { + return false; + } + + _mousedown = false; + _event_end = event; + + // drag gesture + // dragstart is triggered, so dragend is possible + if(_gesture == 'drag') { + triggerEvent("dragend", { + originalEvent : event, + direction : _direction, + distance : _distance, + angle : _angle + }); + } + + // transform + // transformstart is triggered, so transformed is possible + else if(_gesture == 'transform') { + triggerEvent("transformend", { + originalEvent : event, + position : _pos.center, + scale : event.scale, + rotation : event.rotation + }); + } + else { + gestures.tap(_event_start); + } + + _prev_gesture = _gesture; + + // reset vars + reset(); + break; + } + } + + + // bind events for touch devices + // except for windows phone 7.5, it doesnt support touch events..! + if('ontouchstart' in window) { + element.addEventListener("touchstart", handleEvents, false); + element.addEventListener("touchmove", handleEvents, false); + element.addEventListener("touchend", handleEvents, false); + element.addEventListener("touchcancel", handleEvents, false); + } + // for non-touch + else { + + if(element.addEventListener){ // prevent old IE errors + element.addEventListener("mouseout", function(event) { + if(!isInsideHammer(element, event.relatedTarget)) { + handleEvents(event); + } + }, false); + element.addEventListener("mouseup", handleEvents, false); + element.addEventListener("mousedown", handleEvents, false); + element.addEventListener("mousemove", handleEvents, false); + + // events for older IE + }else if(document.attachEvent){ + element.attachEvent("onmouseout", function(event) { + if(!isInsideHammer(element, event.relatedTarget)) { + handleEvents(event); + } + }, false); + element.attachEvent("onmouseup", handleEvents); + element.attachEvent("onmousedown", handleEvents); + element.attachEvent("onmousemove", handleEvents); + } + } + + + /** + * find if element is (inside) given parent element + * @param object element + * @param object parent + * @return bool inside + */ + function isInsideHammer(parent, child) { + // get related target for IE + if(!child && window.event && window.event.toElement){ + child = window.event.toElement; + } + + if(parent === child){ + return true; + } + + // loop over parentNodes of child until we find hammer element + if(child){ + var node = child.parentNode; + while(node !== null){ + if(node === parent){ + return true; + }; + node = node.parentNode; + } + } + return false; + } + + + /** + * merge 2 objects into a new object + * @param object obj1 + * @param object obj2 + * @return object merged object + */ + function mergeObject(obj1, obj2) { + var output = {}; + + if(!obj2) { + return obj1; + } + + for (var prop in obj1) { + if (prop in obj2) { + output[prop] = obj2[prop]; + } else { + output[prop] = obj1[prop]; + } + } + return output; + } + + function isFunction( obj ){ + return Object.prototype.toString.call( obj ) == "[object Function]"; + } +} \ No newline at end of file diff --git a/js/polyfills/matchMedia.js b/js/polyfills/matchMedia.js new file mode 100644 index 0000000..6d4d17c --- /dev/null +++ b/js/polyfills/matchMedia.js @@ -0,0 +1,30 @@ +/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ + +window.matchMedia = window.matchMedia || (function(doc, undefined){ + + var bool, + docElem = doc.documentElement, + refNode = docElem.firstElementChild || docElem.firstChild, + // fakeBody required for + fakeBody = doc.createElement('body'), + div = doc.createElement('div'); + + div.id = 'mq-test-1'; + div.style.cssText = "position:absolute;top:-100em"; + fakeBody.style.background = "none"; + fakeBody.appendChild(div); + + return function(q){ + + div.innerHTML = '­'; + + docElem.insertBefore(fakeBody, refNode); + bool = div.offsetWidth == 42; + docElem.removeChild(fakeBody); + + return { matches: bool, media: q }; + }; + +})(document); + + diff --git a/js/slides.js b/js/slides.js index cf31aba..784d122 100644 --- a/js/slides.js +++ b/js/slides.js @@ -52,6 +52,18 @@ SlideDeck.prototype.getCurrentSlideFromHash_ = function() { SlideDeck.prototype.onDomLoaded_ = function(e) { this.slides_ = document.querySelectorAll('slide:not([hidden]):not(.backdrop)'); + // If we're on a smartphone device, load phone.css. + if (window.matchMedia('only screen and (max-device-width: 480px)').matches) { + // var style = document.createElement('link'); + // style.rel = 'stylesheet'; + // style.type = 'text/css'; + // style.href = this.CSS_DIR_ + 'phone.css'; + // document.querySelector('head').appendChild(style); + + // Remove widescreen if it's applied. + document.querySelector('slides').classList.remove('layout-widescreen'); + } + // Load config. this.loadConfig_(); this.addEventListeners_(); @@ -172,7 +184,7 @@ SlideDeck.prototype.loadConfig_ = function() { var settings = this.config_.settings; - this.loadTheme_(settings.theme || 'default'); + this.loadTheme_(settings.theme || []); if (settings.favIcon) { this.addFavIcon_(settings.favIcon); @@ -235,21 +247,32 @@ SlideDeck.prototype.loadConfig_ = function() { document.querySelector('[data-config-presenter]').innerHTML = html; } + var slides = document.querySelector('slides'); + /* Clicking and tapping */ var el = document.createElement('div'); el.classList.add('slide-area'); el.id = 'prev-slide-area'; el.addEventListener('click', this.prevSlide.bind(this), false); - document.querySelector('slides').appendChild(el); + slides.appendChild(el); var el = document.createElement('div'); el.classList.add('slide-area'); el.id = 'next-slide-area'; el.addEventListener('click', this.nextSlide.bind(this), false); - document.querySelector('slides').appendChild(el); + slides.appendChild(el); if (!!!('enableTouch' in settings) || settings.enableTouch) { - var toucher = new TouchManager(this); + var self = this; + + var hammer = new Hammer(slides); + hammer.ondragend = function(e) { + if (e.direction == 'right' || e.direction == 'down') { + self.prevSlide(); + } else if (e.direction == 'left' || e.direction == 'up') { + self.nextSlide(); + } + }; } }; @@ -553,7 +576,13 @@ SlideDeck.prototype.addFavIcon_ = function(favIcon) { * @param {string} theme */ SlideDeck.prototype.loadTheme_ = function(theme) { - var styles = [/*'../../js/prettify/prettify',*/ theme]; + var styles = []; + if (theme.constructor.name === 'String') { + styles.push(theme); + } else { + styles = theme; + } + for (var i = 0, style; themeUrl = styles[i]; i++) { var style = document.createElement('link'); style.rel = 'stylesheet'; @@ -565,11 +594,11 @@ SlideDeck.prototype.loadTheme_ = function(theme) { } document.querySelector('head').appendChild(style); } - - var viewportMeta = document.createElement('meta'); - viewportMeta.name = 'viewport'; - viewportMeta.content = 'width=1100,height=750'; - document.querySelector('head').appendChild(viewportMeta); + // TODO(ericbidelman): Removed this. + // var viewportMeta = document.createElement('meta'); + // viewportMeta.name = 'viewport'; + // viewportMeta.content = 'width=1100,height=750'; + // document.querySelector('head').appendChild(viewportMeta); var appleMeta = document.createElement('meta'); appleMeta.name = 'apple-mobile-web-app-capable'; @@ -601,7 +630,8 @@ SlideDeck.prototype.loadAnalytics_ = function() { yepnope({ test: !!body.classList && !!body.dataset, - nope: ['js/polyfills/classList.min.js', 'js/polyfills/dataset.min.js'], + nope: ['js/polyfills/classList.min.js', 'js/polyfills/dataset.min.js', + 'js/polyfills/matchMedia.js'], complete: function() { window.slidedeck = new SlideDeck(); } -- cgit v1.2.3