サイドメニューの固定でこんな方法もあったのか!となったので、備忘録として記載します。
環境
名称 | バージョン |
---|---|
Vue.js | 2.4.4 |
Element UI | 1.4.4 |
vue-scrollto | 2.7.7 |
要件
- 「サイドメニューを固定したい!」
- 「メニュークリックでスクロール移動したい!」
留意事項
- ヘッダーは
position: fixed
で固定 - サイドメニューは
position: fixed
したくない
実装手段
- ElementUIのNavMenu Side barを使用
- 要素までのスクロールに、vue-scrolltoを使用
あとは、サイドメニューの固定方法。
サイドメニューの固定 その1(スクロールを監視)
まず、サイドメニューは、position: absolute
にして、windowのscrollY
の値を監視して、サイドメニューのtop
にscrollY
の値を設定します。
ヘッダーが固定されているので、ヘッダーのouterHeight
も加算します。
こうして、下の方へスクロールしても固定で表示されるサイドメニューが一旦実装できました。
しかし、ここからScrollTo(指定した要素までスクロールする)しようとすると、サイドメニューがプログラムによるスクロールについていけないのか、ギュイーンと引っ張られてしまいます(スクロールし終わると、ちゃんと元の位置に固定される)。
マウスのスクロールなら問題なかったのですが・・・。
サイドメニューの固定 その2(position: sticky)
なんとか頑張ってみたものの解決できず諦めて探していたら、CSSでできそうなことがわかりました。
どうやら、position: sticky
をしておけば、top
を指定するだけで良さそう。
IEとEdgeはもう置いていきます。
ソースコード
コンポーネントの部分だけ抜粋。
<template>
<main>
<header id="main-header" class="header">
<h1>Header</h1>
</header>
<div class="main-contents" :style="{ 'padding-top': getSideMenuTop }">
<el-row>
<el-col :offset="1" :span="5" class="side-menu-wrapper" :style="{ top: getSideMenuTop }">
<el-menu mode="vertical" default-active="0" class="side-menu">
<el-menu-item-group v-for="(group, index1) in groups"
:key="group.title" :title="group.title">
<el-menu-item v-for="(item, index2) in group.items"
:key="item.label" :index="getItemIndex(groups, index1, index2)"
v-scroll-to="item.to">
{{ item.label }}
</el-menu-item>
</el-menu-item-group>
</el-menu>
</el-col>
<el-col :offset="1" :span="17" class="contents">
<div id="contents-1">
<p>CONTENTS 1</p>
</div>
<div style="height: 500px;"></div>
<div id="contents-2">
<p>CONTENTS 2</p>
</div>
<div style="height: 500px;"></div>
<div id="contents-3">
<p>CONTENTS 3</p>
</div>
<div style="height: 500px;"></div>
<div id="contents-4">
<p>CONTENTS 4</p>
</div>
<div style="height: 500px;"></div>
<div style="height: 500px;"></div>
</el-col>
</el-row>
</div>
</main>
</template>
<script>
const Vue = require('vue')
/*
Vue ScrollTo
*/
import VueScrollTo from 'vue-scrollto'
Vue.use(VueScrollTo)
export default {
data() {
return {
headerHeight: 0,
groups: [
{
title: 'GROUP 1',
items: [
{ label: 'contents-1', to: '#contents-1' },
{ label: 'contents-2', to: '#contents-2' },
{ label: 'contents-3', to: '#contents-3' },
]
},
{
title: 'GROUP 2',
items: [
{ label: 'contents-4', to: '#contents-4'}
]
}
]
}
},
mounted() {
this.headerHeight = $('#main-header').outerHeight()
// VueScrollTo初期設定
VueScrollTo.install(Vue, {
container: 'body',
duration: 500,
easing: 'linear',
offset: -this.headerHeight,
cancelable: true,
onDone: false,
onCancel: false,
x: false,
y: true
})
},
computed: {
getSideMenuTop() {
// ヘッダーの高さ
return this.headerHeight + 'px'
}
},
methods: {
getItemIndex(groups, groupIdx, itemIdx) {
let total = 0
for (var i = groupIdx - 1; i >= 0; i--) {
total += groups[i].items.length
}
total += itemIdx
return String(total)
},
}
}
</script>
<style lang="scss" scoped>
@import "resources/assets/sass/variables";
.header {
background-color: #ddddff;
color: #fff;
height: 120px;
position: fixed;
width: 100%;
z-index: 1;
}
.side-menu-wrapper {
position: sticky;
}
.side-menu {
width: 100%;
}
.contents {
padding: 16px;
}
</style>
ポイント
サイドメニューを固定
.side-menu-wrapper {
position: sticky;
}
サイドメニューを固定するCSSの指定です。
親要素の下端まで、指定したtop
の位置で止まってくれます。
<el-col :offset="1" :span="5" class="side-menu-wrapper" :style="{ top: getSideMenuTop }">
getSideMenuTop
により、上記のtop
を設定します。
'px'をつけないと動作しなかったので注意。
Vue Scroll-To
// VueScrollTo初期設定
VueScrollTo.install(Vue, {
container: 'body',
duration: 500,
easing: 'linear',
offset: -this.headerHeight,
cancelable: true,
onDone: false,
onCancel: false,
x: false,
y: true
})
公式によると、Vue.use(VueScrolto, {options})
という記述でも設定することができます。
が、offset
を指定して、ヘッダーの下で止まるようにしたかったので、敢えてmounted
内で初期設定を行います。
<el-menu-item v-for="(item, index2) in group.items"
:key="item.label" :index="getItemIndex(groups, index1, index2)"
v-scroll-to="item.to">
{{ item.label }}
</el-menu-item>
ディレクティブ:v-scroll-to
に移動させたいエレメントのセレクタを設定します。
<div id="contents-1"></div>
まで移動させたければ、#contents-1
を入れれば良いです。
scrolltoのオプションは、ディレクティブ内でも指定できるようなので、フレキシブルな使い方もできそうです。
position:sticky
を知らなかったので、4時間くらい無駄にしてしまったのは内緒です。。
以上。