2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ヘッダー固定のリストコンポーネントの実装

Last updated at Posted at 2020-10-14

始めに

以下のようなヘッダーを固定にした一覧を表示するコンポーネントを作った際に色々ハマったことがあるので備忘録として残したいと思います。

Oct-14-2020 17-15-46.gif

一番単純な実装

一番単純なのはheaderにposition: sticky;をつけることです。

stickyを使ったStickyListコンポーネント
<template lang="pug">
.sticky-list
  .sticky-list__header
    slot(name="header")
  .sticky-list__content
    slot(name="content")
</template>

<style lang="scss" scoped>
.sticky-list {
  position: relative;
  width: 100%;
  height: 100%;
  overflow-y: auto;
  
  &__header {
    position: sticky;
    top: 0;
    left: 0;
  }
}
</style>

呼び出しがわは以下のような感じでやると一番最初に表示したGIFアニメのようなものができます。

呼び出し側
<template lang="pug">
StickyList
  template(v-slot:header)
    .header
      .header__column.-item1 番号
      .header__column.-item2 項目2
      .header__column.-item3 項目3
  template(v-slot:content)
    .content
      template(v-for="index in 30")
        content__item
          .content__item__column.-item1 {{ index }}
          .content__item__column.-item2 内容2
          .content__item__column.-item3 内容3
</template>

<style lang="scss" scoped>
// 各カラム幅はmixinで設定しておく
@mixin list-width() {
  &.-item1 {
    flex: 0 0 50px;
  }

  &.-item2 {
    flex: 1 1 0;
  }

  &.-item3 {
    flex: 2 2 0;
  }
}

.header {
  display: flex;
  padding: 10px;
  background-color: #fff;
  border-bottom: solid 1px #ccc;
  
  &__column {
    @include list-width();
  }
}

.content {
  &__item {
    display: flex;
    padding: 10px;

    & + & {
      border-top: solid 1px #ccc;
    }

    &__column {
      @include list-width();
    }
  }
}
</style>

問題点

基本的にはこれで問題ないですが、親要素がflexを使っている場合、iOS Safariだと上手くいかない場合があるようです😓

サンプルコード

サンプルコードは以下になります。CodePenではVueファイルは書けないので別な書き方になっていますが基本は同じです。

See the Pen StickyList(stickyで実装) by wintyo (@wintyo) on CodePen.

flexで書いたパターンも用意していますので、iPhoneがある方はこちらから動作を確認してみると上手く動いていないことが分かると思います。
https://codepen.io/wintyo/full/xxOwBbB

コンテンツ内だけスクロールさせる

前項のやり方では上手くいかない時があるので、愚直にコンテンツ内だけスクロールするようなコンポーネントにしてみます。
この際にコンテンツ側だけスクロールバーが出てしまうため、その幅分ヘッダーにpaddingを入れて幅を合わせます。

コンテンツ内だけスクロールさせる
<template lang="pug">
.sticky-list
  .sticky-list__header(
    :style="{ paddingRight: `${$data.scrollBarWidth}px` }"
  )
    slot(name="header")
  .sticky-list__content(
    ref="elContent"
  )
    slot(name="content")
</template>

<script>
export default Vue.extend({
  data() {
    return {
      scrollBarWidth: 0,
    };
  },
  mounted() {
    const { elContent } = this.$refs;
    // スクロールバーの幅を計算する
    this.$data.scrollBarWidth = elContent.offsetWidth - elContent.clientWidth;
  }
});
</script>

<style lang="scss" scoped>
.sticky-list {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  
  &__content {
    flex: 1 1 0;
    overflow-x: hidden;
    overflow-y: scroll;
  }
}
</style>

実行すると以下のようなGIFアニメになります。

Oct-14-2020 17-04-47.gif

サンプルコード

このやり方のサンプルコードは以下になります。

See the Pen Sticky List(paddingで実装) by wintyo (@wintyo) on CodePen.

終わりに

以上がヘッダー固定のリストコンポーネントの実装でした。基本的にはposition: stickyで済ませられるならそれにしたいですが、意外と罠がありましたので、確実にやる場合は2つ目のやり方を参考にするといいと思います。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?