| <!-- |
| @license |
| 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"> |
| <link rel="import" href="../iron-resizable-behavior/iron-resizable-behavior.html"> |
| |
| <!-- |
| `paper-scroll-header-panel` contains a header section and a content section. The |
| header is initially on the top part of the view but it scrolls away with the |
| rest of the scrollable content. Upon scrolling slightly up at any point, the |
| header scrolls back into view. This saves screen space and allows users to |
| access important controls by easily moving them back to the view. |
| |
| __Important:__ The `paper-scroll-header-panel` will not display if its parent does not have a height. |
| |
| Using [layout classes](https://www.polymer-project.org/1.0/docs/migration.html#layout-attributes) or custom properties, you can easily make the `paper-scroll-header-panel` fill the screen |
| |
| <body class="fullbleed layout vertical"> |
| <paper-scroll-header-panel class="flex"> |
| <paper-toolbar> |
| <div>Hello World!</div> |
| </paper-toolbar> |
| </paper-scroll-header-panel> |
| </body> |
| |
| or, if you would prefer to do it in CSS, just give `html`, `body`, and `paper-scroll-header-panel` a height of 100%: |
| |
| html, body { |
| height: 100%; |
| margin: 0; |
| } |
| paper-scroll-header-panel { |
| height: 100%; |
| } |
| |
| `paper-scroll-header-panel` works well with `paper-toolbar` but can use any element |
| that represents a header by adding a `paper-header` class to it. |
| |
| <paper-scroll-header-panel> |
| <paper-toolbar>Header</paper-toolbar> |
| <div>Content goes here...</div> |
| </paper-scroll-header-panel> |
| |
| Styling scroll-header-panel: |
| |
| To change background for toolbar when it is at its full size: |
| |
| paper-scroll-header-panel { |
| --paper-scroll-header-panel-full-header: { |
| background-color: red; |
| }; |
| } |
| |
| To change the background for toolbar when it is condensed: |
| |
| paper-scroll-header-panel { |
| --paper-scroll-header-panel-condensed-header: { |
| background-color: #f4b400; |
| }; |
| } |
| |
| @group Paper Element |
| @element paper-scroll-header-panel |
| @demo demo/index.html |
| @hero hero.svg |
| --> |
| |
| <dom-module id="paper-scroll-header-panel"> |
| |
| <style> |
| :host { |
| display: block; |
| position: relative; |
| overflow: hidden; |
| } |
| |
| #mainContainer { |
| position: absolute; |
| top: 0; |
| right: 0; |
| bottom: 0; |
| left: 0; |
| -moz-box-sizing: border-box; |
| box-sizing: border-box; |
| -webkit-overflow-scrolling: touch; |
| overflow-x: hidden; |
| overflow-y: auto; |
| -webkit-transform: translateZ(0); |
| transform: translateZ(0); |
| } |
| |
| #headerContainer { |
| position: absolute; |
| top: 0; |
| right: 0; |
| left: 0; |
| } |
| |
| .bg-container { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| overflow: hidden; |
| } |
| |
| #headerBg { |
| @apply(--paper-scroll-header-panel-full-header); |
| } |
| |
| #condensedHeaderBg { |
| @apply(--paper-scroll-header-panel-condensed-header); |
| } |
| |
| #headerBg, #condensedHeaderBg { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background-repeat: no-repeat; |
| background-size: cover; |
| background-position: center center; |
| } |
| |
| #condensedHeaderBg { |
| opacity: 0; |
| } |
| </style> |
| <template> |
| <div id="mainContainer"> |
| <content id="mainContent" select=":not(paper-toolbar):not(.paper-header)"></content> |
| </div> |
| <div id="headerContainer"> |
| <div class="bg-container"> |
| <div id="condensedHeaderBg"></div> |
| <div id="headerBg"></div> |
| </div> |
| <content id="headerContent" select="paper-toolbar, .paper-header"></content> |
| </div> |
| </template> |
| </dom-module> |
| |
| <script> |
| (function() { |
| |
| 'use strict'; |
| |
| Polymer({ |
| |
| /** |
| * Fired when the content has been scrolled. |
| * |
| * @event content-scroll |
| */ |
| |
| /** |
| * Fired when the header is transformed. |
| * |
| * @event paper-header-transform |
| */ |
| |
| is: 'paper-scroll-header-panel', |
| |
| behaviors: [ |
| Polymer.IronResizableBehavior |
| ], |
| |
| properties: { |
| |
| /** |
| * If true, the header's height will condense to `condensedHeaderHeight` |
| * as the user scrolls down from the top of the content area. |
| */ |
| condenses: { |
| type: Boolean, |
| value: false, |
| observer: '_condensesChanged' |
| }, |
| |
| /** |
| * If true, no cross-fade transition from one background to another. |
| */ |
| noDissolve: { |
| type: Boolean, |
| value: false |
| }, |
| |
| /** |
| * If true, the header doesn't slide back in when scrolling back up. |
| */ |
| noReveal: { |
| type: Boolean, |
| value: false |
| }, |
| |
| /** |
| * If true, the header is fixed to the top and never moves away. |
| */ |
| fixed: { |
| type: Boolean, |
| value: false |
| }, |
| |
| /** |
| * If true, the condensed header is always shown and does not move away. |
| */ |
| keepCondensedHeader: { |
| type: Boolean, |
| value: false |
| }, |
| |
| /** |
| * The height of the header when it is at its full size. |
| * |
| * By default, the height will be measured when it is ready. If the height |
| * changes later the user needs to either set this value to reflect the |
| * new height or invoke `measureHeaderHeight()`. |
| */ |
| headerHeight: { |
| type: Number, |
| value: 0 |
| }, |
| |
| /** |
| * The height of the header when it is condensed. |
| * |
| * By default, `condensedHeaderHeight` is 1/3 of `headerHeight` unless |
| * this is specified. |
| */ |
| condensedHeaderHeight: { |
| type: Number, |
| value: 0 |
| }, |
| |
| /** |
| * By default, the top part of the header stays when the header is being |
| * condensed. Set this to true if you want the top part of the header |
| * to be scrolled away. |
| */ |
| scrollAwayTopbar: { |
| type: Boolean, |
| value: false |
| }, |
| |
| _headerMargin: { |
| type: Number |
| }, |
| |
| _prevScrollTop: { |
| type: Number |
| }, |
| |
| _y: { |
| type: Number |
| } |
| |
| }, |
| |
| observers: [ |
| '_setup(_headerMargin, headerHeight, fixed)', |
| '_headerHeightChanged(headerHeight, condensedHeaderHeight)', |
| '_condensedHeaderHeightChanged(headerHeight, condensedHeaderHeight)' |
| ], |
| |
| listeners: { |
| 'iron-resize': 'measureHeaderHeight' |
| }, |
| |
| ready: function() { |
| this.async(this.measureHeaderHeight, 5); |
| this._scrollHandler = this._scroll.bind(this); |
| this.scroller.addEventListener('scroll', this._scrollHandler); |
| }, |
| |
| detached: function() { |
| this.scroller.removeEventListener('scroll', this._scrollHandler); |
| }, |
| |
| /** |
| * Returns the header element. |
| * |
| * @property header |
| * @type Object |
| */ |
| get header() { |
| return Polymer.dom(this.$.headerContent).getDistributedNodes()[0]; |
| }, |
| |
| /** |
| * Returns the content element. |
| * |
| * @property content |
| * @type Object |
| */ |
| get content() { |
| return Polymer.dom(this.$.mainContent).getDistributedNodes()[0]; |
| }, |
| |
| /** |
| * Returns the scrollable element. |
| * |
| * @property scroller |
| * @type Object |
| */ |
| get scroller() { |
| return this.$.mainContainer; |
| }, |
| |
| /** |
| * Invoke this to tell `paper-scroll-header-panel` to re-measure the header's |
| * height. |
| * |
| * @method measureHeaderHeight |
| */ |
| measureHeaderHeight: function() { |
| var header = this.header; |
| if (header && header.offsetHeight) { |
| this.headerHeight = header.offsetHeight; |
| } |
| }, |
| |
| _headerHeightChanged: function() { |
| if (!this.condensedHeaderHeight) { |
| // assume condensedHeaderHeight is 1/3 of the headerHeight |
| this.condensedHeaderHeight = this.headerHeight * 1 / 3; |
| } |
| }, |
| |
| _condensedHeaderHeightChanged: function() { |
| if (this.headerHeight) { |
| this._headerMargin = this.headerHeight - this.condensedHeaderHeight; |
| } |
| }, |
| |
| _condensesChanged: function() { |
| if (this.condenses) { |
| this._scroll(); |
| } else { |
| // reset transform/opacity set on the header |
| this._condenseHeader(null); |
| } |
| }, |
| |
| _setup: function() { |
| var s = this.scroller.style; |
| s.paddingTop = this.fixed ? '' : this.headerHeight + 'px'; |
| |
| s.top = this.fixed ? this.headerHeight + 'px' : ''; |
| |
| if (this.fixed) { |
| this._transformHeader(null); |
| } else { |
| this._scroll(); |
| } |
| }, |
| |
| _transformHeader: function(y) { |
| var s = this.$.headerContainer.style; |
| this._translateY(s, -y); |
| |
| if (this.condenses) { |
| this._condenseHeader(y); |
| } |
| |
| this.fire('paper-header-transform', {y: y, height: this.headerHeight, |
| condensedHeight: this.condensedHeaderHeight}); |
| }, |
| |
| _condenseHeader: function(y) { |
| var reset = (y === null); |
| |
| // adjust top bar in paper-header so the top bar stays at the top |
| if (!this.scrollAwayTopbar && this.header.$ && this.header.$.topBar) { |
| this._translateY(this.header.$.topBar.style, |
| reset ? null : Math.min(y, this._headerMargin)); |
| } |
| // transition header bg |
| var hbg = this.$.headerBg.style; |
| if (!this.noDissolve) { |
| hbg.opacity = reset ? '' : (this._headerMargin - y) / this._headerMargin; |
| } |
| // adjust header bg so it stays at the center |
| this._translateY(hbg, reset ? null : y / 2); |
| // transition condensed header bg |
| if (!this.noDissolve) { |
| var chbg = this.$.condensedHeaderBg.style; |
| chbg = this.$.condensedHeaderBg.style; |
| chbg.opacity = reset ? '' : y / this._headerMargin; |
| |
| // adjust condensed header bg so it stays at the center |
| this._translateY(chbg, reset ? null : y / 2); |
| } |
| }, |
| |
| _translateY: function(s, y) { |
| var t = (y === null) ? '' : 'translate3d(0, ' + y + 'px, 0)'; |
| setTransform(s, t); |
| }, |
| |
| /** @param {Event=} event */ |
| _scroll: function(event) { |
| if (!this.header) { |
| return; |
| } |
| |
| var sTop = this.scroller.scrollTop; |
| |
| this._y = this._y || 0; |
| this._prevScrollTop = this._prevScrollTop || 0; |
| |
| var y = Math.min(this.keepCondensedHeader ? |
| this._headerMargin : this.headerHeight, Math.max(0, |
| (this.noReveal ? sTop : this._y + sTop - this._prevScrollTop))); |
| |
| if (this.condenses && this._prevScrollTop >= sTop && sTop > this._headerMargin) { |
| y = Math.max(y, this._headerMargin); |
| } |
| |
| if (!event || !this.fixed && y !== this._y) { |
| this._transformHeader(y); |
| } |
| |
| this._prevScrollTop = Math.max(sTop, 0); |
| this._y = y; |
| |
| if (event) { |
| this.fire('content-scroll', {target: this.scroller}, {cancelable: false}); |
| } |
| } |
| |
| }); |
| |
| //determine proper transform mechanizm |
| if (document.documentElement.style.transform !== undefined) { |
| var setTransform = function(style, string) { |
| style.transform = string; |
| } |
| } else { |
| var setTransform = function(style, string) { |
| style.webkitTransform = string; |
| } |
| } |
| |
| })(); |
| |
| </script> |