Search code examples
androidionic-frameworkionic-native

How to get native app feeling in Navigation drawer (Menu) in ionic application


Is there any way to get the native look and feeling in Navigation drawer(Menu) in ionic v1 application rather than pushing the center content?

enter image description here

I want to get the Android native look and feel in ionic application like above picture.


Solution

  • Though this is an old question and Ionic v2+ is currently available with this native Navigation drawer features (<ion-menu>). I just thought to share some code that shows how to implement this feature in Ionic v1 app. I think it will help to those who are still using Ionic v1 in their mobile app. Also Ionic v2+ implementation could be found at the end of this post.


    Ionic v1

    I just created a sample Ionic v1 app using their ready-made sidemenu app template.

    ionic start myApp sidemenu --type ionic1
    

    Then I created the following nativeSideMenu.js file inside js folder.

    (function () {
    
        'use strict';
    
        angular.module('ionic.contrib.NativeDrawer', ['ionic'])
    
            .controller('drawerCtrl', ['$rootScope', '$scope', '$element', '$attrs', '$ionicGesture', '$ionicBody', '$document', function ($rootScope, $scope, $element, $attr, $ionicGesture, $ionicBody, $document) {
                var el = document.querySelectorAll("drawer")[0];
                var mainContent = angular.element(document.querySelectorAll("ion-pane")[0]);
                var dragging = false;
                var startX, lastX, offsetX, newX, startY, lastY, offsetY, newY;
                var side;
                var isOpen = false;
                var primaryScrollAxis = null;
    
                mainContent.addClass('drawer-content');
    
                // How far to drag before triggering
                var thresholdX = 10;
                // How far from edge before triggering
                var edgeX = 40;
                // Width of drawer set in css
                var drawerWidth = 270;
    
                var LEFT = 0;
                var RIGHT = 1;
    
                var isTargetDrag = false;
    
                var isContentDrag = false;
    
                var width = $element[0].clientWidth;
                var height = $element[0].clientHeight;
    
                var enableAnimation = function () {
                    angular.element(el).addClass('animate');
                };
                var disableAnimation = function () {
                    angular.element(el).removeClass('animate');
                };
    
                // Check if this is on target or not
                var isTarget = function (el) {
                    while (el) {
                        if (el === $element[0]) {
                            return true;
                        }
                        el = el.parentNode;
                    }
                };
    
                var startDrag = function (e) {
                    disableAnimation();
    
                    dragging = true;
                    offsetX = lastX - startX;
                    offsetY = lastY - startY;
                    $ionicBody.addClass('drawer-open');
                };
    
                var startTargetDrag = function (e) {
                    disableAnimation();
    
                    dragging = true;
                    isTargetDrag = true;
                    offsetX = lastX - startX;
                    offsetY = lastY - startY;
                };
    
                var doEndDrag = function (e) {
                    startX = null;
                    lastX = null;
                    offsetX = null;
                    startY = null;
                    lastY = null;
                    offsetY = null;
                    isTargetDrag = false;
    
                    if (!dragging) {
                        return;
                    }
    
                    dragging = false;
    
                    enableAnimation();
                    if (isOpen && newX < (-width / 3)) {
                        el.style.transform = el.style.webkitTransform = 'translate3d(' + (-width - 5) + 'px, 0, 0)';
                        $ionicBody.removeClass('drawer-open');
                        isOpen = false;
                    } else if (newX < (-width / 1.5)) {
                        el.style.transform = el.style.webkitTransform = 'translate3d(' + (-width - 5) + 'px, 0, 0)';
                        $ionicBody.removeClass('drawer-open');
                        isOpen = false;
                    } else {
                        el.style.transform = el.style.webkitTransform = 'translate3d(0px, 0, 0)';
                        $ionicBody.addClass('drawer-open');
                        isOpen = true;
                    }
                };
    
                var doEndContentDrag = function (e) {
                    if (startX > lastX) {
                        startX = null;
                        lastX = null;
                        offsetX = null;
                        startY = null;
                        lastY = null;
                        offsetY = null;
                        isTargetDrag = false;
                        isContentDrag = false;
    
                        if (!dragging) {
                            return;
                        }
    
                        dragging = false;
    
                        enableAnimation();
                        el.style.transform = el.style.webkitTransform = 'translate3d(-101%, 0, 0)';
                        $ionicBody.removeClass('drawer-open');
                        isOpen = false;
                    }
                    else {
                        el.style.transform = el.style.webkitTransform = 'translate3d(0 0, 0)';
                    }
    
                };
    
                var doDrag = function (e) {
                    if (e.defaultPrevented) {
                        return;
                    }
    
                    if (!lastX) {
                        startX = e.gesture.touches[0].pageX;
                    }
                    if (!lastY) {
                        startY = e.gesture.touches[0].pageY;
                    }
    
                    lastX = e.gesture.touches[0].pageX;
                    lastY = e.gesture.touches[0].pageY;
    
                    if (!dragging) {
    
                        // Dragged 15 pixels and finger is by edge
                        if (Math.abs(lastX - startX) > thresholdX) {
                            if (isTarget(e.target)) {
                                startTargetDrag(e);
                            } else if (startX < edgeX) {
                                startDrag(e);
                            }
                        }
                        // Closing from outside of drawer
                        else if (isOpen && startX > width) {
                            disableAnimation();
                            dragging = true;
                            isContentDrag = true;
                        }
                    } else {
                        newX = Math.min(0, (-width + (lastX - offsetX)));
                        newY = Math.min(0, (-height + (lastY - offsetY)));
                        var absX = Math.abs(lastX - startX);
                        var absY = Math.abs(lastY - startY);
                        if (isContentDrag && lastX < startX) {
                            var drawerOffsetX = lastX - drawerWidth;
                            el.style.transform = el.style.webkitTransform = 'translate3d(' + -absX + 'px, 0, 0)';
                        }
                        else if (isTargetDrag && absX > absY + 5) {
                            el.style.transform = el.style.webkitTransform = 'translate3d(' + newX + 'px, 0, 0)';
                        } else {
                            el.style.transform = el.style.webkitTransform = 'translate3d(' + newX + 'px, 0, 0)';
                        }
                    }
    
                    if (dragging) {
                        e.gesture.srcEvent.preventDefault();
                    }
                };
    
                side = $attr.side == 'left' ? LEFT : RIGHT;
    
                var dragFunction = function (e) {
                    if (el.attributes.candrag) {
                        doDrag(e);
                    }
                };
    
                var dragEndFunction = function (e) {
                    if (el.attributes.candrag) {
                        doEndDrag(e);
                    }
                };
    
                var onContentDrag = function (e) {
                    if (isOpen) {
                        doDrag(e);
                    }
                };
    
                var onContentTap = function (e) {
                    if (isOpen) {
                        closeDrawer();
                        e.gesture.srcEvent.preventDefault();
                    }
                };
    
                var contentDragEndFunction = function (e) {
                    if (isOpen) {
                        doEndContentDrag(e);
                        e.gesture.srcEvent.preventDefault();
                    }
                };
    
                var openDrawer = function () {
                    enableAnimation();
                    el.style.transform = el.style.webkitTransform = 'translate3d(0%, 0, 0)';
                    $ionicBody.addClass('drawer-open');
                    isOpen = true;
                };
    
                var closeDrawer = function () {
                    enableAnimation();
                    el.style.transform = el.style.webkitTransform = 'translate3d(-101%, 0, 0)';
                    $ionicBody.removeClass('drawer-open');
                    isOpen = false;
                };
    
                var toggleDrawer = function () {
                    if (isOpen) {
                        closeDrawer();
                    } else {
                        openDrawer();
                    }
                };
    
                this.close = function () {
                    closeDrawer();
                };
    
                $rootScope.toggleDrawerRoot = function () {
                    toggleDrawer();
                };
    
                this.open = function () {
                    openDrawer();
                };
    
                this.toggle = function () {
                    toggleDrawer();
                };
    
                $ionicGesture.on('drag', function (e) {
                    doDrag(e);
                }, $document);
                $ionicGesture.on('dragend', function (e) {
                    doEndDrag(e);
                }, $document);
    
                var dragGesture = $ionicGesture.on('drag', dragFunction, $document);
                var dragEndGesture = $ionicGesture.on('dragend', dragEndFunction, $document);
                var contentTapGesture = $ionicGesture.on('tap', onContentTap, mainContent);
                var contentDragGesture = $ionicGesture.on('drag', onContentDrag, mainContent);
                var contentDragEndGesture = $ionicGesture.on('dragend', contentDragEndFunction, mainContent);
    
                $scope.$on('$destroy', function () {
    
                    $ionicGesture.off(dragGesture, 'drag', dragFunction);
                    $ionicGesture.off(dragEndGesture, 'dragend', dragEndFunction);
                    $ionicGesture.off(contentTapGesture, 'tap', onContentTap);
                    $ionicGesture.off(contentDragGesture, 'drag', onContentDrag);
                    $ionicGesture.off(contentDragEndGesture, 'dragend', contentDragEndFunction);
                });
    
            }])
    
            .directive('drawer', ['$rootScope', '$ionicGesture', function ($rootScope, $ionicGesture) {
                return {
                    restrict: 'E',
                    controller: 'drawerCtrl',
                    link: function ($scope, $element, $attr, ctrl) {
                        $element.addClass($attr.side);
                        $scope.openDrawer = function () {
                            ctrl.open();
                        };
                        $scope.closeDrawer = function () {
                            ctrl.close();
                        };
                        $scope.toggleDrawer = function () {
                            ctrl.toggle();
                        };
                    }
                }
            }])
    
            .directive('menuAndDrawerToggle', ['$rootScope', '$ionicGesture', function ($rootScope, $ionicGesture) {
                return {
                    controller: '',
                    link: function ($scope, $element, $attr) {
                        $element.bind('click', function () {
                            $rootScope.toggleDrawerRoot();
                        });
                    }
                };
            }])
    
            .directive('menuAndDrawerClose', ['$ionicViewService', function ($ionicViewService) {
                return {
                    restrict: 'AC',
                    require: '^drawer',
                    link: function ($scope, $element, $attr, ctrl) {
                        $element.bind('click', function () {
                            ctrl.close();
                        });
                    }
                };
            }]);
    
    })();
    

    And also created the following nativeSideMenu.css file inside css folder

    drawer {
        display: block;
        position: fixed;
        width: 270px;
        height: 100%;
        z-index: 100;
        background-color:white;
    }
    drawer .header-menu{
        margin-top:0px;
        padding-top:0px;
    }
    drawer .header-menu ion-list .list{
        padding-top:0px;
    }
    drawer.animate {
        -webkit-transition: all ease-in-out 400ms;
        transition: all ease-in-out 400ms;
    }
    drawer.left {
        -webkit-transform: translate3d(-101%, 0, 0);
        transform: translate3d(-101%, 0, 0);
        box-shadow: 1px 5px 10px rgba(0,0,0,0.3);
    }
    drawer.right {
        right: 0;
        top: 0;
        -webkit-transform: translate3d(100%, 0, 0);
        transform: translate3d(100%, 0, 0);
    }
    .drawer-open .drawer-content .pane, .drawer-open .drawer-content .scroll-content {
        pointer-events: none;
        opacity:0.2;
    }
    drawer .item-icon-left .icon{
        font-size: 24px;
        padding-bottom: 2px;
    }
    ion-side-menu .current a.item-content,ion-side-menu .clickable.current, drawer .current a.item-content, drawer .clickable.current {
        color:#343434 !important;
    }
    drawer a.item-content, drawer .clickable{
        color: #858585;
        font-size:20px;
    }
    drawer .item-complex .item-content{
        padding-top:16px;
        padding-bottom:16px;
        font-size:20px;
    }
    /*This section would normally have .platform-android at the front */
    .platform-android4_0 drawer.left{
        box-shadow: none;
        border-left:0px;
        border-right:0px;
    }
    .header-menu{
        top:0px;
        bottom:44px;
    }
    .menu .scroll-content {
        top: 42px;
    }
    .menu-content{
        box-shadow: none;
    }
    .drawer-open ion-pane.pane{
        background-color: rgb(0,0,0);
    }
    .drawer-open view{
        background-color: rgb(0,0,0);
    }
    .ion-view.pane{
        transition: opacity .25s ease-in-out;
        -moz-transition: opacity .25s ease-in-out;
        -webkit-transition: opacity .25s ease-in-out;
    }
    .menu-open ion-view.pane, .drawer-open ion-view.pane{
       /* opacity:0.4;
        */
    }
    .bar .title.no-nav{
        text-align: left;
        left: 0px !important;
    }
    .bar .button-icon.ion-navicon{
        margin-left: -20px;
        padding-right: 20px;
    }
    .bar .title{
        text-align: left;
        left: 16px !important;
    }
    .modal-wrapper .bar .title{
        text-align: center;
        left: 0px !important;
    }
    .bar .title.title-center{
        text-align: center;
        left: 0px !important;
        right: 0px !important;
    }
    .ion-ios7-arrow-back:before{
        content:"\f2ca";
    }
    .bar .button.button-icon.ion-ios7-arrow-back:before{
        font-size: 17px;
        line-height: 32px;
        padding-right: 100px;
    }
    

    After that I added reference links to index.html file

    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
      <title></title>
    
      <link rel="manifest" href="manifest.json">
    
      <!-- un-comment this code to enable service worker
        <script>
          if ('serviceWorker' in navigator) {
            navigator.serviceWorker.register('service-worker.js')
              .then(() => console.log('service worker installed'))
              .catch(err => console.log('Error', err));
          }
        </script>-->
    
      <link href="lib/ionic/css/ionic.css" rel="stylesheet">
      <link href="css/style.css" rel="stylesheet">
      <link href="css/nativeSideMenu.css" rel="stylesheet">
      <!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above
        <link href="css/ionic.app.css" rel="stylesheet">
        -->
    
      <!-- ionic/angularjs js -->
      <script src="lib/ionic/js/ionic.bundle.js"></script>
    
      <!-- cordova script (this will be a 404 during development) -->
      <script src="cordova.js"></script>
    
      <!-- your app's js -->
      <script src="js/app.js"></script>
      <script src="js/nativeSideMenu.js"></script>
      <script src="js/controllers.js"></script>
    </head>
    
    <body ng-app="starter">
      <ion-nav-view></ion-nav-view>
    </body>
    
    </html>
    

    Then I included the ionic.contrib.NativeDrawer in angular.module in app.js file.

    angular.module('starter', ['ionic', 'starter.controllers','ionic.contrib.NativeDrawer'])
    

    After that I updated the code of menu.html file inside templates folder like below.

    <ion-side-menus>
      <ion-side-menu-content>
        <ion-nav-bar class="bar-stable bar-positive-900">
          <ion-nav-back-button>
          </ion-nav-back-button>
          <ion-nav-buttons side="left">
            <button class="button button-icon button-clear ion-navicon" menu-and-drawer-toggle>
            </button>
          </ion-nav-buttons>
        </ion-nav-bar>
        <ion-pane drawer-menu-content>
          <ion-nav-view name="menuContent"></ion-nav-view>
        </ion-pane>
      </ion-side-menu-content>
    
      <drawer side="left">
        <ion-content>
          <ion-list>
            <ion-item menu-and-drawer-close ng-click="login()">
              Login
            </ion-item>
            <ion-item menu-and-drawer-close href="#/app/search">
              Search
            </ion-item>
            <ion-item menu-and-drawer-close href="#/app/browse">
              Browse
            </ion-item>
            <ion-item menu-and-drawer-close href="#/app/playlists">
              Playlists
            </ion-item>
          </ion-list>
        </ion-content>
      </drawer>
    </ion-side-menus>
    

    Now just run the ionic serve and have the native feeling in Navigation drawer.

    Use bellow reference links for more details

    Ref. 1 Ref. 2 Ref. 3


    Ionic v2+

    We do not have that much of work to do in Ionic v2+ because by default it is giving the native feeling of the Navigation drawer. But we could change that behavior by passing the value to type on the <ion-menu>

    <ion-menu type="overlay" [content]="mycontent">...</ion-menu>
    

    According to DOC type details as bellow.

    The display type of the menu. Default varies based on the mode, see the menuType in the config. Available options: "overlay", "reveal", "push".

    Hope this will help to someone else!