280 lines
9.2 KiB
HTML
280 lines
9.2 KiB
HTML
<!--
|
|
Copyright (c) 2014 Hassan Hayat <hassan.hayat7@gmail.com>
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
-->
|
|
|
|
<!--
|
|
Provides horizontally swipeable pages.
|
|
The `swipe-pages` element must have `swipe-page` elements as its children.
|
|
This ensures readibility of code and a given amount of control on the behavior
|
|
of individual pages.
|
|
A `swipe-pages` element is an element containing several `swipe-page` elements
|
|
as subviews. The `swipe-pages` element is given a certain width and height
|
|
through CSS and then each individual `swipe-page` will automatically take of the full size
|
|
of the parent element. This means that the `swipe-page` elements are assumed
|
|
to all have the exact same size which they all derive from the `swipe-pages`
|
|
element.
|
|
###Example:
|
|
<swipe-pages selected = "1">
|
|
<swipe-page>Hey I'm page 0</swipe-page>
|
|
<swipe-page><h1>Hi, I'm on page 1</h1></swipe-page>
|
|
<swipe-page><p>I am page 2 and I totally rock!</p></swipe-page>
|
|
</swipe-pages>
|
|
Swiping left moves to the next page while swiping right moves to the previous page.
|
|
This behavior is very typical on mobile applications.
|
|
The key to this element is that when swiping, the page follows your finger
|
|
horizontally so as to give the user immediate feedback that he/she is swiping
|
|
between pages.
|
|
Pages only transition when the swipe gesture has crossed a certain threshold
|
|
which is exposed by the `threshold` attribute.
|
|
###Example:
|
|
<swipe-pages threshold = "0.5">
|
|
<swipe-page> ... </swipe-page>
|
|
<swipe-page> ... </swipe-page>
|
|
</swipe-pages>
|
|
By setting the `threshold` to 0.5, you ensure that the page will only transition
|
|
if the swipe gesture has crossed half the `swipe-pages` width horizontally.
|
|
`threshold` accepts values between 0 and 1.
|
|
A `threshold` value of 0 implies that any swipe gesture will cause a page
|
|
transition. A `threshold` value of 1 implies that no page transition is possible
|
|
as you must cross more that the entire size of the `swipe-pages` element horizontally
|
|
which is impossible given that the size of the `swipe-pages` element is well-defined.
|
|
@class swipe-pages
|
|
@blurb Provides horizontally swipeable pages.
|
|
@status alpha
|
|
-->
|
|
|
|
|
|
|
|
<link rel="import" href="../polymer/polymer.html">
|
|
<link rel="import" href="swipe-page.html">
|
|
|
|
|
|
<polymer-element name="swipe-pages" attributes="selected threshold transitionDuration">
|
|
|
|
<template>
|
|
|
|
<link rel="stylesheet" href="swipe-pages.css" />
|
|
|
|
<div id="pagesContainer" class="pagesContainer" touch-action="pan-y">
|
|
<content id="pages" select="swipe-page"></content>
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<script>
|
|
(function(){
|
|
'use strict';
|
|
|
|
var isWebkit = document.body.style.webkitTransform !== undefined;
|
|
|
|
var getPositionArray = function(length){
|
|
var a = [];
|
|
for (var i = 0; i < length; i++){
|
|
a[i] = (i * 100);
|
|
}
|
|
return a;
|
|
};
|
|
|
|
var resetAnimations = function(element){
|
|
|
|
setTransition("", element);
|
|
};
|
|
|
|
var setAnimationDuration = function(duration, element, easingFunction){
|
|
easingFunction = easingFunction || "ease-out";
|
|
var transition = (isWebkit ? "-webkit-" : "") + "transform " + duration.toString() + "s " + easingFunction;
|
|
setTransition(transition, element);
|
|
};
|
|
|
|
var setTransition = function(transition, element){
|
|
if (isWebkit){
|
|
element.$.pagesContainer.style.webkitTransition = transition;
|
|
}else{
|
|
element.$.pagesContainer.style.transition = transition;
|
|
}
|
|
};
|
|
|
|
var setTransform = function(transform, element){
|
|
if (isWebkit){
|
|
element.$.pagesContainer.style.webkitTransform = transform;
|
|
}else{
|
|
element.$.pagesContainer.style.transform = transform;
|
|
}
|
|
};
|
|
|
|
var moveToPosition = function(position, element){
|
|
var transform = "translateX(" + position.toString() + "%)";
|
|
setTransform(transform, element);
|
|
};
|
|
|
|
var moveToPage = function(pageNumber, element){
|
|
var position = -pageNumber * 100 / element.pageCount;
|
|
moveToPosition(position, element);
|
|
};
|
|
|
|
var resetScrollTop = function(element){
|
|
|
|
element.$.pageContainer.scrollTop = 0;
|
|
};
|
|
|
|
Polymer({
|
|
|
|
get pageCount(){
|
|
|
|
return this.$.pages.getDistributedNodes().length;
|
|
},
|
|
|
|
get pages(){
|
|
|
|
return this.$.pages.getDistributedNodes();
|
|
},
|
|
|
|
get pageWidth(){
|
|
|
|
return this.getBoundingClientRect().width;
|
|
},
|
|
|
|
resetPositions : function(){
|
|
var positionArray = getPositionArray(this.pageCount);
|
|
for (var i = 0; i < this.pageCount; i++){
|
|
this.pages[i].style.left = "" + (positionArray[i] / this.pageCount) + "%";
|
|
this.pages[i].style.top = "-" + positionArray[i].toString() + "%";
|
|
}
|
|
},
|
|
|
|
resetWidths : function(){
|
|
this.$.pagesContainer.style.width = "" + (100 * this.pageCount) + "%";
|
|
for (var i = 0; i < this.pageCount; i++){
|
|
this.pages[i].style.width = "" + (100 / this.pageCount) + "%";
|
|
}
|
|
},
|
|
|
|
|
|
setupEventHandlers : function(){
|
|
this.setupTrackStartEventHandler();
|
|
this.setupTrackEventHandler();
|
|
this.setupTrackEndEventHandler();
|
|
},
|
|
|
|
setupTrackStartEventHandler : function(){
|
|
PolymerGestures.addEventListener(this, "trackstart", function(event){
|
|
resetAnimations(this);
|
|
});
|
|
},
|
|
|
|
setupTrackEventHandler : function(){
|
|
PolymerGestures.addEventListener(this, "track", function(event){
|
|
var isFirstPage = (this.selected === 0);
|
|
var isLastPage = (this.selected === (this.pageCount - 1));
|
|
|
|
var userIsSwipingLeftwards = (event.dx < 0);
|
|
var userIsSwipingRightwards = (event.dx > 0);
|
|
|
|
var tryingToSwipeToLeftOfFirstPage = userIsSwipingRightwards && isFirstPage;
|
|
var tryingToSwipeToRightOfLastPage = userIsSwipingLeftwards && isLastPage;
|
|
|
|
var tryingToSwipeToOutOfBoundsPage = tryingToSwipeToLeftOfFirstPage || tryingToSwipeToRightOfLastPage;
|
|
|
|
if (!tryingToSwipeToOutOfBoundsPage){
|
|
var position = -(this.selected - (event.dx / this.pageWidth)) * 100 / this.pageCount;
|
|
moveToPosition(position, this);
|
|
}
|
|
});
|
|
},
|
|
|
|
setupTrackEndEventHandler : function(){
|
|
PolymerGestures.addEventListener(this, "trackend", function(event){
|
|
var userIsSwipingLeftwards = (event.dx < 0);
|
|
var userIsSwipingRightwards = (event.dx > 0);
|
|
|
|
var thresholdWasCrossed = (Math.abs(event.dx) / this.pageWidth) > this.threshold;
|
|
|
|
setAnimationDuration(this.transitionDuration, this);
|
|
|
|
if (thresholdWasCrossed){
|
|
if (userIsSwipingRightwards){
|
|
this.selected = Math.max(this.selected - 1, 0);
|
|
}
|
|
if (userIsSwipingLeftwards){
|
|
this.selected = Math.min(this.selected + 1, this.pageCount - 1);
|
|
}
|
|
}else{
|
|
moveToPage(this.selected, this);
|
|
}
|
|
});
|
|
},
|
|
|
|
publish: {
|
|
|
|
/**
|
|
* Specifies the index of the currently selected page
|
|
* @attribute selected
|
|
* @default 0
|
|
* @type Number
|
|
*/
|
|
threshold: 0.3,
|
|
|
|
/**
|
|
* Specifies the threshold the user must swipe in order to
|
|
* cause a page transition.
|
|
* Only accepts values between 0 and 1.
|
|
* @attribute threshold
|
|
* @default 0.3
|
|
* @type Number
|
|
*/
|
|
transitionDuration: 0.3,
|
|
|
|
/**
|
|
* Specifies the duration (in seconds) of the transition from one
|
|
* page to another
|
|
* @attribute transitionDuration
|
|
* @default 0.3
|
|
* @type Number
|
|
*/
|
|
selected: 0,
|
|
},
|
|
|
|
selectedChanged : function(oldValue, newValue){
|
|
if (newValue >= this.pageCount || newValue < 0){
|
|
var errorMessage = "Page " + newValue.toString() + " is not a defined page. There are only " + this.pageCount.toString() + " pages.";
|
|
throw errorMessage;
|
|
}
|
|
moveToPage(newValue, this);
|
|
|
|
var self = this;
|
|
window.setTimeout(function(){
|
|
resetScrollTop(self.pages[oldValue]);
|
|
}, self.transitionDuration * 1000);
|
|
|
|
},
|
|
|
|
ready: function(){
|
|
this.resetPositions();
|
|
this.resetWidths();
|
|
this.setupEventHandlers();
|
|
}
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
|
|
</script>
|
|
|
|
</polymer-element>
|