◆ ページャでページ切り替えで上に戻るときロードないと違和感あり
◆ スクロールアニメーションで上に戻るのはむしろ邪魔
◆ かと言って一瞬で移動もただページ内リンクでジャンプしただけな感じがする
◆ ロードした風に一瞬白くなるのがなんだかんだ一番それっぽく感じる

SPA ってページ自体をロードしないので画面の切り替わりが早くて好きなのですが ページャに関してのみ多くのサイトで違和感を感じます

まず 「次へ」を押してもスクロールバーがそのままのやつ 自分で上に戻らないといけないので もうちょっと頑張ってよと思います (実際大きめなところのウェブサイトでもたまにありますし)

自動で上に戻る場合でもスクロールして戻るとなんか違うって感じがします
かと言って アニメーションなしで一瞬で戻る場合も 「あれ どうなったの?」 って現状把握に一瞬戸惑います
変にページャ部分の一番上に戻るよりは ページ全体の一番上に行くのがなんだかんだページの切り替わりみたいな感じで一番わかりやすいと自分の中ではほぼ結論づいたのですが SPA だと画面的にロードはなく一瞬過ぎてやっぱり切り替わった感が足りず違和感を拭いきれません

ほぼ個人の好みレベルですが ページをロードした風な演出入れたらいいんじゃない?と思って今回はそれを試してみました

普通に上に戻る例

まずは ページャの切り替えで普通に一番上に戻る単純な例です
面倒なのでデザインもすごくシンプルです

<!doctype html>

<h1>js pager</h1>
<div class="container"></div>

<style>
h1 {
margin: 30px;
}

.container {
width: 80%;
margin: 30px auto;
}

.item {
border-bottom: 1px solid #ddd;
display: flex;
align-items: center;
padding: 5px 20px;
}

.item>div:first-child {
width: 140px;
color: midnightblue;
font-size: 20px;
}

.pager {
text-align: right;
}
</style>
<script>
const data = Array.from(Array(500), (e, i) => {
const randstr = () => Math.random().toString(36).substr(2)
return {
index: i,
value: Array.from(Array(4), randstr).join("")
}
})

const container = document.querySelector(".container")
let page = 0
const per = 64

function render() {
const pager = `
<div class="pager">
<button data-pager="prev" ${page === 0 ? "disabled" : ""}>←</button>
<span>${page}</span>
<button data-pager="next" ${(page + 1) * per >= data.length ? "disabled" : ""}>→</button>
</div>
`
const items = data.slice(per * page, per * (page + 1)).map(e => {
return `<div class="item"><div>${e.index}</div><div>${e.value}</div></div>`
}).join("")

container.innerHTML = pager + items + pager
}

container.addEventListener("click", eve => {
const elem = eve.target.closest("[data-pager]")
if (!elem) return
const diff = { prev: -1, next: 1 }[elem.dataset.pager]
page += diff
document.documentElement.scrollTop = 0
render()
})

render()
</script>

click イベントの

document.documentElement.scrollTop = 0

を消せば スクロールバーはそのまま

代わりに

document.documentElement.scrollTo({ top:0, behavior: "smooth" })

にすればアニメーションでスクロールして移動します

ロードした風にする

これをページをロードしたあるように見せます
ロードした感じといえばロード待ちで一瞬画面が白かったり 画面が乱れたりですが 意図的に乱れさせるのは逆に大変なので 単純に一瞬白くします

長くなるので変更点だけ書くと

style タグの css にこれを追加します

	.cover {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: white;
}

JavaScript の click イベントのリスナをこう変更します

	container.addEventListener("click", async eve => {
const elem = eve.target.closest("[data-pager]")
if (!elem) return
const diff = { prev: -1, next: 1 }[elem.dataset.pager]
page += diff

const duration = 80
const cover = document.createElement("div")
cover.className = "cover"
document.body.append(cover)
const cover_anim = cover.animate([{ opacity: 0 }, { opacity: 1 }], { fill: "forwards", duration, easing: "linear" })
await new Promise(r => cover_anim.onfinish = r)
document.documentElement.scrollTop = 0
render()
const uncover_anim = cover.animate([{ opacity: 1 }, { opacity: 0 }], { fill: "forwards", duration, easing: "linear" })
await new Promise(r => uncover_anim.onfinish = r)
cover.remove()
})

一見長いですけど 「画面全体を覆う .cover 要素を表示 → 表示中にスクロールバーを上に戻す → .cover を非表示にする」 という処理をしてるだけです
カバー要素を上に乗せる以外に body の方を透明にするのでも良かったのですが カバー要素のほうが今後拡張しやすいかなと思ってこっちにしました(たぶんしませんけど)

画面表示が消えるのと現れるのはどちらも 80ms の合計 160ms です
これくらい短いアニメーションだと PC スペックやブラウザ依存で印象が多少違うのですが 自分のメインの PC だとこれくらいか もうちょっと短いくらいがいい感じでした

比較用

移動しない
一瞬で上に移動する
スクロールで上に移動する
一瞬画面を白くして上に移動する

比べてみるとやっぱり白くなる方がロードしてる感があります
一瞬で上に移動するだけだと トップへ戻る ボタンを押したときみたいなページ内リンクで移動した感が強くて画面が更新された感じがあまりしないんですよね