jellyfin-web/dashboard-ui/bower_components/paper-ripple/paper-ripple.html

724 lines
18 KiB
HTML
Raw Normal View History

2015-06-15 21:52:01 -07:00
<!--
Copyright (c) 2014 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">
<link rel="import" href="../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
<!--
`paper-ripple` provides a visual effect that other paper elements can
use to simulate a rippling effect emanating from the point of contact. The
effect can be visualized as a concentric circle with motion.
Example:
<paper-ripple></paper-ripple>
`paper-ripple` listens to "mousedown" and "mouseup" events so it would display ripple
effect when touches on it. You can also defeat the default behavior and
manually route the down and up actions to the ripple element. Note that it is
important if you call downAction() you will have to make sure to call
upAction() so that `paper-ripple` would end the animation loop.
Example:
<paper-ripple id="ripple" style="pointer-events: none;"></paper-ripple>
...
downAction: function(e) {
this.$.ripple.downAction({x: e.x, y: e.y});
},
upAction: function(e) {
this.$.ripple.upAction();
}
Styling ripple effect:
Use CSS color property to style the ripple:
paper-ripple {
color: #4285f4;
}
Note that CSS color property is inherited so it is not required to set it on
the `paper-ripple` element directly.
By default, the ripple is centered on the point of contact. Apply the `recenters`
attribute to have the ripple grow toward the center of its container.
<paper-ripple recenters></paper-ripple>
You can also center the ripple inside its container from the start.
<paper-ripple center></paper-ripple>
Apply `circle` class to make the rippling effect within a circle.
<paper-ripple class="circle"></paper-ripple>
@group Paper Elements
@element paper-ripple
@hero hero.svg
@demo demo/index.html
-->
<dom-module id="paper-ripple">
<!--
Fired when the animation finishes. This is useful if you want to wait until the ripple
animation finishes to perform some action.
@event transitionend
@param {Object} detail
@param {Object} detail.node The animated node
-->
<style>
:host {
display: block;
position: absolute;
border-radius: inherit;
overflow: hidden;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
:host([animating]) {
/* This resolves a rendering issue in Chrome (as of 40) where the
ripple is not properly clipped by its parent (which may have
rounded corners). See: http://jsbin.com/temexa/4
Note: We only apply this style conditionally. Otherwise, the browser
will create a new compositing layer for every ripple element on the
page, and that would be bad. */
-webkit-transform: translate(0, 0);
transform: translate3d(0, 0, 0);
}
:host([noink]) {
pointer-events: none;
}
#background,
#waves,
.wave-container,
.wave {
pointer-events: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#background,
.wave {
opacity: 0;
}
#waves,
.wave {
overflow: hidden;
}
.wave-container,
.wave {
border-radius: 50%;
}
:host(.circle) #background,
:host(.circle) #waves {
border-radius: 50%;
}
:host(.circle) .wave-container {
overflow: hidden;
}
</style>
<template>
<div id="background"></div>
<div id="waves"></div>
</template>
</dom-module>
<script>
(function() {
var Utility = {
cssColorWithAlpha: function(cssColor, alpha) {
var parts = cssColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
if (typeof alpha == 'undefined') {
alpha = 1;
}
if (!parts) {
return 'rgba(255, 255, 255, ' + alpha + ')';
}
return 'rgba(' + parts[1] + ', ' + parts[2] + ', ' + parts[3] + ', ' + alpha + ')';
},
distance: function(x1, y1, x2, y2) {
var xDelta = (x1 - x2);
var yDelta = (y1 - y2);
return Math.sqrt(xDelta * xDelta + yDelta * yDelta);
},
now: (function() {
if (window.performance && window.performance.now) {
return window.performance.now.bind(window.performance);
}
return Date.now;
})()
};
/**
* @param {HTMLElement} element
* @constructor
*/
function ElementMetrics(element) {
this.element = element;
this.width = this.boundingRect.width;
this.height = this.boundingRect.height;
this.size = Math.max(this.width, this.height);
}
ElementMetrics.prototype = {
get boundingRect () {
return this.element.getBoundingClientRect();
},
furthestCornerDistanceFrom: function(x, y) {
var topLeft = Utility.distance(x, y, 0, 0);
var topRight = Utility.distance(x, y, this.width, 0);
var bottomLeft = Utility.distance(x, y, 0, this.height);
var bottomRight = Utility.distance(x, y, this.width, this.height);
return Math.max(topLeft, topRight, bottomLeft, bottomRight);
}
};
/**
* @param {HTMLElement} element
* @constructor
*/
function Ripple(element) {
this.element = element;
this.color = window.getComputedStyle(element).color;
this.wave = document.createElement('div');
this.waveContainer = document.createElement('div');
this.wave.style.backgroundColor = this.color;
this.wave.classList.add('wave');
this.waveContainer.classList.add('wave-container');
Polymer.dom(this.waveContainer).appendChild(this.wave);
this.resetInteractionState();
}
Ripple.MAX_RADIUS = 300;
Ripple.prototype = {
get recenters() {
return this.element.recenters;
},
get center() {
return this.element.center;
},
get mouseDownElapsed() {
var elapsed;
if (!this.mouseDownStart) {
return 0;
}
elapsed = Utility.now() - this.mouseDownStart;
if (this.mouseUpStart) {
elapsed -= this.mouseUpElapsed;
}
return elapsed;
},
get mouseUpElapsed() {
return this.mouseUpStart ?
Utility.now () - this.mouseUpStart : 0;
},
get mouseDownElapsedSeconds() {
return this.mouseDownElapsed / 1000;
},
get mouseUpElapsedSeconds() {
return this.mouseUpElapsed / 1000;
},
get mouseInteractionSeconds() {
return this.mouseDownElapsedSeconds + this.mouseUpElapsedSeconds;
},
get initialOpacity() {
return this.element.initialOpacity;
},
get opacityDecayVelocity() {
return this.element.opacityDecayVelocity;
},
get radius() {
var width2 = this.containerMetrics.width * this.containerMetrics.width;
var height2 = this.containerMetrics.height * this.containerMetrics.height;
var waveRadius = Math.min(
Math.sqrt(width2 + height2),
Ripple.MAX_RADIUS
) * 1.1 + 5;
var duration = 1.1 - 0.2 * (waveRadius / Ripple.MAX_RADIUS);
var timeNow = this.mouseInteractionSeconds / duration;
var size = waveRadius * (1 - Math.pow(80, -timeNow));
return Math.abs(size);
},
get opacity() {
if (!this.mouseUpStart) {
return this.initialOpacity;
}
return Math.max(
0,
this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVelocity
);
},
get outerOpacity() {
// Linear increase in background opacity, capped at the opacity
// of the wavefront (waveOpacity).
var outerOpacity = this.mouseUpElapsedSeconds * 0.3;
var waveOpacity = this.opacity;
return Math.max(
0,
Math.min(outerOpacity, waveOpacity)
);
},
get isOpacityFullyDecayed() {
return this.opacity < 0.01 &&
this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
},
get isRestingAtMaxRadius() {
return this.opacity >= this.initialOpacity &&
this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
},
get isAnimationComplete() {
return this.mouseUpStart ?
this.isOpacityFullyDecayed : this.isRestingAtMaxRadius;
},
get translationFraction() {
return Math.min(
1,
this.radius / this.containerMetrics.size * 2 / Math.sqrt(2)
);
},
get xNow() {
if (this.xEnd) {
return this.xStart + this.translationFraction * (this.xEnd - this.xStart);
}
return this.xStart;
},
get yNow() {
if (this.yEnd) {
return this.yStart + this.translationFraction * (this.yEnd - this.yStart);
}
return this.yStart;
},
get isMouseDown() {
return this.mouseDownStart && !this.mouseUpStart;
},
resetInteractionState: function() {
this.maxRadius = 0;
this.mouseDownStart = 0;
this.mouseUpStart = 0;
this.xStart = 0;
this.yStart = 0;
this.xEnd = 0;
this.yEnd = 0;
this.slideDistance = 0;
this.containerMetrics = new ElementMetrics(this.element);
},
draw: function() {
var scale;
var translateString;
var dx;
var dy;
this.wave.style.opacity = this.opacity;
scale = this.radius / (this.containerMetrics.size / 2);
dx = this.xNow - (this.containerMetrics.width / 2);
dy = this.yNow - (this.containerMetrics.height / 2);
// 2d transform for safari because of border-radius and overflow:hidden clipping bug.
// https://bugs.webkit.org/show_bug.cgi?id=98538
this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' + dy + 'px)';
this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy + 'px, 0)';
this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')';
this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)';
},
/** @param {Event=} event */
downAction: function(event) {
var xCenter = this.containerMetrics.width / 2;
var yCenter = this.containerMetrics.height / 2;
this.resetInteractionState();
this.mouseDownStart = Utility.now();
if (this.center) {
this.xStart = xCenter;
this.yStart = yCenter;
this.slideDistance = Utility.distance(
this.xStart, this.yStart, this.xEnd, this.yEnd
);
} else {
this.xStart = event ?
event.detail.x - this.containerMetrics.boundingRect.left :
this.containerMetrics.width / 2;
this.yStart = event ?
event.detail.y - this.containerMetrics.boundingRect.top :
this.containerMetrics.height / 2;
}
if (this.recenters) {
this.xEnd = xCenter;
this.yEnd = yCenter;
this.slideDistance = Utility.distance(
this.xStart, this.yStart, this.xEnd, this.yEnd
);
}
this.maxRadius = this.containerMetrics.furthestCornerDistanceFrom(
this.xStart,
this.yStart
);
this.waveContainer.style.top =
(this.containerMetrics.height - this.containerMetrics.size) / 2 + 'px';
this.waveContainer.style.left =
(this.containerMetrics.width - this.containerMetrics.size) / 2 + 'px';
this.waveContainer.style.width = this.containerMetrics.size + 'px';
this.waveContainer.style.height = this.containerMetrics.size + 'px';
},
/** @param {Event=} event */
upAction: function(event) {
if (!this.isMouseDown) {
return;
}
this.mouseUpStart = Utility.now();
},
remove: function() {
Polymer.dom(this.waveContainer.parentNode).removeChild(
this.waveContainer
);
}
};
Polymer({
is: 'paper-ripple',
behaviors: [
Polymer.IronA11yKeysBehavior
],
properties: {
/**
* The initial opacity set on the wave.
*
* @attribute initialOpacity
* @type number
* @default 0.25
*/
initialOpacity: {
type: Number,
value: 0.25
},
/**
* How fast (opacity per second) the wave fades out.
*
* @attribute opacityDecayVelocity
* @type number
* @default 0.8
*/
opacityDecayVelocity: {
type: Number,
value: 0.8
},
/**
* If true, ripples will exhibit a gravitational pull towards
* the center of their container as they fade away.
*
* @attribute recenters
* @type boolean
* @default false
*/
recenters: {
type: Boolean,
value: false
},
/**
* If true, ripples will center inside its container
*
* @attribute recenters
* @type boolean
* @default false
*/
center: {
type: Boolean,
value: false
},
/**
* A list of the visual ripples.
*
* @attribute ripples
* @type Array
* @default []
*/
ripples: {
type: Array,
value: function() {
return [];
}
},
/**
* True when there are visible ripples animating within the
* element.
*/
animating: {
type: Boolean,
readOnly: true,
reflectToAttribute: true,
value: false
},
/**
* If true, the ripple will remain in the "down" state until `holdDown`
* is set to false again.
*/
holdDown: {
type: Boolean,
value: false,
observer: '_holdDownChanged'
},
_animating: {
type: Boolean
},
_boundAnimate: {
type: Function,
value: function() {
return this.animate.bind(this);
}
}
},
get target () {
var ownerRoot = Polymer.dom(this).getOwnerRoot();
var target;
if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE
target = ownerRoot.host;
} else {
target = this.parentNode;
}
return target;
},
keyBindings: {
'enter:keydown': '_onEnterKeydown',
'space:keydown': '_onSpaceKeydown',
'space:keyup': '_onSpaceKeyup'
},
attached: function() {
this.listen(this.target, 'up', 'upAction');
this.listen(this.target, 'down', 'downAction');
if (!this.target.hasAttribute('noink')) {
this.keyEventTarget = this.target;
}
},
2015-07-23 07:58:27 -07:00
get shouldKeepAnimating() {
var ripples = this.ripples || [];
for (var index = 0; index < ripples.length; ++index) {
if (!ripples[index].isAnimationComplete) {
2015-06-15 21:52:01 -07:00
return true;
}
}
return false;
},
simulatedRipple: function() {
this.downAction(null);
// Please see polymer/polymer#1305
this.async(function() {
this.upAction();
}, 1);
},
/** @param {Event=} event */
downAction: function(event) {
if (this.holdDown && this.ripples.length > 0) {
return;
}
var ripple = this.addRipple();
ripple.downAction(event);
if (!this._animating) {
this.animate();
}
},
/** @param {Event=} event */
upAction: function(event) {
if (this.holdDown) {
return;
}
2015-07-23 07:58:27 -07:00
if (this.ripples) {
this.ripples.forEach(function (ripple) {
ripple.upAction(event);
});
}
2015-06-15 21:52:01 -07:00
this.animate();
},
onAnimationComplete: function() {
this._animating = false;
this.$.background.style.backgroundColor = null;
this.fire('transitionend');
},
addRipple: function() {
var ripple = new Ripple(this);
Polymer.dom(this.$.waves).appendChild(ripple.waveContainer);
this.$.background.style.backgroundColor = ripple.color;
2015-07-23 07:58:27 -07:00
if (this.ripples) {
this.ripples.push(ripple);
}
2015-06-15 21:52:01 -07:00
this._setAnimating(true);
return ripple;
},
removeRipple: function(ripple) {
var rippleIndex = this.ripples.indexOf(ripple);
if (rippleIndex < 0) {
return;
}
this.ripples.splice(rippleIndex, 1);
ripple.remove();
if (!this.ripples.length) {
this._setAnimating(false);
}
},
animate: function() {
var index;
var ripple;
this._animating = true;
2015-07-23 07:58:27 -07:00
var ripples = this.ripples || [];
for (index = 0; index < ripples.length; ++index) {
ripple = ripples[index];
2015-06-15 21:52:01 -07:00
ripple.draw();
this.$.background.style.opacity = ripple.outerOpacity;
if (ripple.isOpacityFullyDecayed && !ripple.isRestingAtMaxRadius) {
this.removeRipple(ripple);
}
}
2015-07-23 07:58:27 -07:00
if (!this.shouldKeepAnimating && ripples.length === 0) {
2015-06-15 21:52:01 -07:00
this.onAnimationComplete();
} else {
window.requestAnimationFrame(this._boundAnimate);
}
},
_onEnterKeydown: function() {
this.downAction();
this.async(this.upAction, 1);
},
_onSpaceKeydown: function() {
this.downAction();
},
_onSpaceKeyup: function() {
this.upAction();
},
_holdDownChanged: function(holdDown) {
if (holdDown) {
this.downAction();
} else {
this.upAction();
}
}
});
})();
</script>