diff --git a/dist/css/ionic-ios7.css b/dist/css/ionic-ios7.css index a7a55b5fe2..b75758f7e1 100644 --- a/dist/css/ionic-ios7.css +++ b/dist/css/ionic-ios7.css @@ -311,6 +311,9 @@ a { -webkit-user-drag: none; -webkit-tap-highlight-color: transparent; } +img { + -webkit-user-drag: none; } + a:focus, button:focus { outline: 0; } diff --git a/dist/css/ionic-scoped.css b/dist/css/ionic-scoped.css index 23aefb3a4a..bab5c9e234 100644 --- a/dist/css/ionic-scoped.css +++ b/dist/css/ionic-scoped.css @@ -1055,6 +1055,8 @@ .ionic a { -webkit-user-drag: none; -webkit-tap-highlight-color: transparent; } + .ionic img { + -webkit-user-drag: none; } .ionic a:focus, .ionic button:focus { outline: 0; } .ionic body, .ionic .ionic-body { diff --git a/dist/css/ionic.css b/dist/css/ionic.css index 05fbd12629..a87c177d41 100644 --- a/dist/css/ionic.css +++ b/dist/css/ionic.css @@ -1375,6 +1375,9 @@ a { -webkit-user-drag: none; -webkit-tap-highlight-color: transparent; } +img { + -webkit-user-drag: none; } + a:focus, button:focus { outline: 0; } @@ -3084,6 +3087,19 @@ a.button { .card-footer { padding: 10px; } +.slide-box { + position: relative; + white-space: nowrap; + -webkit-transition: -webkit-transform 0 ease-in-out; } + +.slide-box-content { + display: inline-block; + vertical-align: top; + width: 100%; + height: 100%; } + .slide-box-content img { + width: 100%; } + .slide-in-up { opacity: 0; -webkit-transform: translate3d(0, 100%, 0); diff --git a/dist/js/ionic.js b/dist/js/ionic.js index b77c4e60ca..e0195276ff 100644 --- a/dist/js/ionic.js +++ b/dist/js/ionic.js @@ -2264,6 +2264,125 @@ window.ionic = { })(ionic); ; +/** + * The SlideBox is a swipeable, slidable, slideshowable box. Think of any image gallery + * or iOS "dot" pager gallery, or maybe a carousel. + * + * Each screen fills the full width and height of the viewport, and screens can + * be swiped between, or set to automatically transition. + */ +(function(ionic) { +'use strict'; + + ionic.views.SlideBox = function(opts) { + var _this = this; + + this.el = opts.el; + + this.dragThresholdX = opts.dragThresholdX || 10; + + // Listen for drag and release events + window.ionic.onGesture('drag', function(e) { + _this._handleDrag(e); + }, this.el); + window.ionic.onGesture('release', function(e) { + _this._handleEndDrag(e); + }, this.el); + }; + + ionic.views.SlideBox.prototype = { + _initDrag: function() { + this._isDragging = false; + this._currentDrag = null; + }, + _startDrag: function(e) { + var offsetX, content; + + this._initDrag(); + + // Make sure to grab the element we will slide as our target + content = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'slide-box'); + if(!content) { + return; + } + + // Disable transitions during drag + content.style.webkitTransitionDuration = '0'; + + // Grab the starting X point for the item (for example, so we can tell whether it is open or closed to start) + offsetX = parseFloat(content.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0; + + this._currentDrag = { + content: content, + startOffsetX: offsetX + }; + }, + + _handleEndDrag: function(e) { + var _this = this; + + // We didn't have a drag, so just init and leave + if(!this._currentDrag) { + this._initDrag(); + return; + } + + // Snap to the correct spot + + var content = this._currentDrag.content; + + // Enable transition duration + content.style.webkitTransitionDuration = '0.2s'; + + var offsetX = Math.abs(parseFloat(content.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0); + var slideWidth = content.offsetWidth; + var totalWidth = content.offsetWidth * content.children.length; + + // Calculate how far in this slide we've dragged + var ratio = (offsetX % slideWidth) / slideWidth; + + var finalOffsetX = Math.min(totalWidth, Math.ceil(offsetX / slideWidth) * slideWidth); + + if(ratio < 0.5) { + finalOffsetX = Math.max(0, Math.floor(offsetX / slideWidth) * slideWidth); + } + + + content.style.webkitTransform = 'translate3d(' + -finalOffsetX + 'px, 0, 0)'; + + this._initDrag(); + }, + /** + * Process the drag event to move the item to the left or right. + */ + _handleDrag: function(e) { + var _this = this; + window.requestAnimationFrame(function() { + // We really aren't dragging + if(!_this._currentDrag) { + _this._startDrag(e); + } + + // Check if we should start dragging. Check if we've dragged past the threshold, + // or we are starting from the open state. + if(!_this._isDragging && + ((Math.abs(e.gesture.deltaX) > _this.dragThresholdX) || (Math.abs(_this._currentDrag.startOffsetX) > 0))) + { + _this._isDragging = true; + } + + if(_this._isDragging) { + // Grab the new X point, capping it at zero + var newX = Math.min(0, _this._currentDrag.startOffsetX + e.gesture.deltaX); + + _this._currentDrag.content.style.webkitTransform = 'translate3d(' + newX + 'px, 0, 0)'; + } + }); + } + }; + +})(window.ionic); +; (function(ionic) { 'use strict'; diff --git a/js/views/slideBox.js b/js/views/slideBox.js new file mode 100644 index 0000000000..46c40293ae --- /dev/null +++ b/js/views/slideBox.js @@ -0,0 +1,118 @@ +/** + * The SlideBox is a swipeable, slidable, slideshowable box. Think of any image gallery + * or iOS "dot" pager gallery, or maybe a carousel. + * + * Each screen fills the full width and height of the viewport, and screens can + * be swiped between, or set to automatically transition. + */ +(function(ionic) { +'use strict'; + + ionic.views.SlideBox = function(opts) { + var _this = this; + + this.el = opts.el; + + this.dragThresholdX = opts.dragThresholdX || 10; + + // Listen for drag and release events + window.ionic.onGesture('drag', function(e) { + _this._handleDrag(e); + }, this.el); + window.ionic.onGesture('release', function(e) { + _this._handleEndDrag(e); + }, this.el); + }; + + ionic.views.SlideBox.prototype = { + _initDrag: function() { + this._isDragging = false; + this._currentDrag = null; + }, + _startDrag: function(e) { + var offsetX, content; + + this._initDrag(); + + // Make sure to grab the element we will slide as our target + content = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'slide-box'); + if(!content) { + return; + } + + // Disable transitions during drag + content.style.webkitTransitionDuration = '0'; + + // Grab the starting X point for the item (for example, so we can tell whether it is open or closed to start) + offsetX = parseFloat(content.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0; + + this._currentDrag = { + content: content, + startOffsetX: offsetX + }; + }, + + _handleEndDrag: function(e) { + var _this = this; + + // We didn't have a drag, so just init and leave + if(!this._currentDrag) { + this._initDrag(); + return; + } + + // Snap to the correct spot + + var content = this._currentDrag.content; + + // Enable transition duration + content.style.webkitTransitionDuration = '0.2s'; + + var offsetX = Math.abs(parseFloat(content.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0); + var slideWidth = content.offsetWidth; + var totalWidth = content.offsetWidth * content.children.length; + + // Calculate how far in this slide we've dragged + var ratio = (offsetX % slideWidth) / slideWidth; + + var finalOffsetX = Math.min(totalWidth, Math.ceil(offsetX / slideWidth) * slideWidth); + + if(ratio < 0.5) { + finalOffsetX = Math.max(0, Math.floor(offsetX / slideWidth) * slideWidth); + } + + + content.style.webkitTransform = 'translate3d(' + -finalOffsetX + 'px, 0, 0)'; + + this._initDrag(); + }, + /** + * Process the drag event to move the item to the left or right. + */ + _handleDrag: function(e) { + var _this = this; + window.requestAnimationFrame(function() { + // We really aren't dragging + if(!_this._currentDrag) { + _this._startDrag(e); + } + + // Check if we should start dragging. Check if we've dragged past the threshold, + // or we are starting from the open state. + if(!_this._isDragging && + ((Math.abs(e.gesture.deltaX) > _this.dragThresholdX) || (Math.abs(_this._currentDrag.startOffsetX) > 0))) + { + _this._isDragging = true; + } + + if(_this._isDragging) { + // Grab the new X point, capping it at zero + var newX = Math.min(0, _this._currentDrag.startOffsetX + e.gesture.deltaX); + + _this._currentDrag.content.style.webkitTransform = 'translate3d(' + newX + 'px, 0, 0)'; + } + }); + } + }; + +})(window.ionic); diff --git a/scss/ionic/_scaffolding.scss b/scss/ionic/_scaffolding.scss index 9b194b9d05..1ef2972811 100644 --- a/scss/ionic/_scaffolding.scss +++ b/scss/ionic/_scaffolding.scss @@ -8,6 +8,9 @@ a { -webkit-user-drag: none; -webkit-tap-highlight-color: transparent; } +img { + -webkit-user-drag: none; +} a, button { &:focus { diff --git a/scss/ionic/_slideBox.scss b/scss/ionic/_slideBox.scss new file mode 100644 index 0000000000..ce96d99dfb --- /dev/null +++ b/scss/ionic/_slideBox.scss @@ -0,0 +1,18 @@ +.slide-box { + position: relative; + white-space: nowrap; + + -webkit-transition: -webkit-transform 0 ease-in-out; +} +.slide-box.animate { +} +.slide-box-content { + display: inline-block; + vertical-align: top; + width: 100%; + height: 100%; + + img { + width: 100%; + } +} diff --git a/scss/ionic/ionic.scss b/scss/ionic/ionic.scss index bdc0b6f170..d3587f980a 100644 --- a/scss/ionic/ionic.scss +++ b/scss/ionic/ionic.scss @@ -40,6 +40,8 @@ "alerts", "card", + "slideBox", + // Animations "animations", diff --git a/test/slideBox.html b/test/slideBox.html new file mode 100644 index 0000000000..23037bed6d --- /dev/null +++ b/test/slideBox.html @@ -0,0 +1,43 @@ + +
+ +