🎆

【NuxtJS】パンくずコンポーネントを作ってみた

2020/11/20に公開

背景

こんにちは。フロントエンドエンジニアの游です。
仕事でパンずくを自前に実装することになりまして、ネットで手軽に実装できる方法があるのかなぁ〜とぐぐってみたら色んな記事があったものの、どれも今回実装したいパンくずの仕様(次のセクションに書いてます)に合わなかったです。
結局色んな記事を調べながらやっと実装できたので、もしみなさんも同じ様な仕様のパンくずを探してるならこの記事で多少お役に立てるかもしれません。

今回実装するパンくずの仕様

  1. propsは一個だけです。言わずともパンくずのデータです。
  2. パンくずが画面外まではみ出す場合、スクロールバーを非表示する。
  3. パンくずが画面外まではみ出す場合、ページを表示する時パンくずは現在のページまで自動スクロールする。

1は多分どの記事でも同じなんですが、2と3はあんまり言及されてないから今回はそれを中心に解説します。

実物

ソースコード

// 親コンポーネント

<template>
  <BreadCrumb :bread-crumbs="breadCrumbs" />
</template>
<script>
import BreadCrumb from '~/components/BreadCrumb';
export default {
  components: {
    BreadCrumb,
  },
  data() {
    return {
      breadCrumbs: [
        {
	  text: "サイトのトップ"
	  path: ""
        },
	{
	  text: "パンくずリンク1"
	  path: "/#1"
        },
	// 中略...
	{
	  text: "今のページ"
	  path: ""
	}
      ],
    };
  },
}
</script>
// パンくずコンポーネント
<template>
  <ul ref="breadcrumb" class="breadcrumb-wrapper">
    <li v-for="(breadCrumb, index) in breadCrumbs" :key="breadCrumb.text">
      <span v-if="index === breadCrumbs.length - 1" class="item-text">
        {{ breadCrumb.text }}
      </span>
      <a
        v-else
        class="item-link"
        :href="
          breadCrumb.path != '' ? `${breadCrumb.path}` : `/`
        "
      >
        {{ breadCrumb.text }}
      </a>
    </li>
  </ul>
</template>
<script>
export default {
  props: {
    breadCrumbs: { type: Array, default: () => [] },
  },
  mounted() {
    // コンポーネントが表示される時一番右側までスクロールさせる
    const breadCrumbList = this.$refs.breadcrumb;
    breadCrumbList.scrollLeft = breadCrumbList.scrollWidth;
  },
};
</script>
<style lang="postcss" scoped>
.breadcrumb-wrapper {
  display: flex;
  flex-wrap: nowrap;
  overflow-x: scroll;
  overflow-y: hidden;
  box-sizing: border-box;
  border-width: 1px;
  border-color: #e0e0e0; border border-border-gray;
  /* IE, Edge スクロールバー非表示 */
  -ms-overflow-style: none;
  /* Firefox スクロールバー非表示 */
  scrollbar-width: none;
}

/* Chrome, Safari スクロールバー非表示 */
.breadcrumb-wrapper::-webkit-scrollbar {
  display: none;
}

.breadcrumb-wrapper li {
  position: relative;
  display: flex;
  align-items: center;
  white-space: nowrap;
  flex-shrink: 0;
}

.breadcrumb-wrapper li:first-of-type .item-link {
  margin-left: 0;
}

.item-link {
  padding-left: 0.75rem;
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
  font-size: 12px;
  margin-left: 0.75rem;
  margin-right: 0.75rem;
}

.item-text {
  padding-left: 0.75rem;
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
  font-size: 12px;
  margin-left: 0.75rem;
  margin-right: 0.75rem;
}

.breadcrumb-wrapper li:last-child {
  background-color: #5a5a5a;
  color: #fff;
  flex-grow: 1;
}

.breadcrumb-wrapper li::before {
  top: -1px;
  right: -12px;
  border-width: 18px 0 18px 12px;
  border-color: transparent #e0e0e0 transparent #e0e0e0;
}

.breadcrumb-wrapper li::after {
  top: 0;
  right: -11px;
  border-width: 17px 0 17px 12px;
  border-color: transparent transparent transparent #fff;
}

.breadcrumb-wrapper li:last-child::before {
  left: 0;
  top: -1px;
}

.breadcrumb-wrapper li:last-child::after {
  left: -1px;
  top: 0;
}
.breadcrumb-wrapper li::before,
.breadcrumb-wrapper li::after {
  border-style: solid;
  width: 0;
  height: 0;
  content: '';
  position: absolute;
}

解説

スクロールバーを非表示する

まずは各ブラウザでスクロールバーを非表示するため、下記のCSSが必要です。
注意すべきのはChromeとSafariでは疑似クラスを使う必要があります。

  /* IE, Edge スクロールバー非表示 */
  -ms-overflow-style: none;
  /* Firefox スクロールバー非表示 */
  scrollbar-width: none;
  
  /* Chrome, Safari スクロールバー非表示 */
.breadcrumb-wrapper::-webkit-scrollbar {
  display: none;
}

パンくずが表示する時一番右側にスクロールさせる

これはググっても見つからなかったのでmountedと$refでなんとか実装できました。

// まずパンくずのラッパーにrefを指定します
<ul ref="breadcrumb" class="breadcrumb-wrapper">

// ... 中略
mounted() {
    // コンポーネントが表示される時一番右側までスクロールさせる
    const breadCrumbList = this.$refs.breadcrumb;
    breadCrumbList.scrollLeft = breadCrumbList.scrollWidth;
},

mounted内で$refsでパンくず要素を取得して、後はスクロールを制御するだけです。
JSでスクロールを制御するため参考になった素晴らしい記事

終わりに

スクロールバーを非表示もスクロールバーの制御もそうですが、それぞれぐぐってみたら参考できる記事はありますが、どう組み合わせして仕様を満たすのが重要ですね。いい勉強になりました。
最後まで読んで頂き、ありがとうございました!

Discussion