始めに
以下のようなヘッダーを固定にした一覧を表示するコンポーネントを作った際に色々ハマったことがあるので備忘録として残したいと思います。
一番単純な実装
一番単純なのはheaderにposition: sticky;
をつけることです。
<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アニメになります。
サンプルコード
このやり方のサンプルコードは以下になります。
See the Pen Sticky List(paddingで実装) by wintyo (@wintyo) on CodePen.
終わりに
以上がヘッダー固定のリストコンポーネントの実装でした。基本的にはposition: sticky
で済ませられるならそれにしたいですが、意外と罠がありましたので、確実にやる場合は2つ目のやり方を参考にするといいと思います。