diff --git a/packages/duoyun-ui/src/elements/base/resize.ts b/packages/duoyun-ui/src/elements/base/resize.ts
index 2e4f73e7..6c1bca83 100644
--- a/packages/duoyun-ui/src/elements/base/resize.ts
+++ b/packages/duoyun-ui/src/elements/base/resize.ts
@@ -3,16 +3,24 @@ import { GemElement, GemElementOptions } from '@mantou/gem/lib/element';
 
 import { throttle } from '../../lib/utils';
 
+export type ResizeDetail = {
+  contentRect: DuoyunResizeBaseElement['contentRect'];
+  borderBoxSize: DuoyunResizeBaseElement['borderBoxSize'];
+};
+
 export function resizeObserver(ele: DuoyunResizeBaseElement, options: { throttle?: boolean } = {}) {
   const { throttle: needThrottle = true } = options;
   const callback = (entryList: ResizeObserverEntry[]) => {
     entryList.forEach((entry) => {
-      ele.contentRect = entry.contentRect;
-      ele.borderBoxSize = entry.borderBoxSize?.[0]
-        ? entry.borderBoxSize[0]
-        : { blockSize: ele.contentRect.height, inlineSize: ele.contentRect.width };
+      const oldDetail = { contentRect: ele.contentRect, borderBoxSize: ele.borderBoxSize };
+      const { x, y, width, height } = entry.contentRect;
+      // 只支持一个
+      // https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/borderBoxSize
+      const { blockSize, inlineSize } = entry.borderBoxSize[0];
+      ele.contentRect = { x, y, width, height };
+      ele.borderBoxSize = { blockSize, inlineSize };
       ele.update();
-      ele.resize(ele);
+      ele.resize(oldDetail);
     });
   };
   const throttleCallback = needThrottle ? throttle(callback, 300, { leading: true }) : callback;
@@ -22,7 +30,7 @@ export function resizeObserver(ele: DuoyunResizeBaseElement, options: { throttle
 }
 
 export class DuoyunResizeBaseElement<_T = Record<string, unknown>> extends GemElement {
-  @emitter resize: Emitter<DuoyunResizeBaseElement>;
+  @emitter resize: Emitter<ResizeDetail>;
 
   constructor(options: GemElementOptions & { throttle?: boolean } = {}) {
     super(options);
@@ -38,6 +46,8 @@ export class DuoyunResizeBaseElement<_T = Record<string, unknown>> extends GemEl
   };
 
   contentRect = {
+    x: 0,
+    y: 0,
     height: 0,
     width: 0,
   };
diff --git a/packages/duoyun-ui/src/elements/list.ts b/packages/duoyun-ui/src/elements/list.ts
index ac29cf62..9d0a5337 100644
--- a/packages/duoyun-ui/src/elements/list.ts
+++ b/packages/duoyun-ui/src/elements/list.ts
@@ -81,6 +81,7 @@ export class DuoyunListElement extends GemElement<State> {
   /**@deprecated */
   @property data?: any[];
   @property items?: any[];
+  @property key?: any; // 除了 items 提供另外一种方式来更新
   @property renderItem?: (item: any) => TemplateResult;
   @boolattribute debug: boolean;
 
@@ -134,7 +135,8 @@ export class DuoyunListElement extends GemElement<State> {
     return (item === undefined && key === undefined) || key === this.getKey!(item);
   };
 
-  #getElementRowHeight = (ele: DuoyunListItemElement) => ele.borderBoxSize.blockSize + this.#rowGap;
+  #getRowHeight = (ele?: DuoyunListItemElement) =>
+    (ele ? ele.borderBoxSize.blockSize : this.#itemHeight) + this.#rowGap;
 
   #setState = (state: Partial<State>) => {
     this.#log(state);
@@ -144,8 +146,8 @@ export class DuoyunListElement extends GemElement<State> {
   #isLeftItem = (count: number) => !(count % this.#itemColumnCount);
 
   // 没有渲染内容时
-  #reLayout = (options: { silent?: boolean } = {}) => {
-    this.#log('reLayout');
+  #reLayout = (options: { silent?: boolean; resize?: boolean } = {}) => {
+    this.#log('reLayout', options);
     const { beforeHeight, afterHeight, renderList } = this.state;
     // 初始状态
     if (!renderList.length && !beforeHeight && !afterHeight) {
@@ -160,7 +162,7 @@ export class DuoyunListElement extends GemElement<State> {
     const firstElementY = this === this.scrollContainer ? thisRect.top - this.scrollTop : thisRect.top;
     // 上下安全余量
     const safeHeight = containerRect.height;
-    // TODO: Improve performance
+
     let beforeHeightSum = 0;
     let renderHeightSum = 0;
     let afterHeightSum = 0;
@@ -169,8 +171,13 @@ export class DuoyunListElement extends GemElement<State> {
     let pushed = false;
     while (node) {
       const ele = this.#getElement(node.value);
+
+      // 修正那些尚未显示的元素高度,只适用于高度相同的情况
+      // 因为之后触发 before visible 需要用到
+      if (options.resize) ele.borderBoxSize.blockSize = this.#itemHeight;
+
       const isLeft = this.#isLeftItem(count);
-      const currentItemHeight = this.#getElementRowHeight(ele);
+      const currentItemHeight = this.#getRowHeight(ele);
 
       const y = firstElementY + beforeHeightSum + renderHeightSum;
 
@@ -223,7 +230,7 @@ export class DuoyunListElement extends GemElement<State> {
     for (let i = len - 1; i >= 0; i--) {
       const ele = this.#getElement(this.state.renderList[i]);
       if (!ele.visible) {
-        if (this.#isLeftItem(count)) afterHeight += this.#getElementRowHeight(ele);
+        if (this.#isLeftItem(count)) afterHeight += this.#getRowHeight(ele);
         len--;
       }
       count++;
@@ -250,7 +257,7 @@ export class DuoyunListElement extends GemElement<State> {
     let beforeHeight = 0;
     for (let i = 0; i < this.#appendCount; i++) {
       if (!node) break;
-      if (this.#isLeftItem(i)) beforeHeight += this.#getElementRowHeight(this.#getElement(node.value));
+      if (this.#isLeftItem(i)) beforeHeight += this.#getRowHeight(this.#getElement(node.value));
       appendList.unshift(node.value);
       node = node.prev;
     }
@@ -261,6 +268,27 @@ export class DuoyunListElement extends GemElement<State> {
     });
   };
 
+  #appendItems = (items: any[], oldItems?: any[]) => {
+    if (!oldItems) return;
+    let beforeHeight = 0;
+    for (let i = 0; i < items.length; i++) {
+      if (this.getKey!(items[i]) === this.getKey!(oldItems[0])) break;
+      if (this.#isLeftItem(i)) beforeHeight += this.#getRowHeight();
+    }
+    if (beforeHeight) {
+      // 有向前(上)加载数据,必须是列数的倍数
+      this.#setState({ beforeHeight: this.state.beforeHeight + beforeHeight });
+      // 等待渲染后再滚动
+      queueMicrotask(() => {
+        this.scrollContainer.scrollBy({
+          left: 0,
+          top: beforeHeight,
+          behavior: 'instant',
+        });
+      });
+    }
+  };
+
   #onAfterItemVisible = () => {
     this.#log('onAfterItemVisible');
 
@@ -278,7 +306,7 @@ export class DuoyunListElement extends GemElement<State> {
       const ele = this.#getElement(key);
       if (!ele.visible) {
         len++;
-        if (this.#isLeftItem(count)) beforeHeight += this.#getElementRowHeight(ele);
+        if (this.#isLeftItem(count)) beforeHeight += this.#getRowHeight(ele);
       }
       count++;
     }
@@ -306,7 +334,7 @@ export class DuoyunListElement extends GemElement<State> {
     for (let i = 0; i < this.#appendCount; i++) {
       if (!node) break;
       appendList.push(node.value);
-      if (this.#isLeftItem(i)) afterHeight += this.#getElementRowHeight(this.#getElement(node.value));
+      if (this.#isLeftItem(i)) afterHeight += this.#getRowHeight(this.#getElement(node.value));
       node = node.next;
     }
     this.#setState({
@@ -320,34 +348,47 @@ export class DuoyunListElement extends GemElement<State> {
   // 延迟执行确保读取 afterVisible 正确
   #initCheckOnce = once((silent: boolean) => setTimeout(() => this.#afterVisible && this.#reLayout({ silent }), 60));
 
+  // 用于计算那些没有显示过的元素,item 高度不一致时需要用户提供函数?
   #itemHeight = 0;
   #itemColumnCount = 1;
   #rowGap = 0;
   #columnGap = 0;
   // 跟用户初始 Items 长度相同会触发两次 backward 事件,用户配置?
   #itemCountPerScreen = 19;
-  #onItemResize = throttle(
-    ({ target }) => {
-      const ele = target as DuoyunListItemElement | null;
-      if (ele?.borderBoxSize.blockSize) {
-        this.#initCheckOnce(this.items!.length > this.#itemCountPerScreen);
-
-        const style = getComputedStyle(this.listRef.element!);
-        const thisGrid = getComputedStyle(this);
-        this.#rowGap = parseFloat(style.rowGap) || parseFloat(thisGrid.rowGap) || 0;
-        this.#columnGap = parseFloat(style.columnGap) || parseFloat(thisGrid.columnGap) || 0;
-
-        this.#itemColumnCount = Math.round(
-          this.scrollContainer.clientWidth / (ele.borderBoxSize.inlineSize + this.#columnGap),
-        );
-        this.#itemHeight = ele.borderBoxSize.blockSize;
-        this.#itemCountPerScreen =
-          Math.ceil(this.scrollContainer.clientHeight / this.#getElementRowHeight(ele)) * this.#itemColumnCount;
-      }
-    },
-    1000,
-    { leading: true },
-  );
+  // 初次渲染
+  #initLayout = (ele: DuoyunListItemElement) => {
+    this.#initCheckOnce(this.items!.length > this.#itemCountPerScreen);
+
+    const style = getComputedStyle(this.listRef.element!);
+    const thisGrid = getComputedStyle(this);
+    this.#rowGap = parseFloat(style.rowGap) || parseFloat(thisGrid.rowGap) || 0;
+    this.#columnGap = parseFloat(style.columnGap) || parseFloat(thisGrid.columnGap) || 0;
+
+    this.#itemColumnCount = Math.round(
+      this.scrollContainer.clientWidth / (ele.borderBoxSize.inlineSize + this.#columnGap),
+    );
+    this.#itemHeight = ele.borderBoxSize.blockSize;
+    this.#itemCountPerScreen =
+      Math.ceil(this.scrollContainer.clientHeight / this.#getRowHeight(ele)) * this.#itemColumnCount;
+  };
+
+  #onItemResizeInit = throttle(this.#initLayout, 1000, { leading: true });
+
+  #onItemResize = ({ target }: CustomEvent) => {
+    const ele = target as DuoyunListItemElement;
+    if (!ele.borderBoxSize.blockSize) return;
+    this.#onItemResizeInit(ele);
+
+    // 视口宽度改变导致的 Resize,使用 `itemHeight` 是避免滚动时再次触发
+    if (this.#itemHeight !== ele.borderBoxSize.blockSize) {
+      this.#reLayoutByResize(ele);
+    }
+  };
+
+  #reLayoutByResize = throttle((ele: DuoyunListItemElement) => {
+    this.#initLayout(ele);
+    this.#reLayout({ resize: true });
+  }, 200);
 
   #keyElementMap = new Map<any, DuoyunListItemElement>();
   #getElement = (key: Key) => {
@@ -357,11 +398,13 @@ export class DuoyunListElement extends GemElement<State> {
       ele.addEventListener('resize', this.#onItemResize);
       ele.addEventListener('show', () => this.itemshow(this.#keyItemMap.get(key)));
       ele.intersectionRoot = this.scrollContainer;
+      // 赋值初始值,用于没渲染的计算高度
       ele.borderBoxSize.blockSize = this.#itemHeight;
       this.#keyElementMap.set(key, ele);
     }
     const ele = this.#keyElementMap.get(key)!;
     ele.item = this.#keyItemMap.get(key);
+    ele.key = this.key;
     ele.renderItem = this.renderItem;
     return ele;
   };
@@ -396,35 +439,25 @@ export class DuoyunListElement extends GemElement<State> {
 
     this.memo(
       ([items], oldDeps) => {
-        if (this.infinite && items) {
-          // 向前(上)加载数据,必须是列数的倍数
-          if (items.length && oldDeps?.[0]?.length) {
-            let beforeHeight = 0;
-            for (let i = 0; i < items.length; i++) {
-              if (this.getKey!(items[i]) === this.getKey!(oldDeps[0][0])) break;
-              if (this.#isLeftItem(i)) beforeHeight += this.#itemHeight + this.#rowGap;
-            }
-            if (beforeHeight) {
-              this.#setState({ beforeHeight: this.state.beforeHeight + beforeHeight });
-              // 等待渲染后再滚动
-              queueMicrotask(() => {
-                this.scrollContainer.scrollBy({
-                  left: 0,
-                  top: beforeHeight,
-                  behavior: 'instant',
-                });
-              });
-            }
-          }
-
-          // TODO: Improve performance
-          this.#itemLinked = new LinkedList();
-          this.#keyItemMap = new Map();
-          items.forEach((item) => {
-            const key = this.getKey!(item);
-            this.#keyItemMap.set(key, item);
-            this.#itemLinked.add(key);
-          });
+        // infinite 改变会发生什么?
+        if (!this.infinite) return;
+
+        const oldLinkedList = this.#itemLinked;
+        this.#itemLinked = new LinkedList();
+        this.#keyItemMap = new Map();
+        items?.forEach((item) => {
+          const key = this.getKey!(item);
+          this.#keyItemMap.set(key, item);
+          this.#itemLinked.add(key);
+        });
+
+        if (this.#itemLinked.isSuperLinkOf(oldLinkedList)) {
+          // 是父集 items 就肯定有内容
+          this.#appendItems(items!, oldDeps?.at(0));
+        } else {
+          // 列表改了,需要重排
+          this.#setState({ beforeHeight: 0, renderList: [], afterHeight: 0 });
+          this.#reLayout();
         }
       },
       () => [this.#items],
@@ -433,7 +466,7 @@ export class DuoyunListElement extends GemElement<State> {
 
   mounted = () => {
     this.scrollContainer = findScrollContainer(this) || document.documentElement;
-    this.scrollContainer.scrollTo(0, this.#initState?.scrollTop || 0);
+    if (this.#initState) this.scrollContainer.scrollTo(0, this.#initState.scrollTop);
 
     this.effect(() => {
       this.scrollContainer.addEventListener('scroll', this.#onScroll);
@@ -451,31 +484,38 @@ export class DuoyunListElement extends GemElement<State> {
     return html`
       <slot name=${DuoyunListElement.before}></slot>
       ${this.infinite
-        ? html`
-            <dy-list-outside
-              ref=${this.beforeItemRef.ref}
-              part=${DuoyunListElement.beforeOutside}
-              .intersectionRoot=${this.scrollContainer}
-              @show=${this.#onBeforeItemVisible}
-              style=${styleMap({ height: `${beforeHeight}px` })}
-            ></dy-list-outside>
-            <div ref=${this.listRef.ref} class="list" part=${DuoyunListElement.list}>
-              ${renderList.map((key) => this.#getElement(key))}
-            </div>
-            <dy-list-outside
-              ref=${this.afterItemRef.ref}
-              part=${DuoyunListElement.afterOutside}
-              .intersectionRoot=${this.scrollContainer}
-              @show=${this.#onAfterItemVisible}
-              style=${styleMap({ height: `${afterHeight}px` })}
-            >
-            </dy-list-outside>
-          `
-        : this.#items?.map(
-            (item) => html`
-              <dy-list-item part=${DuoyunListElement.item} .item=${item} .renderItem=${this.renderItem}></dy-list-item>
-            `,
-          )}
+        ? html`<dy-list-outside
+            ref=${this.beforeItemRef.ref}
+            part=${DuoyunListElement.beforeOutside}
+            .intersectionRoot=${this.scrollContainer}
+            @show=${this.#onBeforeItemVisible}
+            style=${styleMap({ height: `${beforeHeight}px` })}
+          ></dy-list-outside>`
+        : html``}
+      <div ref=${this.listRef.ref} class="list" part=${DuoyunListElement.list}>
+        ${this.infinite
+          ? renderList.map((key) => this.#getElement(key))
+          : this.#items?.map(
+              (item) => html`
+                <dy-list-item
+                  part=${DuoyunListElement.item}
+                  .item=${item}
+                  .key=${this.key}
+                  .renderItem=${this.renderItem}
+                ></dy-list-item>
+              `,
+            )}
+      </div>
+      ${this.infinite
+        ? html`<dy-list-outside
+            ref=${this.afterItemRef.ref}
+            part=${DuoyunListElement.afterOutside}
+            .intersectionRoot=${this.scrollContainer}
+            @show=${this.#onAfterItemVisible}
+            style=${styleMap({ height: `${afterHeight}px` })}
+          >
+          </dy-list-outside>`
+        : html``}
       <slot name=${DuoyunListElement.after}>
         <!-- 无限滚动时避免找不到 "dy-list-outside", e.g: dy-list docs -->
         <div class="placeholder" style="height: 1px"></div>
@@ -545,6 +585,7 @@ export class DuoyunListItemElement extends DuoyunResizeBaseElement implements Du
 
   @property item?: any;
   @property renderItem?: (item: any) => TemplateResult;
+  @property key?: any; // 提供另外一种方式来更新
 
   constructor() {
     super({ delegatesFocus: true });
diff --git a/packages/gem-examples/src/multi-page/index.ts b/packages/gem-examples/src/multi-page/index.ts
index 2248203b..bf0d2ef2 100644
--- a/packages/gem-examples/src/multi-page/index.ts
+++ b/packages/gem-examples/src/multi-page/index.ts
@@ -1,5 +1,5 @@
 import { GemElement, html, render } from '@mantou/gem';
-import { GemTitleElement } from '@mantou/gem/elements/title';
+import '@mantou/gem/elements/title';
 import '@mantou/gem/elements/route';
 import '@mantou/gem/elements/link';
 
@@ -54,7 +54,7 @@ class App extends GemElement {
           text-decoration: underline;
         }
       </style>
-      <header><gem-title prefix=${GemTitleElement.defaultPrefix}>AppName</gem-title></header>
+      <header><gem-title prefix=${'😊'}>AppName</gem-title></header>
       <nav>
         <gem-link path="/">Home</gem-link>
         <gem-link path="/a">PageA</gem-link>
diff --git a/packages/gem/src/elements/base/route.ts b/packages/gem/src/elements/base/route.ts
index 862efa68..e1a4ee9b 100644
--- a/packages/gem/src/elements/base/route.ts
+++ b/packages/gem/src/elements/base/route.ts
@@ -122,6 +122,8 @@ export class GemRouteElement extends GemElement<State> {
   @boolattribute transition: boolean;
   @property routes?: RouteItem[] | RoutesObject;
   /**
+   * 不要多个 `<gem-route>` 共享,因为那样会导致后面的元素卸载前触发更新
+   *
    * @example
    * const locationStore = GemRouteElement.createLocationStore()
    * html`<gem-route .locationStore=${locationStore}>`
@@ -194,10 +196,13 @@ export class GemRouteElement extends GemElement<State> {
       this.setState({ content });
       this.routechange(this.currentRoute);
       this.#updateLocationStore();
-      return Promise.resolve();
     };
     if (this.transition && 'startViewTransition' in document) {
-      (document as any).startViewTransition(changeContent);
+      (document as any).startViewTransition(() => {
+        changeContent();
+        // 等待路由渲染
+        return Promise.resolve();
+      });
     } else {
       changeContent();
     }
diff --git a/packages/gem/src/elements/base/title.ts b/packages/gem/src/elements/base/title.ts
index 76123b24..527ac3df 100644
--- a/packages/gem/src/elements/base/title.ts
+++ b/packages/gem/src/elements/base/title.ts
@@ -4,7 +4,7 @@
  * - 桌面端 Tab 的 title
  * - 移动端 AppBar 的 title
  *
- * 修改标题:
+ * 修改标题:titleStore 的 title 优先级高,比如 history 添加的 dialog Title
  *
  * - `<gem-route>` 匹配路由时自动设置 `route.title`
  * - `<gem-link>` 的 `doc-title` 属性和 `route.title`
@@ -18,42 +18,55 @@ import { attribute, connectStore } from '../../lib/decorators';
 import { updateStore, connect } from '../../lib/store';
 import { titleStore } from '../../lib/history';
 
+const defaultTitle = document.title;
+
+function updateTitle(str?: string | null, prefix = '', suffix = '') {
+  const title = titleStore.title || str;
+  if (title && title !== defaultTitle) {
+    GemTitleElement.title = title;
+    document.title = prefix + GemTitleElement.title + suffix;
+  } else {
+    GemTitleElement.title = defaultTitle;
+    document.title = GemTitleElement.title;
+  }
+}
+
+export const PREFIX = `${defaultTitle} | `;
+export const SUFFIX = ` - ${defaultTitle}`;
+
 /**
  * 允许声明式设置 `document.title`
+ * @attr prefix
  * @attr suffix
  */
 @connectStore(titleStore)
 export class GemTitleElement extends GemElement {
-  // 没有后缀的标题
+  @attribute prefix: string;
+  @attribute suffix: string;
+
+  // 没有后缀的当前标题
   static title = document.title;
-  static defaultTitle = document.title;
-  static defaultPrefix = `${document.title} | `;
-  static defaultSuffix = ` - ${document.title}`;
+
+  /**@deprecated */
+  static defaultPrefix = PREFIX;
+  /**@deprecated */
+  static defaultSuffix = SUFFIX;
 
   static setTitle(title: string) {
     updateStore(titleStore, { title });
   }
 
-  static updateTitle(title = titleStore.title, prefix = '', suffix = '') {
-    if (title === GemTitleElement.defaultTitle) {
-      document.title = GemTitleElement.title = title;
-    } else {
-      GemTitleElement.title = title;
-      document.title = prefix + GemTitleElement.title + suffix;
-    }
-  }
-
-  @attribute prefix: string;
-  @attribute suffix: string;
-
   constructor() {
     super();
-    new MutationObserver(() => this.update()).observe(this, { childList: true, characterData: true, subtree: true });
+    new MutationObserver(() => this.update()).observe(this, {
+      characterData: true,
+      subtree: true,
+    });
   }
 
   render() {
     // 多个 <gem-title> 时,最终 document.title 按执行顺序决定
-    GemTitleElement.updateTitle(this.textContent || undefined, this.prefix, this.suffix);
+    updateTitle(this.textContent, this.prefix, this.suffix);
 
     if (this.hidden) {
       return html``;
@@ -62,4 +75,4 @@ export class GemTitleElement extends GemElement {
   }
 }
 
-connect(titleStore, GemTitleElement.updateTitle);
+connect(titleStore, updateTitle);
diff --git a/packages/gem/src/lib/utils.ts b/packages/gem/src/lib/utils.ts
index 8aeaceef..eaed5a85 100644
--- a/packages/gem/src/lib/utils.ts
+++ b/packages/gem/src/lib/utils.ts
@@ -91,6 +91,18 @@ export class LinkedList<T = any> extends EventTarget {
     return this.#lastItem;
   }
 
+  isSuperLinkOf(subLink: LinkedList<T>) {
+    let subItem = subLink.first;
+    if (!subItem) return true;
+    let item = this.find(subItem.value);
+    while (item && item.value === subItem.value) {
+      subItem = subItem.next;
+      if (!subItem) return true;
+      item = item.next;
+    }
+    return false;
+  }
+
   find(value: T) {
     return this.#map.get(value);
   }