Material: mdToolbar: Implement Flexible space with drag and animations

Created on 26 Dec 2014  路  41Comments  路  Source: angular/material

The app bar is flexible and can be extended to accommodate larger typography or images. To extend the app bar, add a flexible space block.

The Flexible space shrinks until only the navigation bar is left. The title should also shrink into place to become a 20sp title in the navigation bar. When scrolling all the way back, the flexible space and the title grow into place again.
The whole app bar scrolls off. When the user reverse-scrolls, the navigation bar returns anchored to the top. When scrolling all the way back, the flexible space and the title grow into place again.

flexible space video
flexible space

- Lots of Comments won't fix enhancement

Most helpful comment

Similar to the demo posted above you could use this directive and apply it as property on the toolbar.

app.directive("scroll", function ($window) {

        return function(scope, element, attrs) {

            /* header DOM element with md-page-header attribute */
            var header = document.querySelector('[md-page-header]');
            /* Store header dimensions to initialize header styling */
            var baseDimensions = header.getBoundingClientRect();
            /* DOM element with md-header-title attribute (title in toolbar) */
            var title = angular.element(document.querySelector('[md-header-title]'));
            /* DOM element with md-header-picture attribute (picture in header) */
            var picture = angular.element(document.querySelector('[md-header-picture]'));
            /* DOM element with main-fab class (a DOM element which contains the main float action button element) */
            var fab = angular.element(document.querySelector('.main-fab'));
            /* The height of a toolbar by default in Angular Material */
            var legacyToolbarH = 64;
            /* The mid-height of a float action button by default in Angular Material */
            var legacyFabMid = 56 / 2;
            /* The zoom scale of the toolbar title when it's placed at the bottom of the header picture */
            var titleZoom = 1.5;
            /* The primary color palette used by Angular Material */
            var primaryColor = [241, 60, 13];

            function styleInit() {
                title.css('padding-left', '16px');
                title.css('position', 'relative');
                title.css('transform-origin', '24px');
            }

            function handleStyle(dim) {
                fab.css('top', (dim.height - legacyFabMid) + 'px');
                if ((dim.bottom - baseDimensions.top) > legacyToolbarH) {
                    title.css('top', ((dim.bottom - baseDimensions.top) - legacyToolbarH) + 'px');
                    element.css('height', (dim.bottom - baseDimensions.top) + 'px');
                    title.css('transform', 'scale(' + ((titleZoom - 1) * ratio(dim) + 1) + ',' + ((titleZoom - 1) * ratio(dim) + 1) + ')');

                } else {
                    title.css('top', '0px');
                    element.css('height', legacyToolbarH + 'px');
                    title.css('transform', 'scale(1,1)');
                }
                if ((dim.bottom - baseDimensions.top) < legacyToolbarH * 2 && !fab.hasClass('hide')) {
                    fab.addClass('hide');
                }
                if ((dim.bottom - baseDimensions.top) > legacyToolbarH * 2 && fab.hasClass('hide')) {
                    fab.removeClass('hide');
                }
                element.css('background-color', 'rgba(' + primaryColor[0] + ',' + primaryColor[1] + ',' + primaryColor[2] + ',' + (1 - ratio(dim)) + ')');
                picture.css('background-position', '50% ' + (ratio(dim) * 50) + '%');
            }

            function ratio(dim) {
                var r = (dim.bottom - baseDimensions.top) / dim.height;
                if (r < 0) return 0;
                if (r > 1) return 1;
                return Number(r.toString().match(/^\d+(?:\.\d{0,2})?/));
            }

            styleInit();
            handleStyle(baseDimensions);

            /* Scroll event listener */
            angular.element($window).bind("scroll", function () {
                var dimensions = header.getBoundingClientRect();
                handleStyle(dimensions);
                scope.$apply();
            });

            /* Resize event listener */
            angular.element($window).bind('resize', function () {
                baseDimensions = header.getBoundingClientRect();
                var dimensions = header.getBoundingClientRect();
                handleStyle(dimensions);
                scope.$apply();
            });

        }
});
 <header md-page-header>
        <div md-header-picture></div>
        <md-toolbar scroll>
            <div class="md-toolbar-tools">
                <h2 md-header-title flex>Title</h2>
                <md-button class="md-tools" aria-label="More">
                    BUTTON
                </md-button>
            </div>
        </md-toolbar>
    </header>

I have tried using an md-subheader below it and its not working right yet so it could use some tweaking.

All 41 comments

+1

Flexible space needs to be implemented in some other components as well. This will be an interesting one...

As mentioned in #1259 - Flexible space should support the existence of tabs

scrolling techniques - patterns - google design guidelines

Any update on when this would be implemented? the milestone is just being updated.

Any update on this? It's interesting as I couldn't get to make header just like http://www.materialup.com/

+1 surprised not to see more votes on this one.

+1

+1

+1

as @guiporto mentioned in a linked issue, this is a nice implementation:
http://codepen.io/jbltx/pen/KwQxBX

@EladBezalel - that is an excellent, very impressive demo!

scrollingheader

+1 this is a great demo. I tried it for my project. It works good, some little issues with sidenav (generate a scroll to the top) and dialog (open from md-menu in toolbar, md-dialog open anchor like if we are on the top even if we are on the bottom of the page).

What would the type of toolbar in the demo be called?

Polymer has the "paper-scroll-header"
https://elements.polymer-project.org/elements/paper-scroll-header-panel?view=demo:demo/index.html

Its not really a waterfall toolbar is it?
https://www.google.com/design/spec/components/toolbars.html#toolbars-usage

This is the closest example I could find, but it doesn't have a name.
https://material-design.storage.googleapis.com/publish/material_v_4/material_ext_publish/0B6Okdz75tqQscXNQY3dNdVlYeTQ/patterns-scrolling-techniques_flex_space_image_xhdpi_003.webm

EDIT: Also I was thinking about adapting this to the coding guidelines and polishing it up a bit and submitting a pull request, but I know the contributing guidelines say you arent accepting PR's. Should I spend some time on this or not?

Allright I did some work on it. I actually went a totally different route and just implimented it as attributes on the md-toolbar. Let me know what you guys think

http://nickbolles.github.io/material/dist/demos/toolbar/

The new demos are:
demoScrollFade
demoScrollAway
demoScrollTabs (Not working yet I dont think, just threw this in at the end)

Nice. Although I have to point out, that flexible space in material design is described as placed below the content (content hovers part of the flexible space). I, for instance, am most interested in this effect. It is shown in video at the bottom of spec page http://www.google.com/design/spec/patterns/scrolling-techniques.html#scrolling-techniques-scrolling

@TiS I have implemented a way to overlap the content with the background image. It seems to be working pretty good and fulfill the flexible space of material design for the most part. I am going to try to complete the tests in the next day so that the PR can be reviewed and merged hopefully.

+1

+1

+1!

+1

+1

Similar to the demo posted above you could use this directive and apply it as property on the toolbar.

app.directive("scroll", function ($window) {

        return function(scope, element, attrs) {

            /* header DOM element with md-page-header attribute */
            var header = document.querySelector('[md-page-header]');
            /* Store header dimensions to initialize header styling */
            var baseDimensions = header.getBoundingClientRect();
            /* DOM element with md-header-title attribute (title in toolbar) */
            var title = angular.element(document.querySelector('[md-header-title]'));
            /* DOM element with md-header-picture attribute (picture in header) */
            var picture = angular.element(document.querySelector('[md-header-picture]'));
            /* DOM element with main-fab class (a DOM element which contains the main float action button element) */
            var fab = angular.element(document.querySelector('.main-fab'));
            /* The height of a toolbar by default in Angular Material */
            var legacyToolbarH = 64;
            /* The mid-height of a float action button by default in Angular Material */
            var legacyFabMid = 56 / 2;
            /* The zoom scale of the toolbar title when it's placed at the bottom of the header picture */
            var titleZoom = 1.5;
            /* The primary color palette used by Angular Material */
            var primaryColor = [241, 60, 13];

            function styleInit() {
                title.css('padding-left', '16px');
                title.css('position', 'relative');
                title.css('transform-origin', '24px');
            }

            function handleStyle(dim) {
                fab.css('top', (dim.height - legacyFabMid) + 'px');
                if ((dim.bottom - baseDimensions.top) > legacyToolbarH) {
                    title.css('top', ((dim.bottom - baseDimensions.top) - legacyToolbarH) + 'px');
                    element.css('height', (dim.bottom - baseDimensions.top) + 'px');
                    title.css('transform', 'scale(' + ((titleZoom - 1) * ratio(dim) + 1) + ',' + ((titleZoom - 1) * ratio(dim) + 1) + ')');

                } else {
                    title.css('top', '0px');
                    element.css('height', legacyToolbarH + 'px');
                    title.css('transform', 'scale(1,1)');
                }
                if ((dim.bottom - baseDimensions.top) < legacyToolbarH * 2 && !fab.hasClass('hide')) {
                    fab.addClass('hide');
                }
                if ((dim.bottom - baseDimensions.top) > legacyToolbarH * 2 && fab.hasClass('hide')) {
                    fab.removeClass('hide');
                }
                element.css('background-color', 'rgba(' + primaryColor[0] + ',' + primaryColor[1] + ',' + primaryColor[2] + ',' + (1 - ratio(dim)) + ')');
                picture.css('background-position', '50% ' + (ratio(dim) * 50) + '%');
            }

            function ratio(dim) {
                var r = (dim.bottom - baseDimensions.top) / dim.height;
                if (r < 0) return 0;
                if (r > 1) return 1;
                return Number(r.toString().match(/^\d+(?:\.\d{0,2})?/));
            }

            styleInit();
            handleStyle(baseDimensions);

            /* Scroll event listener */
            angular.element($window).bind("scroll", function () {
                var dimensions = header.getBoundingClientRect();
                handleStyle(dimensions);
                scope.$apply();
            });

            /* Resize event listener */
            angular.element($window).bind('resize', function () {
                baseDimensions = header.getBoundingClientRect();
                var dimensions = header.getBoundingClientRect();
                handleStyle(dimensions);
                scope.$apply();
            });

        }
});
 <header md-page-header>
        <div md-header-picture></div>
        <md-toolbar scroll>
            <div class="md-toolbar-tools">
                <h2 md-header-title flex>Title</h2>
                <md-button class="md-tools" aria-label="More">
                    BUTTON
                </md-button>
            </div>
        </md-toolbar>
    </header>

I have tried using an md-subheader below it and its not working right yet so it could use some tweaking.

+1 for flexible space animations

:+1:

:+1:

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

This issue is closed as part of our 鈥楽urge Focus on Material 2' efforts.
For details, see our forum posting.

This issue is closed as part of our 鈥楽urge Focus on Material 2' efforts.
For details, see our forum posting @ http://bit.ly/1UhZyWs.

This is infuriating, this issue has been +1'd many times and kept being pushed back for more than a year and now you guys while doing fantastic work on angular-material in general are just "dropping the ball" :-1:

Will flexible space at least be a priority in Material2 ?

This issue is closed as part of our 鈥楽urge Focus on Material 2' efforts.
For details, see our forum posting @ http://bit.ly/1UhZyWs.

+1

Was this page helpful?
0 / 5 - 0 ratings