Navigation

ITEM
ITEM
ITEM
ITEM
ITEM
ITEM
ITEM
ITEM
ITEM
ITEM
ITEM
ITEM
ITEM
ITEM
ITEM
ITEM
ITEM
ITEM
ITEM
ITEM

Opinions

  • Alternative to scrollbar to seek content.
  • Should only appear if there are content to be scrolled.
  • Should snap to item after scrolling.
  • Should scroll smoothly to indicate to touch screen and trackpad users they can touch scroll.
  • Should not block content.
    • Should be relative to the content height, smaller content height equals to smaller button.
    • Thus, for the benefit of mobile users, it should be hidden if view port width is low.
    • For mobile users peeking should be used to indicate that users can touch scroll to next.

Customizing

In this example, the next button is replaced with slots. The left button style is overridden with basic CSS.

0

1

2

3

4

5

6

7

8

9

10

11

12

Navigation.vueimport=design/design-navigation-customize.vue
<template>
  <vue-horizontal responsive class="horizontal" :displacement="0.7">
    <template v-slot:btn-next>
      <div class="replaced-btn">
        <div>MORE</div>
      </div>
    </template>

    <placeholder-component v-for="i in [0,1,2,3,4,5,6,7,8,9,10,11,12]" :key="i">
      {{ i }}
    </placeholder-component>
  </vue-horizontal>
</template>

<style scoped>
.horizontal >>> .v-hl-btn-prev svg {
  background: red;
  color: white;
  border-radius: 0;
}

.horizontal >>> .v-hl-btn-next {
  top: 0;
  bottom: 0;
  transform: translateX(0);
}

.replaced-btn {
  height: 100%;
  background: linear-gradient(to right, #ffffff00, white);
  padding-left: 48px;
  display: flex;
  align-items: center;
}

.replaced-btn > div {
  font-weight: 700;
  font-size: 15px;
  line-height: 1;
  color: black;
  padding: 8px 12px;
  background: white;
  border-radius: 3px;
  border: 1px solid black;
}
</style>

Adaptive to content height

Reduce the size of the navigation buttons and add linear gradient to blend and hide the content at the back. Additionally, the displacement is set to 0.7 so that it will not move beyond content that are not fully visible.

Tiny content height

0
1
2
3
4
5
6
7
8
9
10
11
12
TinyNavigation.vueimport=design/design-navigation-tiny.vue
<template>
  <vue-horizontal responsive class="horizontal" :displacement="0.7" snap="center">
    <div class="tag" v-for="i in [0,1,2,3,4,5,6,7,8,9,10,11,12]" :key="i">
      <div>
        <span>{{ i }}</span>
      </div>
    </div>
  </vue-horizontal>
</template>

<style scoped>
.horizontal >>> .v-hl-btn svg {
  border-radius: 0;
  margin: 0;
  padding: 8px;
  height: 100%;
  box-shadow: none;
  background: none;
}

.horizontal >>> .v-hl-btn-prev {
  background: linear-gradient(to left, #ffffff00 0, #fff 66%, #fff);
  padding-right: 24px;
}

.horizontal >>> .v-hl-btn-next {
  background: linear-gradient(to right, #ffffff00 0, #fff 66%, #fff);
  padding-left: 24px;
}

.horizontal >>> .v-hl-btn {
  top: 0;
  bottom: 0;
  transform: translateX(0);
}
</style>

<style scoped>
/* Content styling is done with tailwind postcss @apply for brevity. */
.tag {
  @apply border rounded py-2 px-4;
}

.tag > div {
  @apply h-4 bg-gray-300 rounded-sm flex justify-center items-center font-bold text-xs;
}
</style>

Small content height

Reduce the size of the navigation buttons for smaller content.

SmallNavigation.vueimport=design/design-navigation-small.vue
<template>
  <vue-horizontal responsive class="horizontal">
    <placeholder-component v-for="i in [0,1,2,3,4,5,6,7,8,9]" :key="i" small>
    </placeholder-component>
  </vue-horizontal>
</template>

<style scoped>
.horizontal >>> .v-hl-btn svg {
  padding: 3px;
  height: 30px;
  width: 30px;
}
</style>

Above content

For edge to edge content, you might want to add nav button on top or below your horizontal content so that the nav button doesn't block your horizontal content. This example is created with tailwind.

My header

AboveNavigation.vueimport=design/design-navigation-above.vue
<template>
  <div>
    <header class="mb-4 flex justify-between items-center">
      <h2>My header</h2>
      <nav>
        <button @click="prev" :class="{'active': hasPrev, 'inactive': !hasPrev}">
          <svg viewBox="0 0 24 24">
            <path d="m9.8 12 5 5a1 1 0 1 1-1.4 1.4l-5.7-5.7a1 1 0 0 1 0-1.4l5.7-5.7a1 1 0 0 1 1.4 1.4l-5 5z"/>
          </svg>
        </button>
        <button @click="next" :class="{'active': hasNext, 'inactive': !hasNext}">
          <svg viewBox="0 0 24 24">
            <path d="m14.3 12.1-5-5a1 1 0 0 1 1.4-1.4l5.7 5.7a1 1 0 0 1 0 1.4l-5.7 5.7a1 1 0 0 1-1.4-1.4l5-5z"/>
          </svg>
        </button>
      </nav>
    </header>

    <vue-horizontal responsive :button="false" ref="horizontal" @scroll-debounce="onScroll">
      <placeholder-component v-for="i in [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]" :key="i">
      </placeholder-component>
    </vue-horizontal>
  </div>
</template>

<script>
export default {
  data() {
    return {
      hasPrev: false,
      hasNext: true,
    }
  },
  methods: {
    prev() {
      this.$refs.horizontal.prev()
    },
    next() {
      this.$refs.horizontal.next()
    },
    onScroll({hasPrev, hasNext}) {
      this.hasPrev = hasPrev
      this.hasNext = hasNext
    }
  },
}
</script>

<style scoped>
/* Content styling is done with tailwind postcss @apply for brevity. */

header {
  @apply mb-4 flex justify-between items-center;
}

nav > button {
  @apply p-1 rounded-sm border outline-none;
}

nav > button.active {
  @apply text-gray-700 border-gray-500;
}

nav > button.inactive {
  @apply text-gray-300 border-gray-200;
}

nav > button:focus {
  @apply outline-none;
}

nav > button > svg {
  @apply h-6 w-6 fill-current;
}
</style>