core-scroll-header-panel.html revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1<!--
2Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
3This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
4The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
5The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
6Code distributed by Google as part of the polymer project is also
7subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
8-->
9
10<!--
11`core-scroll-header-panel` contains a header section and a content section.  The
12header is initially on the top part of the view but it scrolls away with the 
13rest of the scrollable content.  Upon scrolling slightly up at any point, the 
14header scrolls back into view.  This saves screen space and allows users to
15access important controls by easily moving them back to the view.
16
17__Important:__ The `core-scroll-header-panel` will not display if its parent does not have a height.
18
19Using [layout attributes](http://www.polymer-project.org/docs/polymer/layout-attrs.html), you can easily make the `core-scroll-header-panel` fill the screen
20
21    <body fullbleed layout vertical>
22      <core-scroll-header-panel flex>
23        <core-toolbar>
24          <div>Hello World!</div>
25        </core-toolbar>
26      </core-scroll-header-panel>
27    </body>
28
29or, if you would prefer to do it in CSS, just give `html`, `body`, and `core-scroll-header-panel` a height of 100%:
30
31    html, body {
32      height: 100%;
33      margin: 0;
34    }
35    core-scroll-header-panel {
36      height: 100%;
37    }
38
39`core-scroll-header-panel` works well with `core-toolbar` but can use any element 
40that represents a header by adding a `core-header` class to it.  Use the attribute 
41or class `content` to delineate the content section.
42
43    <core-scroll-header-panel>
44      <core-toolbar>Header</core-toolbar>
45      <div content>Content goes here...</div>
46    </core-scroll-header-panel>
47
48@group Polymer Core Elements
49@element core-scroll-header-panel
50@homepage github.io
51-->
52
53<link rel="import" href="/polymer/polymer.html">
54
55<polymer-element name="core-scroll-header-panel">
56<template>
57
58  <link rel="stylesheet" href="core-scroll-header-panel.css">
59
60  <div id="mainContainer" on-scroll="{{scroll}}">
61  
62    <content id="mainContent" select="[content], .content"></content>
63    
64  </div>
65  
66  <div id="headerContainer">
67  
68    <div class="bg-container">
69      <div id="condensedHeaderBg"></div>
70      <div id="headerBg"></div>
71    </div>
72    
73    <content id="headerContent" select="core-toolbar, .core-header"></content>
74    
75  </div>
76  
77</template>
78<script>
79
80  Polymer('core-scroll-header-panel', {
81    
82    /**
83     * Fired when the content has been scrolled.
84     *
85     * @event scroll
86     */
87     
88    /**
89     * Fired when the header is transformed.
90     *
91     * @event core-header-transform
92     */
93     
94    publish: {
95      /**
96       * If true, the header's height will condense to `_condensedHeaderHeight`
97       * as the user scrolls down from the top of the content area.
98       *
99       * @attribute condenses
100       * @type boolean
101       * @default false
102       */
103      condenses: false,
104
105      /**
106       * If true, no cross-fade transition from one background to another.
107       *
108       * @attribute noDissolve
109       * @type boolean
110       * @default false
111       */
112      noDissolve: false,
113
114      /**
115       * If true, the header doesn't slide back in when scrolling back up.
116       *
117       * @attribute noReveal
118       * @type boolean
119       * @default false
120       */
121      noReveal: false,
122
123      /**
124       * If true, the header is fixed to the top and never moves away.
125       *
126       * @attribute fixed
127       * @type boolean
128       * @default false
129       */
130      fixed: false,
131      
132      /**
133       * If true, the condensed header is always shown and does not move away.
134       *
135       * @attribute keepCondensedHeader
136       * @type boolean
137       * @default false
138       */
139      keepCondensedHeader: false,
140
141      /**
142       * The height of the header when it is at its full size.
143       *
144       * By default, the height will be measured when it is ready.  If the height
145       * changes later the user needs to either set this value to reflect the
146       * new height or invoke `measureHeaderHeight()`.
147       *
148       * @attribute headerHeight
149       * @type number
150       */
151      headerHeight: 0,
152
153      /**
154       * The height of the header when it is condensed.
155       *
156       * By default, `_condensedHeaderHeight` is 1/3 of `headerHeight` unless
157       * this is specified.
158       *
159       * @attribute condensedHeaderHeight
160       * @type number
161       */
162      condensedHeaderHeight: 0
163    },
164
165    prevScrollTop: 0,
166    
167    headerMargin: 0,
168    
169    y: 0,
170    
171    observe: {
172      'headerMargin fixed': 'setup'
173    },
174    
175    domReady: function() {
176      this.async('measureHeaderHeight');
177    },
178
179    get header() {
180      return this.$.headerContent.getDistributedNodes()[0];
181    },
182    
183    get scroller() {
184      return this.$.mainContainer;
185    },
186    
187    measureHeaderHeight: function() {
188      var header = this.header;
189      if (this.header) {
190        this.headerHeight = header.offsetHeight;
191      }
192    },
193    
194    headerHeightChanged: function() {
195      if (!this.condensedHeaderHeight) {
196        // assume _condensedHeaderHeight is 1/3 of the headerHeight
197        this._condensedHeaderHeight = this.headerHeight * 1 / 3;
198      }
199      this.condensedHeaderHeightChanged();
200    },
201    
202    condensedHeaderHeightChanged: function() {
203      if (this.condensedHeaderHeight) {
204        this._condensedHeaderHeight = this.condensedHeaderHeight;
205      }
206      if (this.headerHeight) {
207        this.headerMargin = this.headerHeight - this._condensedHeaderHeight;
208      }
209    },
210    
211    condensesChanged: function() {
212      if (this.condenses) {
213        this.scroll();
214      } else {
215        // reset transform/opacity set on the header
216        this.condenseHeader(null);
217      }
218    },
219    
220    setup: function() {
221      var s = this.scroller.style;
222      s.paddingTop = this.fixed ? '' : this.headerHeight + 'px';
223      s.top = this.fixed ? this.headerHeight + 'px' : '';
224      if (this.fixed) {
225        this.transformHeader(null);
226      } else {
227        this.scroll();
228      }
229    },
230    
231    transformHeader: function(y) {
232      var s = this.$.headerContainer.style;
233      this.translateY(s, -y);
234      
235      if (this.condenses) {
236        this.condenseHeader(y);
237      }
238      
239      this.fire('core-header-transform', {y: y, height: this.headerHeight, 
240          condensedHeight: this._condensedHeaderHeight});
241    },
242    
243    condenseHeader: function(y) {
244      var reset = y == null;
245      // adjust top bar in core-header so the top bar stays at the top
246      if (this.header.$ && this.header.$.topBar) {
247        this.translateY(this.header.$.topBar.style, 
248            reset ? null : Math.min(y, this.headerMargin));
249      }
250      // transition header bg
251      var hbg = this.$.headerBg.style;
252      if (!this.noDissolve) {
253        hbg.opacity = reset ? '' : (this.headerMargin - y) / this.headerMargin;
254      }
255      // adjust header bg so it stays at the center
256      this.translateY(hbg, reset ? null : y / 2);
257      // transition condensed header bg
258      var chbg = this.$.condensedHeaderBg.style;
259      if (!this.noDissolve) {
260        chbg = this.$.condensedHeaderBg.style;
261        chbg.opacity = reset ? '' : y / this.headerMargin;
262        // adjust condensed header bg so it stays at the center
263        this.translateY(chbg, reset ? null : y / 2);
264      }
265    },
266    
267    translateY: function(s, y) {
268      s.transform = s.webkitTransform = y == null ? '' : 
269          'translate3d(0, ' + y + 'px, 0)';
270    },
271    
272    scroll: function(event) {
273      if (!this.header) {
274        return;
275      }
276      
277      var sTop = this.scroller.scrollTop;
278      
279      var y = Math.min(this.keepCondensedHeader ? 
280          this.headerMargin : this.headerHeight, Math.max(0, 
281          (this.noReveal ? sTop : this.y + sTop - this.prevScrollTop)));
282      
283      if (this.condenses && this.prevScrollTop >= sTop && sTop > this.headerMargin) {
284        y = Math.max(y, this.headerMargin);
285      }
286      
287      if (!event || !this.fixed && y !== this.y) {
288        requestAnimationFrame(this.transformHeader.bind(this, y));
289      }
290      
291      this.prevScrollTop = sTop;
292      this.y = y;
293      
294      if (event) {
295        this.fire('scroll', {target: this.scroller}, this, false);
296      }
297    }
298
299  });
300
301</script>
302</polymer-element>
303