#はじめに
スマホ向けのWebアプリケーションをReactで開発していて、横スクロール型のスライドメニュー(ナビゲーション)を作りたい!という時にご参考ください。 React×Railsで開発しています。
HTMLのアンカータグで#を使って特定のid属性に遷移させたい!それをReactでやりたい!みたいな時を想定。
#やりたいこと
- アプリライクな横スクロール型のメニューを作りたい(スクロールバー非表示にして)
- メニューの各要素をクリックしたらそれぞれの要素までぬるっとアニメーション付きでページ内遷移させたい
- ヘッダーの高さを考慮してページ内遷移させたい
- mapではき出した動的な要素にid属性を振って遷移先にしたい
#方針
Reactでページ内遷移するいい方法ないかなーと探していたところ、こちらの記事(Reactでページ内リンクを実装する)を発見。今回はこちらを参考にreact-router-hash-link
を使うことに。
Githubはこちら:React Router Hash Link
前提として本実装では、itemCategory
のcategory_name
がカテゴリ名で、それをメニューバーにし、同一ページ内のそれぞれのcategory_name
の部分に遷移させています。
#実装
###1. yarn add かnpmでインストール
npm install --save react-router-hash-link
###2.react-router-hash-link
とMemoryRouter
(後述するエラーが出たため)をimport
import {MemoryRouter} from 'react-router-dom';
import {HashLink} from 'react-router-hash-link';
###3.遷移元となる横スクロールのメニューを作成
smooth
をつけるだけでぬるっといい感じの遷移に
HashLinkの機能。とても便利。
href=#hoge
になるようにid設定
JSX記法でto={'#' + itemCategory.category_name}
とした。これで#hogeとなり、idに遷移できる。
#####謎のエラーをMemoryRouter
で解決
HashLink
を使ったところ、Uncaught Error: Invariant failed: You should not use <Link> outside a <Router>
のエラーが表示されたため、こちらの記事(解決方法: "Error: Invariant failed: You should not use outside a ")を参考に、MemoryRouter
でHashLink
を囲う。
#####ヘッダーの高さ調整
scroll={el => { el.scrollIntoView(true); window.scrollBy(0, -160) }}
でヘッダー部分の高さを考慮。この場合は160px分下げている。
<div className={classes.scrollMenuList}>
<div className={classes.scrollMenuListMask}>
{props.shopItemCategories.map((itemCategory) => (
<Typography component="h2" variant="h3" >
<MemoryRouter>
<HashLink
smooth to={'#' + itemCategory.category_name}
scroll={el => { el.scrollIntoView(true); window.scrollBy(0, -160) }}
className={classes.scrollMenu}>{itemCategory.category_name}
</HashLink>
</MemoryRouter>
</Typography>
))}
</div>
</div>
###4.スクロールバーを非表示に
こちらの記事(Appleに学ぶ、横スクロールナビを組む時のCSSメモ)を参考に、MaskとなるscrollMenuListMask
を用意し、親要素のscrollMenuList
をoverflow:"hidden"
としラッパーする。各要素はアンダーバーがダサいので、textDecoration:"none"
に。
scrollMenuList: {
width: '900px',
overflow: "hidden",
height: 60,
},
scrollMenuListMask: {
overflowX: "auto",
whiteSpace: "nowrap",
display: 'flex',
height: 80, //Maskのheightを親要素より高くすることがミソ
},
scrollMenu: {
margin: theme.spacing(1, 2, 1),
fontSize: 36,
color: "#8f8f8f",
textDecoration: "none"
},
###5.遷移先にid属性を振る
id属性を振るだけなので簡単。ここで振ったidを遷移元となるHashLinkで指定することでページ内遷移できる。
{props.shopItemCategories.map((itemCategory) => (
<div className={classes.root}>
<Typography component="h2" variant="h3" id={itemCategory.category_name}>
{itemCategory.category_name}
</Typography>
</div>
...省略
))}
#完成
完成したものが以下の通り。
//Import
import {MemoryRouter} from 'react-router-dom';
import {HashLink} from 'react-router-hash-link';
//該当部分のみのCSS
const useStyles = makeStyles((theme: Theme) =>
createStyles({
scrollMenuList: {
width: '900px',
overflow: "hidden",
height: 60,
},
scrollMenuListMask: {
overflowX: "auto",
whiteSpace: "nowrap",
display: 'flex',
height: 80,
},
scrollMenu: {
margin: theme.spacing(1, 2, 1),
fontSize: 36,
color: "#8f8f8f",
textDecoration: "none"
},
})
)
//スクロールスライドメニューとなる部分
<div className={classes.scrollMenuList}>
<div className={classes.scrollMenuListMask}>
{props.shopItemCategories.map((itemCategory) => (
<Typography component="h2" variant="h3" >
<MemoryRouter>
<HashLink
smooth to={'#' + itemCategory.category_name}
scroll={el => { el.scrollIntoView(true); window.scrollBy(0, -160) }}
className={classes.scrollMenu}>{itemCategory.category_name}
</HashLink>
</MemoryRouter>
</Typography>
))}
</div>
</div>
//メニューからの遷移先になる要素たち
{props.shopItemCategories.map((itemCategory) => (
<>
<Box>
<div className={classes.root}>
<Typography component="h2" variant="h3" id={itemCategory.category_name}>
{itemCategory.category_name}
</Typography>
</div>
</Box>
...省略
</>
))}
今回も色々な記事を参考にさせていただきました。横スクロールのスライダーみたいなのは結構使いたい場面多いと思うので、少しでも参考になれば嬉しいです。
#参考
横スクロールナビゲーションを実装する3つの方法
Reactでページ内リンクを実装する
【React Router】画面遷移時に#を用いて特定の要素に移動させる方法
Reactで動的に属性の値を生成する方法