npm-polymer-elements
Version:
Polymer Elements package for npm
267 lines (223 loc) • 8.26 kB
HTML
<!--
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../polymer/polymer.html">
<!--
`<iron-swipeable-container>` is a container that allows any of its nested
children (native or custom elements) to be swiped away. By default it supports
a curved or horizontal transition, but the transition duration and properties
can be customized.
Example:
<iron-swipeable-container>
<div>I can be swiped</div>
<paper-card heading="Me too!"></paper-card>
</iron-swipeable-container>
To disable swiping on individual children, you must give them the `.disable-swipe`
class. Alternatively, to disable swiping on the whole container, you can use its
`disable-swipe` attribute:
<iron-swipeable-container>
<div class="disable-swipe">I cannot be swiped be swiped</div>
<paper-card heading="But I can!"></paper-card>
</iron-swipeable-container>
<iron-swipeable-container disable-swipe>
<div>I cannot be swiped</div>
<paper-card heading="Me neither :("></paper-card>
</iron-swipeable-container>
It is a good idea to disable text selection on any of the children that you
want to be swiped:
.swipe {
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
user-select: none;
cursor: default;
}
Iron Elements
iron-swipeable-container
demo/index.html
-->
<dom-module id="iron-swipeable-container">
<template>
<style>
:host {
display: block;
}
</style>
<content id="content"></content>
</template>
</dom-module>
<script>
Polymer({
is: 'iron-swipeable-container',
/**
* Fired when a child element is swiped away.
*
* @event iron-swipe
*/
properties: {
/**
* The style in which to swipe the card. Currently supported
* options are `curve | horizontal`. If left unspecified, the default
* is assumed to be `horizontal`.
*/
swipeStyle: {
type: String,
value: 'horizontal'
},
/**
* If true, then the container will not allow swiping.
*/
disabled: {
type: Boolean,
value: false
},
/**
* The ratio of the width of the element that the translation animation
* should happen over. For example, if the `widthRatio` is 3, the
* animation will take place on a distance 3 times the width of the
* element being swiped.
*/
widthRatio: {
type: Number,
value: 3
},
/**
* The ratio of the total animation distance after which the opacity
* transformation begins. For example, if the `widthRatio` is 1 and
* the `opacityRate` is 0.5, then the element needs to travel half its
* width before its opacity starts decreasing.
*/
opacityRate: {
type: Number,
value: 0.2
},
/**
* The CSS transition applied while swiping.
*/
transition: {
type: String,
value: '300ms cubic-bezier(0.4, 0.0, 0.2, 1)'
}
},
ready: function() {
this._transitionProperty = 'opacity, transform';
this._swipeComplete = false;
this._direction = '';
},
attached: function() {
this._nodeObserver = Polymer.dom(this.$.content).observeNodes(
function(mutations) {
for (var i = 0; i < mutations.addedNodes.length; i++) {
this._addListeners(mutations.addedNodes[i]);
}
for (var i = 0; i < mutations.removedNodes.length; i++) {
this._removeListeners(mutations.removedNodes[i]);
}
}.bind(this));
},
_addListeners: function(node) {
if (node.nodeType === Node.TEXT_NODE || node.nodeType === Node.COMMENT_NODE)
return;
// Set up the animation.
node.style.transitionProperty = this._transitionProperty;
node.style.transition = this.transition;
this.listen(node, 'track', '_onTrack');
this.listen(node, 'transitionend', '_onTransitionEnd');
},
_removeListeners: function(node) {
if (node.nodeType === Node.TEXT_NODE)
return;
this.unlisten(node, 'track', '_onTrack');
this.unlisten(node, 'transitionend', '_onTransitionEnd');
},
detached: function() {
if (this._nodeObserver) {
Polymer.dom(this.$.content).unobserveNodes(this._nodeObserver);
this._nodeObserver = null;
}
},
_onTrack: function(event) {
if (this.disabled)
return;
var target = event.currentTarget;
if (target.classList.contains('disable-swipe'))
return;
var track = event.detail;
if (track.state === 'start') {
this._trackStart(track, target);
} else if (track.state === 'track') {
this._trackMove(track, target);
} else if (track.state === 'end') {
this._trackEnd(track, target);
}
},
_trackStart: function(event, target) {
// Save the width of the element, so that we don't trigger a style
// recalc every time we need it.
this._nodeWidth = target.offsetWidth;
target.style.transition = 'none';
},
_trackMove: function(event, target) {
this._animate(event.dx, target);
},
_trackEnd: function(event, target) {
// The element is swiped away if it's moved halfway its total width.
this._swipeComplete = Math.abs(event.dx) > this._nodeWidth / 2;
this._direction = event.dx > 0;
this._swipeEnd(target);
},
_animate: function(x, target) {
var direction = x > 0 ? 1 : -1;
// This is the total distance the animation will take place over.
var totalDistance = this._nodeWidth * this.widthRatio;
// Opacity distance overflow. `this._nodeWidth * this.opacityRate` is the
// total distance the element needs to travel to become completely
// transparent, and `x` is how much the element has already travelled.
var opaqueDistance = Math.max(0, Math.abs(x) - this._nodeWidth * this.opacityRate);
var opacity = Math.max(0, (totalDistance - opaqueDistance) / totalDistance);
target.style.opacity = opacity;
var translate, rotate;
if (this.swipeStyle === 'horizontal') {
translate = 'translate3d(' + x + 'px,' + 0 + 'px,0)';
rotate = '';
} else { // Default is assumed to be `curve`.
// Assume the element will be completely transparent at 90 degrees, so
// figure out the rotation and vertical displacement needed to
// achieve that.
var y = totalDistance - Math.sqrt(totalDistance * totalDistance - opaqueDistance * opaqueDistance);
var deg = (1 - opacity) * direction * 90;
translate = 'translate3d(' + x + 'px,' + y + 'px,0)';
rotate = ' rotate(' + deg + 'deg)';
}
this.transform(translate + rotate, target);
},
_swipeEnd: function(target) {
// Restore the original transition;
target.style.transition = this.transition;
if (this._swipeComplete) {
// If the element is ready to be swiped away, then translate it to the full
// transparency distance.
var totalDistance = this._nodeWidth * this.widthRatio;
this._animate(this._direction ? totalDistance : -totalDistance, target);
} else {
this._animate(0, target);
}
},
_onTransitionEnd: function(event) {
var target = event.currentTarget;
if (this._swipeComplete && event.propertyName === 'opacity') {
Polymer.dom(this).removeChild(target);
this.fire('iron-swipe',
{ direction: this._direction > 0 ? 'right' : 'left',
target:target
});
}
}
});
</script>