zurb / foundation-sites-6

[ARCHIVED] Version 6.0 of Foundation for Sites (Public Beta).
MIT License
14 stars 4 forks source link

Off-canvas: positioning opinion. #102

Closed zurbchris closed 8 years ago

zurbchris commented 9 years ago

Should menu's by default force the page to scroll to the top, as they do now, or adjust the menu's top attribute before open to keep it at the current scroll depth?

gpspake commented 9 years ago

I have a lot of opinions about the off canvas implementation, and off canvas patterns in general, and I'm a little hesitant to voice them all because they might not be fully composed and I don't have a bunch of fancy examples :) I'll also admit that I haven't kept up with changes that might have been made to the off-canvas component in the last couple of releases, but I'll try to touch on a few of them starting with this one and elaborate later as needed.

No, if I fire the off canvas from a scrolled position, I shouldn't end up in a different position on the page if I choose to close it, which is what happens currently; If I open the off canvas it jumps to the top so whenever it's closed, you're always at the top. A jump to the top on close option with the default set to no would be nice.

Backing up a little, though, the off canvas nav can't be made sticky (last I checked) so in what scenario would you fire the off canvas menu from a scrolled position, right? If you're activating off canvas, then you must be scrolled to the top already, which brings me to my second issue...

The off canvas nav should be able to be sticky. scrolling all the way up on a phone to get back to the nav is a real pain on long pages so sticky nav is ideal in a lot of cases. To extend on that, bottom navigation is catching on as giant phones become more popular because it's easier to access buttons at the bottom of the screen. Implementing bottom navigation, which is inherently sticky, with the current off canvas implementation would probably take more hacking than it's worth.

Finally, I think one major roadblock to the off canvas component's flexibility is that the height of the menu is bound to the height of the page and vise versa. on a really short page with 40 items in the off canvas menu, the off canvas menu forces the height of the page to grow; the height of the page should remain the same and the navigation should be scrollable without moving the page. On a really long page with only a few menu items, the off canvas menu should inherit the pages height.

Aside from some differences across browsers, most of these things can be probably achieved pretty cleanly and simply. I often use [starwars.com]() as a good example; although they break my first rule by jumping to the top when the nav is activated, the off canvas nav (or drawer) scrolls independently of the page and can be activated at any scroll position. If you resize your window so that it's height is larger than the navigation height, the navigation assumes the height of the window but if you resize the window so that it's shorter than the nav height, the nav scrolls - but only to the height of the menu and not the page as it would in foundation.

I just wanted to put this out there, I could probably come up with some diagrams and stuff and point to some other good examples. There's probably some low hanging fruit here, though, that could make the component a lot better and I'd like to hear what others think about this.

gakimball commented 9 years ago

@gpspake I'm working on off-canvas right now, trying to finalize the feature set. To summarize your feedback:

For the last one, you could do a manual or automatic method. The manual method is to add some .scrollable class that you apply to a menu. That's faster to implement and more performant, but doesn't work if your pages are always going to vary in size. In those cases you need it scrollable only when the off-canvas is taller.

In that case, we could update the plugin to read the heights of .off-canvas and .main-content, and manually set the menu to be scrollable. I imagine this setting would also be user-configurable.

Does that make sense?

gakimball commented 9 years ago

@gpspake It's worth nothing that position: fixed doesn't work if the element is inside a transforming container. The off-canvas plugin pushes the entire body over to make the effect work, which means it's impossible to fix the position of the menu. I'm not sure if it's a browser bug or just something CSS can't solve for, but it is a thing.

gpspake commented 9 years ago

Hey @gakimball, Thanks. Those requirements look good. You bring up an interesting point in the second comment. Sticky navigation is important enough, I think, that if there's a way it can be made to work it would be worth the effort - even if it takes some rethinking of the overall implementation. We actually disabled foundations off canvas component and threw our own together that seems to work pretty well.

Here are some shots of the behavior (with animations to show some of what's going on). The bar/button stays sticky at the bottom of the page.

When the off canvas is fired, the bar slides out of the way with the body as the drawer slides in, just like it does with the current implementation - but sticky, the off canvas menu also scrolls to the top - this is animated to show what's happening but could be done invisibly by turning off the transition (it's necessary to scroll the drawer to the top because, with this implementation, the off canvas scrolls with the page but it should always be at the top when you open it. Once the menu is open, it's scrollable (independent of the page).

When the off canvas is closed, the page returns to it's original position (again, animated for emphasis but could be invisible). the page has to jump to the top when the off canvas is opened (trying to stay put is really tricky) but as long as it returns to the original position when it's closed, that should be fine.

Another note: if this is used in a desktop browser, the off canvas scroll uses the main browser scrollbar instead of getting it's own which is a convenient aesthetic feature.

Here's what ours looks like:

Single off canvas menu

GitHub Logo

Left and Right off canvas menus

GitHub Logo

A lot of this is hardcoded could be optionized like, whether to animate the scrolly stuff, or whether to put the bar at the top or the bottom if it's sticky.

I'm not sure if this could be packaged up in its entirety in a component but hopefully there might be one or two helpful ideas here.

html

<body>

<!--Left Off Canvas Menu-->
<nav class="offcanvas-nav menu push-menu-left">
    <button class="close-menu expand">Close Menu</button>
</nav>

<!--Right Off Canvas Menu-->
<nav class="offcanvas-nav menu push-menu-right">
    <button class="close-menu expand">Close Menu</button>
</nav>

<!--Off Canvas Wrapper-->
<div id="wrapper">
    <div id="mobile-navigation" class="hide-for-large-up">
        <button class="nav-toggler toggle-push-left left"></button>
        <button class="nav-toggler toggle-push-right right"></button>

        //content

    </div>
</div>

<script type="text/javascript" src="_resources/2015/js/uthsc.foundation.min.js"></script>
<script type="text/javascript">
    $(document).foundation();
</script>
</body>

js

/**
 * The nav stuff
 */
(function ($) {

    'use strict';

    var body = $('body'),
        mask = document.createElement("div"),
        wrapper = $("#wrapper"),
        togglePushLeft = $(".toggle-push-left"),
        togglePushRight = $(".toggle-push-right"),
        windowLocation,
        loc,
        activeNav;

    mask.className = "mask";

    function getWindowLocation() {
        loc = $(window).scrollTop();
        return loc;
    }

    function scrollToLocation(location) {

        //scroll without animation
        //$('html, body').scrollTop(location);

        $('html,body').animate({
            scrollTop: location
        },300)
    }

    function toggleMenu(menuClass) {
        //set window location
        windowLocation = getWindowLocation();

        //add menu class to body
        body.addClass(menuClass);

        //scroll to top of nav
        //scrollToLocation(0);

        //append mask
        document.body.appendChild(mask);

        //set active nav
        activeNav = menuClass;

        scrollToLocation(0);
    }

    function closeMenu() {
        //debug: log window location
        //console.log(windowLocation);

        //remove activeNav class from body
        body.removeClass(activeNav);

        //clear active nave variable
        activeNav = "";

        //remove mask
        document.body.removeChild(mask);

        //debug: log loc variable
        //console.log('hide mask loc: ' + loc);

        //scroll to loc
        scrollToLocation(loc);
    }

    togglePushLeft.click(function () {
        toggleMenu("pml-open");
    });

    togglePushRight.click(function () {
        toggleMenu("pmr-open");
    });

    /* hide active menu if mask is clicked */
    mask.addEventListener("click", function () {
        closeMenu();
    });

    /* hide active menu if close button is clicked */
    $(".close-menu").click(function () {
        closeMenu();
    });

})(jQuery);

scss

#wrapper {
  position: relative;
  z-index: 10;
  top: 0;
  left: 0;
  -webkit-transition: all 0.3s;
  -moz-transition: all 0.3s;
  -ms-transition: all 0.3s;
  -o-transition: all 0.3s;
  transition: all 0.3s;
}

section {
  margin-bottom: 30px
}

section.toggle {
  text-align: center
}

.mask {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 15;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.8);
}

.pmr-open {
  #wrapper {
    position: fixed;
    width: 100%;
  }
  .offcanvas-nav.push-menu-right {
    position: absolute;
  }
}

.pml-open {
  #wrapper {
    position: fixed;
    width: 100%;
  }
  .offcanvas-nav.push-menu-left {
    position: absolute;
  }
}

.offcanvas-nav {

  &.menu {
    position: fixed;
    z-index: 20;
    background-color: lighten($oil, 5%);
    overflow: auto;
    overflow-x: hidden;
    -webkit-transition: all 0.3s;
    -moz-transition: all 0.3s;
    -ms-transition: all 0.3s;
    -o-transition: all 0.3s;
    transition: all 0.3s;
    min-height: 100%;

    .button, {

      &.inverse {
        background-color: $gainsboro;
        color: $uthsc-green;
        border-bottom: 1px solid $base;
        text-align: left;
        font-weight: bold;
        margin-bottom: 0;

        .fa {
          padding: 0 14px 0 8px;
          width: 44px;
        }

        &:nth-child(even) {
          background: $smoke;
        }
        &:last-of-type {
          border-bottom-left-radius: 10px;
          border-bottom-right-radius: 10px;
        }
      }

      &.menu {
        margin: 0;
        padding: 0 0 0 17px;

        .fa {
          width: 37px;
        }
      }
    }

    ul {
      list-style-type: none;
      margin: 0;
      padding: 0;
    }

    a {
      font-weight: 300;
      color: #fff;
    }

    p {
      padding: 10px;
      color: $white;
      line-height: 1.25;
      font-size: .75em;
      background-color: $oil;
      margin: 0;
    }
  }

  &.push-menu-left,
  &.push-menu-right {
    width: 300px;
    max-width: 86%;

    li {
      display: block;
      text-align: left;
      border-bottom: solid 1px $jet;

      &:first-child {
        border-top: none
      }

      &:last-child {
        border-bottom: none
      }
    }

    a {
      display: block;
      padding: 10px;
      font-size: 1.15rem;
    }

    button.close-menu {
      margin: 0;
      padding: 10px 30px;
      background-color: $jet;
    }
  }

  &.push-menu-left {
    left: -300px
  }
  &.push-menu-right {
    right: -300px
  }

  @media all and (max-width: 330px) {
    nav.push-menu-left,
    nav.push-menu-right {
      top: 0;
      width: 300px;
    }
    &.push-menu-left {
      left: -300px
    }
    &.push-menu-right {
      right: -300px
    }
  }
  .bottom-nav-bar-off-canvas {
    width: 100%;
    height: 44px;
    clear: left;
    text-align: center;
    line-height: 44px;
    background-size: 89%;
    background-repeat: no-repeat;
    background-position: center;
    background-color: lighten($oil, 5%);
    opacity: 0.4;
  }
}

body {
  &.pml-open nav.push-menu-left {
    left: 0
  }
  &.pmr-open nav.push-menu-right {
    right: 0
  }
  &.pml-open #wrapper {
    left: 300px
  }
  &.pmr-open #wrapper {
    left: -300px
  }

  @media all and (max-width: 330px) {
    &.pml-open nav.push-menu-left {
      left: 0
    }
    &.pmr-open nav.push-menu-right {
      right: 0
    }
    &.pml-open #wrapper {
      left: 100%
    }
    &.pmr-open #wrapper {
      left: -100%
    }
  }
}
zurbrandon commented 8 years ago

Thanks for all the feedback, everyone! The team has went over all these discussion topics and have either incorporated the feedback, have a plan to incorporate I or decided to not include it.

We'll close these topics, but feel free to keep adding and discussing if you'd like.