459 lines
13 KiB
HTML
459 lines
13 KiB
HTML
<!--
|
|
@license
|
|
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 href="../core-selector/core-selector.html" rel="import">
|
|
<link href="../core-resizable/core-resizable.html" rel="import">
|
|
|
|
<link href="transitions/hero-transition.html" rel="import">
|
|
<link href="transitions/cross-fade.html" rel="import">
|
|
|
|
<!--
|
|
|
|
`core-animated-pages` selects one of its children "pages" to show and runs a transition
|
|
when switching between them. The transitions are designed to be pluggable, and can
|
|
accept any object that is an instance of a `core-transition-pages`. Transitions to run
|
|
are specified in the `transitions` attribute as a space-delimited string of `id`s of
|
|
transition elements. Several transitions are available with `core-animated-pages` by
|
|
default, including `hero-transition`, `cross-fade`, and `tile-cascade`.
|
|
|
|
Example:
|
|
|
|
<style>
|
|
#hero1 {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 300px;
|
|
height: 300px;
|
|
background-color: orange;
|
|
}
|
|
#hero2 {
|
|
position: absolute;
|
|
top: 200px;
|
|
left: 300px;
|
|
width: 300px;
|
|
height: 300px;
|
|
background-color: orange;
|
|
}
|
|
#bottom1, #bottom2 {
|
|
position: absolute;
|
|
bottom: 0;
|
|
top: 0;
|
|
left: 0;
|
|
height: 50px;
|
|
}
|
|
#bottom1 {
|
|
background-color: blue;
|
|
}
|
|
#bottom2 {
|
|
background-color: green;
|
|
}
|
|
</style>
|
|
// hero-transition and cross-fade are declared elsewhere
|
|
<core-animated-pages transitions="hero-transition cross-fade">
|
|
<section id="page1">
|
|
<div id="hero1" hero-id="hero" hero></div>
|
|
<div id="bottom1" cross-fade></div>
|
|
</section>
|
|
<section id="page2">
|
|
<div id="hero2" hero-id="hero" hero></div>
|
|
<div id="bottom2" cross-fade></div>
|
|
</section>
|
|
</core-animated-pages>
|
|
|
|
In the above example, two transitions (`hero-transition` and `cross-fade`) are run when switching
|
|
between `page1` and `page2`. `hero-transition` transforms elements with the same `hero-id` such
|
|
that they appear to be shared across different pages. `cross-fade` fades out the elements marked
|
|
`cross-fade` in the outgoing page, and fades in those in the incoming page. See the individual
|
|
transition's documentation for specific details.
|
|
|
|
Finding elements to transition
|
|
------------------------------
|
|
|
|
In general, a transition is applied to elements marked with a certain attribute. For example,
|
|
`hero-transition` applies the transition on elements with the `hero` and `hero-id` attribute.
|
|
Among the transitions included with `core-animated-pages`, script-based transitions such as
|
|
`hero-transition` generally look for elements up to one level of shadowRoot from the
|
|
`core-animated-pages` element, and CSS-based transitions such as `cross-fade` look for elements
|
|
within any shadowRoot within the `core-animated-pages` element. This means you can use
|
|
custom elements as pages and mark elements in their shadowRoots as heroes, or mark
|
|
elements in deeper shadowRoots with other transitions.
|
|
|
|
Example:
|
|
|
|
<polymer-element name="x-el" noscript>
|
|
<template>
|
|
<style>
|
|
#hero {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
width: 50px;
|
|
height: 300px;
|
|
background-color: blue;
|
|
}
|
|
</style>
|
|
<div id="hero" hero-id="bar" hero></div>
|
|
</template>
|
|
</polymer-element>
|
|
|
|
<polymer-element name="x-page-1" noscript>
|
|
<template>
|
|
<style>
|
|
#hero1 {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 300px;
|
|
height: 300px;
|
|
background-color: orange;
|
|
}
|
|
</style>
|
|
<div id="hero1" hero-id="foo" hero></div>
|
|
<div id="hero2" hero-id="bar" hero></div>
|
|
</template>
|
|
</polymer-element>
|
|
|
|
<polymer-element name="x-page-2" noscript>
|
|
<template>
|
|
<style>
|
|
#hero1 {
|
|
position: absolute;
|
|
top: 200px;
|
|
left: 300px;
|
|
width: 300px;
|
|
height: 300px;
|
|
background-color: orange;
|
|
}
|
|
#hero2 {
|
|
background-color: blue;
|
|
height: 150px;
|
|
width: 400px;
|
|
}
|
|
</style>
|
|
// The below element is one level of shadow from the core-animated-pages and will
|
|
// be transitioned.
|
|
<div id="hero1" hero-id="foo" hero></div>
|
|
// The below element contains a hero inside its shadowRoot making it two levels away
|
|
// from the core-animated-pages, and will not be transitioned.
|
|
<x-el></x-el>
|
|
</template>
|
|
</polymer-element>
|
|
|
|
<core-animated-pages transitions="hero-transition">
|
|
<x-page-1></x-page-1>
|
|
<x-page-2></x-page-2>
|
|
</core-animated-pages>
|
|
|
|
Note that the container element of the page does not participate in the transition.
|
|
|
|
// This does not work
|
|
<core-animated-pages transitions="cross-fade">
|
|
<section cross-fade></section>
|
|
<section cross-fade></section>
|
|
</core-animated-pages>
|
|
|
|
// This works
|
|
<core-animated-pages transitions="cross-fade">
|
|
<section>
|
|
<div cross-fade></div>
|
|
</section>
|
|
<section>
|
|
<div cross-fade></div>
|
|
</section>
|
|
</core-animated-pages>
|
|
|
|
Dynamically setting up transitions
|
|
----------------------------------
|
|
|
|
An easy way to set up transitions dynamically is to use property binding on
|
|
the transition attributes.
|
|
|
|
Example:
|
|
|
|
<core-animated-pages selected="{{selected}}">
|
|
<section id="page1">
|
|
<div hero-id="hero" hero></div>
|
|
</section>
|
|
<section id="page2">
|
|
<div id="foo" hero-id="hero" hero?="{{selected === 1 || selected === 0}}" cross-fade="{{selected === 2}}"></div>
|
|
</section>
|
|
<section id="page3">
|
|
</section>
|
|
</core-animated-pages>
|
|
|
|
In the above example, the "foo" element only behaves as a hero element if transitioning between
|
|
`#page1` and `#page2`. It gets cross-faded when transition to or from `#page3`.
|
|
|
|
Nesting pages
|
|
-------------
|
|
|
|
It is possible to nest core-animated-pages elements for organization. Excessive nesting is
|
|
not encouraged, however, since it makes setting up the transition more complex.
|
|
|
|
To nest core-animated-pages, the page containing the nested core-animated-pages element should
|
|
have a `selectedItem` property bound to the `selectedItem` property of the nested element. This
|
|
will allow the outer core-animated-pages to know which nested page it is actually transitioning
|
|
to.
|
|
|
|
Example:
|
|
|
|
<polymer-element name="nested-page" attributes="selectedItem">
|
|
<template>
|
|
<core-animated-pages selectedItem="{{selectedItem}}">
|
|
...
|
|
</core-animated-pages>
|
|
</template>
|
|
</polymer-element>
|
|
|
|
<core-animated-pages>
|
|
<section id="page1"></section>
|
|
<nested-page id="page2"></nested-page>
|
|
</core-animated-pages>
|
|
|
|
@element core-animated-pages
|
|
@extends core-selector
|
|
@mixins Polymer.CoreResizer https://github.com/polymer/core-resizable
|
|
@status beta
|
|
@homepage github.io
|
|
-->
|
|
<!--
|
|
Fired before a page transition occurs. Both pages involved in the transition are visible when
|
|
this event fires. This is useful if there is something the client needs to do when a page becomes
|
|
visible.
|
|
|
|
@event core-animated-pages-transition-prepare
|
|
-->
|
|
<!--
|
|
Fired when a page transition completes.
|
|
|
|
@event core-animated-pages-transition-end
|
|
-->
|
|
<polymer-element name="core-animated-pages" extends="core-selector" notap attributes="transitions">
|
|
|
|
<template>
|
|
|
|
<link href="core-animated-pages.css" rel="stylesheet">
|
|
|
|
<shadow></shadow>
|
|
|
|
</template>
|
|
|
|
<script>
|
|
|
|
Polymer(Polymer.mixin({
|
|
|
|
eventDelegates: {
|
|
'core-transitionend': 'transitionEnd'
|
|
},
|
|
|
|
/**
|
|
* A space-delimited string of transitions to use when switching between pages in this element.
|
|
* The strings are `id`s of `core-transition-pages` elements included elsewhere. See the
|
|
* individual transition's document for specific details.
|
|
*
|
|
* @attribute transitions
|
|
* @type string
|
|
* @default ''
|
|
*/
|
|
transitions: '',
|
|
|
|
selected: 0,
|
|
|
|
/**
|
|
* The last page selected. This property is useful to dynamically set transitions based
|
|
* on incoming and outgoing pages.
|
|
*
|
|
* @attribute lastSelected
|
|
* @type Object
|
|
* @default null
|
|
*/
|
|
lastSelected: null,
|
|
|
|
registerCallback: function() {
|
|
this.tmeta = document.createElement('core-transition');
|
|
},
|
|
|
|
created: function() {
|
|
this._transitions = [];
|
|
this.transitioning = [];
|
|
},
|
|
|
|
attached: function() {
|
|
this.resizerAttachedHandler();
|
|
},
|
|
|
|
detached: function() {
|
|
this.resizerDetachedHandler();
|
|
},
|
|
|
|
transitionsChanged: function() {
|
|
this._transitions = this.transitions.split(' ');
|
|
},
|
|
|
|
_transitionsChanged: function(old) {
|
|
if (this._transitionElements) {
|
|
this._transitionElements.forEach(function(t) {
|
|
t.teardown(this);
|
|
}, this);
|
|
}
|
|
this._transitionElements = [];
|
|
this._transitions.forEach(function(transitionId) {
|
|
var t = this.getTransition(transitionId);
|
|
if (t) {
|
|
this._transitionElements.push(t);
|
|
t.setup(this);
|
|
}
|
|
}, this);
|
|
},
|
|
|
|
getTransition: function(transitionId) {
|
|
return this.tmeta.byId(transitionId);
|
|
},
|
|
|
|
selectionSelect: function(e, detail) {
|
|
this.updateSelectedItem();
|
|
// Wait to call applySelection when we run the transition
|
|
},
|
|
|
|
applyTransition: function(src, dst) {
|
|
if (this.animating) {
|
|
this.cancelAsync(this.animating);
|
|
this.animating = null;
|
|
}
|
|
|
|
Polymer.flush();
|
|
|
|
if (this.transitioning.indexOf(src) === -1) {
|
|
this.transitioning.push(src);
|
|
}
|
|
if (this.transitioning.indexOf(dst) === -1) {
|
|
this.transitioning.push(dst);
|
|
}
|
|
// force src, dst to display
|
|
src.setAttribute('animate', '');
|
|
dst.setAttribute('animate', '');
|
|
//
|
|
var options = {
|
|
src: src,
|
|
dst: dst,
|
|
easing: 'cubic-bezier(0.4, 0, 0.2, 1)'
|
|
};
|
|
|
|
// fire an event so clients have a chance to do something when the
|
|
// new page becomes visible but before it draws.
|
|
this.fire('core-animated-pages-transition-prepare');
|
|
|
|
//
|
|
// prepare transition
|
|
this._transitionElements.forEach(function(transition) {
|
|
transition.prepare(this, options);
|
|
}, this);
|
|
//
|
|
// force layout!
|
|
src.offsetTop;
|
|
|
|
//
|
|
// apply selection
|
|
this.applySelection(dst, true);
|
|
this.applySelection(src, false);
|
|
//
|
|
// start transition
|
|
this._transitionElements.forEach(function(transition) {
|
|
transition.go(this, options);
|
|
}, this);
|
|
|
|
if (!this._transitionElements.length) {
|
|
this.complete();
|
|
} else {
|
|
this.animating = this.async(this.complete.bind(this), null, 5000);
|
|
}
|
|
},
|
|
|
|
complete: function() {
|
|
if (this.animating) {
|
|
this.cancelAsync(this.animating);
|
|
this.animating = null;
|
|
}
|
|
|
|
this.transitioning.forEach(function(t) {
|
|
t.removeAttribute('animate');
|
|
});
|
|
this.transitioning = [];
|
|
|
|
this._transitionElements.forEach(function(transition) {
|
|
transition.ensureComplete(this);
|
|
}, this);
|
|
|
|
this.fire('core-animated-pages-transition-end');
|
|
},
|
|
|
|
transitionEnd: function(e) {
|
|
if (this.transitioning.length) {
|
|
var completed = true;
|
|
this._transitionElements.forEach(function(transition) {
|
|
if (!transition.completed) {
|
|
completed = false;
|
|
}
|
|
});
|
|
if (completed) {
|
|
this.job('transitionWatch', function() {
|
|
this.complete();
|
|
}, 100);
|
|
}
|
|
}
|
|
},
|
|
|
|
selectedChanged: function(old) {
|
|
this.lastSelected = old;
|
|
this.super(arguments);
|
|
},
|
|
|
|
selectedItemChanged: function(oldItem) {
|
|
this.super(arguments);
|
|
|
|
if (!oldItem) {
|
|
this.applySelection(this.selectedItem, true);
|
|
this.async(this.notifyResize);
|
|
return;
|
|
}
|
|
|
|
if (this.hasAttribute('no-transition') || !this._transitionElements || !this._transitionElements.length) {
|
|
this.applySelection(oldItem, false);
|
|
this.applySelection(this.selectedItem, true);
|
|
this.notifyResize();
|
|
return;
|
|
}
|
|
|
|
if (oldItem && this.selectedItem) {
|
|
// TODO(sorvell): allow bindings to update first?
|
|
var self = this;
|
|
Polymer.flush();
|
|
Polymer.endOfMicrotask(function() {
|
|
self.applyTransition(oldItem, self.selectedItem);
|
|
self.notifyResize();
|
|
});
|
|
}
|
|
},
|
|
|
|
resizerShouldNotify: function(el) {
|
|
// Only notify descendents of selected item
|
|
while (el && (el != this)) {
|
|
if (el == this.selectedItem) {
|
|
return true;
|
|
}
|
|
el = el.parentElement || (el.parentNode && el.parentNode.host);
|
|
}
|
|
}
|
|
|
|
}, Polymer.CoreResizer));
|
|
|
|
</script>
|
|
|
|
</polymer-element>
|