BlogContacts
BlogGamesContacts

{{item}}

Scroll-up fab-button

08.03.2020

AngularJS, Material

Source code
There is such button on a AngularJS Material site, but without description only! Let's find out a algorithm of how it is done since the code of Material library is open.Inside there is used undocumented factory $mdUtil for scrolling, additional custom directive docsScrollClass for visual effects and a bit of custom CSS.The original code looks something like this:
JavaScript
'use strict';
const application = angular.module("application", ['ngMaterial']);
application.controller('applicationCtrl', ['$scope', '$mdUtil', function (scope, mdUtil) {
    let mainContentArea = document.querySelector("main");
    let scrollContentEl = mainContentArea.querySelector('md-content[md-scroll-y]');
    scope.scrollTop = function() {
        mdUtil.animateScrollTo(scrollContentEl, 0, 200);
    }
}])
.directive('docsScrollClass', function() {
    return {
        restrict: 'A',
        link: function(scope, element, attr) {

            let scrollParent = element.parent();
            let isScrolling = false;

            // Initial update of the state.
            updateState();

            // Register a scroll listener, which updates the state.
            scrollParent.on('scroll', updateState);

            function updateState() {
                let newState = scrollParent[0].scrollTop !== 0;

                if (newState !== isScrolling) {
                    element.toggleClass(attr.docsScrollClass, newState);
                }

                isScrolling = newState;
            }
        }
    };
});
CSS
.md-fab.docs-scroll-fab {
    position: fixed !important;

    transform: scale(0);
    transition: transform 0.2s;
}

.docs-scroll-fab.scrolling {
    transform: scale(1);
}
HTML
<body ng-app="application" ng-controller="applicationCtrl"
      layout="column" flex class="ng-cloak" style="overflow: hidden;">
    <main layout="column" flex>
        <!-- Top menu -->
        <md-toolbar class="md-toolbar-tools">
            <a href="https://example.com/">
                <md-button>
                    <b class="md-title">Main page</b>
                </md-button>
            </a>
        </md-toolbar>
        <!-- Page content -->
        <md-content flex layout="column" md-scroll-y>
            <div layout="column">
                <span>Lorem ipsum and so on..</span>
            </div>
            <!-- Scroll-up fub-button -->
            <md-button class="md-fab md-fab-bottom-right docs-scroll-fab"
                       docs-scroll-class="scrolling"
                       ng-click="scrollTop()"
                       aria-label="Back to Top">
                <md-tooltip md-direction="top">Back to Top</md-tooltip>
                <span>Top</span>
            </md-button>
        </md-content>
    </main>
</body>
This code works fine on the original site, but it turned out that in my case it don't work properly since the md-scroll-shrink attribute is used, i.e. header shrinks away as the user scrolls down.Solution: pull out the button outside from md-content - leave it in main. Don't change factory $mdUtil and custom CSS - update directive docsScrollClass.This code will work with both on and off md-scroll-shrink:
JavaScript
'use strict';
const application = angular.module("application", ['ngMaterial']);
application.controller('applicationCtrl', ['$scope', '$mdUtil', function (scope, mdUtil) {
    let mainContentArea = document.querySelector("main");
    let scrollContentEl =
        mainContentArea.querySelector('md-content[md-scroll-y]');
    scope.scrollTop = function() {
        mdUtil.animateScrollTo(scrollContentEl, 0, 200);
    }
}])
.directive('scrollClass', function() {
    return {
        restrict: 'A',
        link: function(scope, element, attr) {

            let scrollMainChildren = element.parent().children();
            let isScrolling = false;

            // Initial update of the state.
            updateState();

            // Register a scroll listener, which updates the state.
            scrollMainChildren.on('scroll', updateState);

            function updateState() {
                // md-content element
                let newState = scrollMainChildren[1].scrollTop !== 0;
                if (newState !== isScrolling) {
                    element.toggleClass(attr.scrollClass, newState);
                }

                isScrolling = newState;
            }
        }
    };
});
CSS
.md-fab.scroll-fab {
    position: fixed !important;

    transform: scale(0);
    transition: transform 0.2s;
}

.scroll-fab.scrolling {
    transform: scale(1);
}
HTML
<body ng-app="application" ng-controller="applicationCtrl"
      layout="column" flex class="ng-cloak" style="overflow: hidden;">
    <main layout="column" flex>
        <!-- Top menu with md-scroll-shrink -->
        <md-toolbar class="md-toolbar-tools" md-scroll-shrink>
            <a href="https://example.com/">
                <md-button>
                    <b class="md-title">Main page</b>
                </md-button>
            </a>
        </md-toolbar>
        <!-- Page content with scroll -->
        <md-content flex layout="column" md-scroll-y>
            <div layout="column">
                <span>Lorem ipsum and so on..</span>
            </div>
        </md-content>
        <!-- Scroll-up fub-button -->
        <md-button class="md-fab md-fab-bottom-right scroll-fab"
                   scroll-class="scrolling"
                   ng-click="scrollTop()"
                   aria-label="Back to Top">
            <md-tooltip md-direction="top">Back to Top</md-tooltip>
            <span>Top</span>
        </md-button>
    </main>
</body>
Privacy policy
Back to Top