diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index 65d3d529dd..57d24d82ee 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -22,6 +22,33 @@
 </div>
 </template>
 
+<script lang="ts">
+/**
+ * アスペクト比算出のためにHTMLElement.clientWidthを使うが、
+ * 大変重たいのでコンテナ要素とメディアリスト幅のペアをキャッシュする
+ * (タイムラインごとにスクロールコンテナが存在する前提だが……)
+ */
+const widthCache = new Map<Element, number>();
+
+/**
+ * コンテナ要素がリサイズされたらキャッシュを削除する
+ */
+const ro = new ResizeObserver(entries => {
+	for (const entry of entries) {
+		widthCache.delete(entry.target);
+	}
+});
+
+function getClientWidthWithCache(targetEl: HTMLElement, containerEl: HTMLElement) {
+	if (widthCache.has(containerEl)) return widthCache.get(containerEl)!;
+
+	const width = targetEl.clientWidth;
+	widthCache.set(containerEl, width);
+	ro.observe(containerEl);
+	return width;
+}
+</script>
+
 <script lang="ts" setup>
 import { onMounted, shallowRef } from 'vue';
 import * as misskey from 'misskey-js';
@@ -62,7 +89,8 @@ function calcAspectRatio() {
 		return;
 	}
 
-	const width = gallery.value.clientWidth;
+	if (!container.value) container.value = getScrollContainer(root.value);
+	const width = container.value ? getClientWidthWithCache(root.value, container.value) : root.value.clientWidth;
 
 	const heightMin = (ratio: number) => {
 		const imgResizeRatio = width / img.properties.width;
@@ -84,7 +112,6 @@ function calcAspectRatio() {
 			gallery.value.style.height = heightMin(3 / 2);
 			break;
 		default: {
-			if (!container.value) container.value = getScrollContainer(root.value);
 			const maxHeight = Math.max(64, (container.value ? container.value.clientHeight : getBodyScrollHeight()) * 0.5 || 360);
 			if (width === 0 || !maxHeight) return;
 			const imgResizeRatio = width / img.properties.width;