diff --git a/CHANGELOG.md b/CHANGELOG.md index fc6569bb2d..d084006562 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ - Feat: ノート検索で投稿日時の期間を条件に加えられるように(#16035) ### Client +- Enhance: ユーザーページのファイルタブでスクロール位置が保持されるように +- Enhance: ドライブページでスクロール位置が保持されるように - Fix: URLプレビューのプレイヤーをウィンドウで開いたとき、プレイヤーが読み込まれるまでの間 `Invalid URL` と表示される問題を修正 - Fix: 一部の実績が正しく表示されない問題を修正 - Fix: アクセストークン発行時のダイアログのタイトルが「確認コード」となっているのを修正 diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index e0eb858047..0386322de9 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -69,6 +69,7 @@ SPDX-License-Identifier: AGPL-3.0-only v-for="(f, i) in foldersPaginator.items.value" :key="f.id" v-anim="i" + :data-scroll-anchor="f.id" :folder="f" :selectMode="select === 'folder'" :isSelected="selectedFolders.some(x => x.id === f.id)" @@ -101,6 +102,7 @@ SPDX-License-Identifier: AGPL-3.0-only > - + ): void { let anchorId: string | null = null; + // キャプチャ時のアンカー要素上端のコンテナ上端からの距離 + let anchorContainerLocalY = 0; + let savedScrollTop = 0; let ready = true; watch(scrollContainerRef, (el) => { if (!el) return; - const onScroll = () => { + const captureAnchor = () => { if (!el) return; if (!ready) return; @@ -29,16 +32,17 @@ export function useScrollPositionKeeper(scrollContainerRef: Ref('[data-scroll-anchor]'); for (let i = anchorEls.length - 1; i > -1; i--) { // 下から見た方が速い - const anchorEl = anchorEls[i] as HTMLElement; - const anchorRect = anchorEl.getBoundingClientRect(); - const anchorTop = anchorRect.top; - const anchorBottom = anchorRect.bottom; - if (anchorTop <= viewPosition && anchorBottom >= viewPosition) { + const anchorEl = anchorEls[i]; + const anchorTop = anchorEl.getBoundingClientRect().top; + // 上端が viewPosition 以下の最初の要素(=中央を跨ぐか、中央より上にある中で最も近いもの)を選択する + // 最下部スクロール時に min-height による空白に viewPosition が入った場合も最後のアイテムをキャプチャできる + if (anchorTop <= viewPosition) { anchorId = anchorEl.getAttribute('data-scroll-anchor'); + anchorContainerLocalY = anchorTop - scrollContainerRect.top; break; } } @@ -47,7 +51,10 @@ export function useScrollPositionKeeper(scrollContainerRef: Ref(`[data-scroll-anchor="${CSS.escape(anchorId)}"]`); if (!scrollAnchorEl) return; - scrollAnchorEl.scrollIntoView({ - behavior: 'instant', - block: 'center', - inline: 'center', - }); + const anchorRect = scrollAnchorEl.getBoundingClientRect(); + // anchorContentY: コンテンツ先頭からのアンカー要素上端の距離(scrollTopに依存しない) + const anchorContentY = scrollContainer.scrollTop + anchorRect.top - scrollContainer.getBoundingClientRect().top; + // キャプチャ時と同じ scrollTop になるよう直接セット(コンテナ高さ変化に依存しない) + scrollContainer.scrollTop = anchorContentY - anchorContainerLocalY; }; onDeactivated(() => { + const el = scrollContainerRef.value; + if (el) savedScrollTop = el.scrollTop; ready = false; }); @@ -76,6 +85,13 @@ export function useScrollPositionKeeper(scrollContainerRef: Ref { restore(); + // anchor方式が失敗した場合(anchorIdがnullまたは要素が見つからない場合)の + // フォールバック + const el = scrollContainerRef.value; + if (el && el.scrollTop === 0 && savedScrollTop > 0) { + el.scrollTop = savedScrollTop; + } + ready = true; }, 100); }); diff --git a/packages/frontend/src/pages/drive.vue b/packages/frontend/src/pages/drive.vue index 38939f9503..9ed7097bd3 100644 --- a/packages/frontend/src/pages/drive.vue +++ b/packages/frontend/src/pages/drive.vue @@ -4,17 +4,21 @@ SPDX-License-Identifier: AGPL-3.0-only -->