diff --git a/src/core/render/container.ts b/src/core/render/container.ts
index 3086650..d975cce 100644
--- a/src/core/render/container.ts
+++ b/src/core/render/container.ts
@@ -1,3 +1,4 @@
+import { ElementNamespace, ComponentInternalInstance } from 'vue';
 import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
 import { ActionType, EventProgress, ActionEventMap } from './event';
 import {
@@ -14,7 +15,6 @@ export class Container<E extends EContainerEvent = EContainerEvent>
     extends RenderItem<E | EContainerEvent>
     implements IRenderChildable
 {
-    children: Set<RenderItem> = new Set();
     sortedChildren: RenderItem[] = [];
 
     private needSort: boolean = false;
@@ -133,7 +133,53 @@ export class Container<E extends EContainerEvent = EContainerEvent>
     destroy(): void {
         super.destroy();
         this.children.forEach(v => {
-            v.destroy();
+            v.remove();
         });
     }
 }
+
+export type CustomContainerRenderFn = (
+    canvas: MotaOffscreenCanvas2D,
+    children: RenderItem[],
+    transform: Transform
+) => void;
+
+export class ContainerCustom extends Container {
+    private renderFn?: CustomContainerRenderFn;
+
+    protected render(
+        canvas: MotaOffscreenCanvas2D,
+        transform: Transform
+    ): void {
+        if (!this.renderFn) {
+            super.render(canvas, transform);
+        } else {
+            this.renderFn(canvas, this.sortedChildren, transform);
+        }
+    }
+
+    /**
+     * 设置这个自定义容器的渲染函数
+     * @param render 渲染函数
+     */
+    setRenderFn(render?: CustomContainerRenderFn) {
+        this.renderFn = render;
+    }
+
+    patchProp(
+        key: string,
+        prevValue: any,
+        nextValue: any,
+        namespace?: ElementNamespace,
+        parentComponent?: ComponentInternalInstance | null
+    ): void {
+        switch (key) {
+            case 'render': {
+                if (!this.assertType(nextValue, 'function', key)) return;
+                this.setRenderFn(nextValue);
+                return;
+            }
+        }
+        super.patchProp(key, prevValue, nextValue, namespace, parentComponent);
+    }
+}
diff --git a/src/core/render/item.ts b/src/core/render/item.ts
index f9deaf0..f81562c 100644
--- a/src/core/render/item.ts
+++ b/src/core/render/item.ts
@@ -362,6 +362,11 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
         this._transform.bind(this);
         this.cache = this.requireCanvas();
         this.cache.withGameScale(true);
+        if (!enableCache) {
+            this.cache.withGameScale(false);
+            this.cache.size(1, 1);
+            this.cache.freeze();
+        }
     }
 
     /**
@@ -437,7 +442,9 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
     size(width: number, height: number): void {
         this.width = width;
         this.height = height;
-        this.cache.size(width, height);
+        if (this.enableCache) {
+            this.cache.size(width, height);
+        }
         this.update(this);
     }
 
@@ -480,13 +487,17 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
 
     setHD(hd: boolean): void {
         this.highResolution = hd;
-        this.cache.setHD(hd);
+        if (this.enableCache) {
+            this.cache.setHD(hd);
+        }
         this.update(this);
     }
 
     setAntiAliasing(anti: boolean): void {
         this.antiAliasing = anti;
-        this.cache.setAntiAliasing(anti);
+        if (this.enableCache) {
+            this.cache.setAntiAliasing(anti);
+        }
         this.update(this);
     }
 
@@ -540,7 +551,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
     }
 
     /**
-     * 获取到可以包围这个元素的最小矩形
+     * 获取到可以包围这个元素的最小矩形,相对于父元素
      */
     getBoundingRect(): DOMRectReadOnly {
         if (this.type === 'absolute') {
diff --git a/src/core/render/renderer/elements.tsx b/src/core/render/renderer/elements.tsx
index aac021a..818b070 100644
--- a/src/core/render/renderer/elements.tsx
+++ b/src/core/render/renderer/elements.tsx
@@ -13,6 +13,7 @@ import {
     BezierProps,
     CirclesProps,
     CommentProps,
+    ConatinerCustomProps,
     ContainerProps,
     CustomProps,
     DamageProps,
@@ -84,6 +85,10 @@ declare module 'vue/jsx-runtime' {
         export interface IntrinsicElements {
             sprite: TagDefine<SpriteProps, ESpriteEvent>;
             container: TagDefine<ContainerProps, EContainerEvent>;
+            'container-custom': TagDefine<
+                ConatinerCustomProps,
+                EContainerEvent
+            >;
             shader: TagDefine<ShaderProps, EShaderEvent>;
             text: TagDefine<TextProps, ETextEvent>;
             image: TagDefine<ImageProps, EImageEvent>;
diff --git a/src/core/render/renderer/map.ts b/src/core/render/renderer/map.ts
index d7cc4a9..611e443 100644
--- a/src/core/render/renderer/map.ts
+++ b/src/core/render/renderer/map.ts
@@ -1,7 +1,7 @@
 import { logger } from '@/core/common/logger';
 import { ERenderItemEvent, RenderItem, RenderItemPosition } from '../item';
 import { ElementNamespace, VNodeProps } from 'vue';
-import { Container } from '../container';
+import { Container, ContainerCustom } from '../container';
 import { MotaRenderer } from '../render';
 import { Sprite } from '../sprite';
 import {
@@ -75,8 +75,13 @@ const standardElement = (
     return (_0: any, _1: any, props?: any) => {
         if (!props) return new Item('static');
         else {
-            const { type = 'static', cache = true, fall = false } = props;
-            return new Item(type, cache, fall);
+            const {
+                type = 'static',
+                cache = true,
+                fall = false,
+                nocache = false
+            } = props;
+            return new Item(type, cache && !nocache, fall);
         }
     };
 };
@@ -91,8 +96,13 @@ const standardElementNoCache = (
     return (_0: any, _1: any, props?: any) => {
         if (!props) return new Item('static');
         else {
-            const { type = 'static', cache = false, fall = false } = props;
-            return new Item(type, cache, fall);
+            const {
+                type = 'static',
+                cache = false,
+                fall = false,
+                nocache = true
+            } = props;
+            return new Item(type, cache && !nocache, fall);
         }
     };
 };
@@ -124,15 +134,17 @@ const se = (
             const {
                 type = position,
                 cache = defaultCache,
-                fall = defautFall
+                fall = defautFall,
+                nocache = !defaultCache
             } = props;
-            return new Item(type, cache, fall);
+            return new Item(type, cache && !nocache, fall);
         }
     };
 };
 
 // Default elements
 tagMap.register('container', standardElement(Container));
+tagMap.register('container-custom', standardElement(ContainerCustom));
 tagMap.register('template', standardElement(Container));
 tagMap.register('mota-renderer', (_0, _1, props) => {
     return new MotaRenderer(props?.id);
diff --git a/src/core/render/renderer/props.ts b/src/core/render/renderer/props.ts
index 0f155ab..9d2e204 100644
--- a/src/core/render/renderer/props.ts
+++ b/src/core/render/renderer/props.ts
@@ -8,6 +8,7 @@ import {
 import type { EnemyCollection } from '@/game/enemy/damage';
 import { ILineProperty } from '../preset/graphics';
 import { ElementAnchor, ElementLocator } from '../utils';
+import { CustomContainerRenderFn } from '../container';
 
 export interface CustomProps {
     _item: (props: BaseProps) => RenderItem;
@@ -27,7 +28,10 @@ export interface BaseProps {
     hidden?: boolean;
     transform?: Transform;
     type?: RenderItemPosition;
+    /** 是否启用缓存,用处较少,主要用于一些默认不启用缓存的元素的特殊优化 */
     cache?: boolean;
+    /** 是否不启用缓存,优先级大于 cache,用处较少,主要用于一些特殊优化 */
+    nocache?: boolean;
     fall?: boolean;
     id?: string;
     alpha?: number;
@@ -49,6 +53,10 @@ export interface SpriteProps extends BaseProps {
 
 export interface ContainerProps extends BaseProps {}
 
+export interface ConatinerCustomProps extends ContainerProps {
+    render?: CustomContainerRenderFn;
+}
+
 export interface GL2Props extends BaseProps {}
 
 export interface ShaderProps extends BaseProps {}
diff --git a/src/core/render/sprite.ts b/src/core/render/sprite.ts
index ddb177f..db8c5e9 100644
--- a/src/core/render/sprite.ts
+++ b/src/core/render/sprite.ts
@@ -7,7 +7,6 @@ import {
 import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
 import { Transform } from './transform';
 import { ElementNamespace, ComponentInternalInstance } from 'vue';
-import { ActionType, EventProgress, ActionEventMap } from './event';
 
 export interface ESpriteEvent extends ERenderItemEvent {}
 
@@ -43,14 +42,6 @@ export class Sprite<
         this.update(this);
     }
 
-    protected propagateEvent<T extends ActionType>(
-        type: T,
-        _progress: EventProgress,
-        event: ActionEventMap[T]
-    ): void {
-        this.parent?.bubbleEvent(type, event);
-    }
-
     patchProp(
         key: string,
         prevValue: any,
diff --git a/src/module/render/components/scroll.tsx b/src/module/render/components/scroll.tsx
index 0d1c1df..88ba74d 100644
--- a/src/module/render/components/scroll.tsx
+++ b/src/module/render/components/scroll.tsx
@@ -1,10 +1,10 @@
 import {
     computed,
     defineComponent,
+    nextTick,
     onMounted,
     onUnmounted,
     onUpdated,
-    reactive,
     ref,
     SlotsType,
     VNode,
@@ -13,11 +13,10 @@ import {
 import { SetupComponentOptions } from './types';
 import {
     Container,
-    ContainerProps,
     ElementLocator,
     RenderItem,
     Sprite,
-    SpriteProps
+    Transform
 } from '@/core/render';
 import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
 import { hyper, Transition } from 'mutate-animate';
@@ -29,9 +28,18 @@ export const enum ScrollDirection {
     Vertical
 }
 
-interface ScrollProps {
-    direction: ScrollDirection;
+export interface ScrollExpose {
+    /**
+     * 控制滚动条滚动至目标位置
+     * @param y 滚动至的目标位置
+     * @param time 滚动的动画时长,默认为无动画
+     */
+    scrollTo(y: number, time?: number): void;
+}
+
+export interface ScrollProps {
     loc: ElementLocator;
+    hor?: boolean;
     noscroll?: boolean;
     /**
      * 滚动到最下方(最右方)时的填充大小,如果默认的高度计算方式有误,
@@ -45,22 +53,47 @@ type ScrollSlots = SlotsType<{
 }>;
 
 const scrollProps = {
-    props: ['direction', 'noscroll']
+    props: ['hor', 'noscroll', 'loc', 'padHeight']
 } satisfies SetupComponentOptions<ScrollProps, {}, string, ScrollSlots>;
 
 /** 滚动条图示的最短长度 */
 const SCROLL_MIN_LENGTH = 20;
 /** 滚动条图示的宽度 */
 const SCROLL_WIDTH = 10;
+/** 滚动条的颜色 */
+const SCROLL_COLOR = '#ddd';
 
+/**
+ * 滚动条组件,具有虚拟滚动功能,即在画面外的不渲染。参数参考 {@link ScrollProps},暴露接口参考 {@link ScrollExpose}
+ *
+ * ---
+ *
+ * 使用时,建议使用平铺式布局,即包含很多子元素,而不要用一个 container 将所有内容包裹,
+ * 每个子元素的高度(宽度)不建议过大,以更好地通过虚拟滚动优化
+ *
+ * **推荐写法**:
+ * ```tsx
+ * <Scroll>
+ *   <item />
+ *   <item />
+ *   ...其他元素
+ *   <item />
+ *   <item />
+ * </Scroll>
+ * ```
+ * **不推荐**使用这种写法:
+ * ```tsx
+ * <Scroll>
+ *   <container>
+ *     <item />
+ *   </container>
+ * <Scroll>
+ * ```
+ */
 export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
-    (props, { slots }) => {
-        const scrollProps: SpriteProps = reactive({
-            loc: [0, 0, 0, 0]
-        });
-        const contentProps: ContainerProps = reactive({
-            loc: [0, 0, 0, 0]
-        });
+    (props, { slots, expose }) => {
+        /** 滚动条的定位 */
+        const sp = ref<ElementLocator>([0, 0, 1, 1]);
 
         const listenedChild: Set<RenderItem> = new Set();
         const areaMap: Map<RenderItem, [number, number]> = new Map();
@@ -69,52 +102,63 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
 
         const width = computed(() => props.loc[2] ?? 200);
         const height = computed(() => props.loc[3] ?? 200);
+        const direction = computed(() =>
+            props.hor ? ScrollDirection.Horizontal : ScrollDirection.Vertical
+        );
 
-        let showScroll = 0;
-        let nowScroll = 0;
+        /** 滚动内容的当前位置 */
+        let contentPos = 0;
+        /** 滚动条的当前位置 */
+        let scrollPos = 0;
+        /** 滚动内容的目标位置 */
+        let contentTarget = 0;
+        /** 滚动条的目标位置 */
+        let scrollTarget = 0;
+        /** 滚动内容的长度 */
         let maxLength = 0;
+        /** 滚动条的长度 */
         let scrollLength = SCROLL_MIN_LENGTH;
 
         const transition = new Transition();
         transition.value.scroll = 0;
+        transition.value.showScroll = 0;
         transition.mode(hyper('sin', 'out')).absolute();
 
+        //#region 滚动操作
+
         transition.ticker.add(() => {
-            if (transition.value.scroll !== nowScroll) {
-                showScroll = transition.value.scroll;
-                scroll.value?.update();
+            if (scrollPos !== scrollTarget) {
+                scrollPos = transition.value.scroll;
+                content.value?.update();
+            }
+            if (contentPos !== contentTarget) {
+                contentPos = transition.value.showScroll;
+                checkAllItem();
+                updatePosition();
                 content.value?.update();
             }
         });
 
-        watch(
-            () => props.loc,
-            value => {
-                const width = value[2] ?? 200;
-                const height = value[3] ?? 200;
-                if (props.direction === ScrollDirection.Horizontal) {
-                    props.loc = [0, height - SCROLL_WIDTH, width, SCROLL_WIDTH];
-                } else {
-                    props.loc = [width - SCROLL_WIDTH, 0, SCROLL_WIDTH, height];
-                }
-            }
-        );
-
         /**
          * 滚动到目标值
          * @param time 动画时长
          */
-        const scrollTo = (y: number, time: number = 1) => {
-            const target = clamp(y, 0, maxLength);
-            transition.time(time).transition('scroll', target);
-            nowScroll = y;
+        const scrollTo = (y: number, time: number = 0) => {
+            if (maxLength < height.value) return;
+            const max = maxLength - height.value;
+            const target = clamp(y, 0, max);
+            contentTarget = target;
+            scrollTarget =
+                (height.value - scrollLength) * (contentTarget / max);
+            transition.time(time).transition('scroll', scrollTarget);
+            transition.time(time).transition('showScroll', target);
         };
 
         /**
          * 计算一个元素会在画面上显示的区域
          */
         const getArea = (item: RenderItem, rect: DOMRectReadOnly) => {
-            if (props.direction === ScrollDirection.Horizontal) {
+            if (direction.value === ScrollDirection.Horizontal) {
                 areaMap.set(item, [rect.left - width.value, rect.right]);
             } else {
                 areaMap.set(item, [rect.top - height.value, rect.bottom]);
@@ -131,13 +175,20 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
                 return;
             }
             const [min, max] = area;
-            if (nowScroll > min - 10 && nowScroll < max + 10) {
+            if (contentPos > min - 10 && contentPos < max + 10) {
                 item.show();
             } else {
                 item.hide();
             }
         };
 
+        /**
+         * 对所有元素执行显示检查
+         */
+        const checkAllItem = () => {
+            content.value?.children.forEach(v => checkItem(v));
+        };
+
         /**
          * 当一个元素的矩阵发生变换时执行,检查其显示区域
          */
@@ -147,15 +198,46 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
             checkItem(item);
         };
 
+        /**
+         * 更新滚动条位置
+         */
+        const updatePosition = () => {
+            if (direction.value === ScrollDirection.Horizontal) {
+                scrollLength = Math.max(
+                    SCROLL_MIN_LENGTH,
+                    (width.value / maxLength) * width.value
+                );
+                const h = props.noscroll
+                    ? height.value
+                    : height.value - SCROLL_WIDTH;
+                sp.value = [0, h, width.value, SCROLL_WIDTH];
+            } else {
+                scrollLength = clamp(
+                    (height.value / maxLength) * height.value,
+                    SCROLL_MIN_LENGTH,
+                    height.value - 10
+                );
+                const w = props.noscroll
+                    ? width.value
+                    : width.value - SCROLL_WIDTH;
+                sp.value = [w, 0, SCROLL_WIDTH, height.value];
+            }
+        };
+
+        let updating = false;
         const updateScroll = () => {
-            if (!content.value) return;
+            if (!content.value || updating) return;
+            updating = true;
+            nextTick(() => {
+                updating = false;
+            });
             let max = 0;
             listenedChild.forEach(v => v.off('transform', onTransform));
             listenedChild.clear();
             areaMap.clear();
             content.value.children.forEach(v => {
                 const rect = v.getBoundingRect();
-                if (props.direction === ScrollDirection.Horizontal) {
+                if (direction.value === ScrollDirection.Horizontal) {
                     if (rect.right > max) {
                         max = rect.right;
                     }
@@ -166,76 +248,95 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
                 }
                 v.on('transform', onTransform);
                 listenedChild.add(v);
+                checkItem(v);
             });
             maxLength = max + (props.padHeight ?? 0);
-            if (props.direction === ScrollDirection.Horizontal) {
-                scrollLength = Math.max(
-                    SCROLL_MIN_LENGTH,
-                    (width.value / max) * width.value
-                );
-                const h = props.noscroll
-                    ? height.value
-                    : height.value - SCROLL_WIDTH;
-                contentProps.loc = [-showScroll, 0, width.value, h];
-            } else {
-                scrollLength = clamp(
-                    (height.value / max) * height.value,
-                    SCROLL_MIN_LENGTH,
-                    height.value - 10
-                );
-                const w = props.noscroll
-                    ? width.value
-                    : width.value - SCROLL_WIDTH;
-                contentProps.loc = [0, -showScroll, w, height.value];
-            }
+            updatePosition();
             scroll.value?.update();
         };
 
+        watch(() => props.loc, updateScroll);
         onUpdated(updateScroll);
         onMounted(updateScroll);
         onUnmounted(() => {
             listenedChild.forEach(v => v.off('transform', onTransform));
         });
 
+        //#endregion
+
+        //#region 渲染滚动
+
         const drawScroll = (canvas: MotaOffscreenCanvas2D) => {
             if (props.noscroll) return;
             const ctx = canvas.ctx;
             ctx.lineCap = 'round';
-            ctx.lineWidth = 6;
-            ctx.strokeStyle = '#fff';
+            ctx.lineWidth = 3;
+            ctx.strokeStyle = SCROLL_COLOR;
             ctx.beginPath();
-            if (props.direction === ScrollDirection.Horizontal) {
-                ctx.moveTo(nowScroll + 5, 5);
-                ctx.lineTo(nowScroll + scrollLength + 5, 5);
+            const scroll = transition.value.scroll;
+            if (direction.value === ScrollDirection.Horizontal) {
+                ctx.moveTo(scroll + 5, 5);
+                ctx.lineTo(scroll + scrollLength - 5, 5);
             } else {
-                ctx.moveTo(5, nowScroll + 5);
-                ctx.lineTo(5, nowScroll + scrollLength + 5);
+                ctx.moveTo(5, scroll + 5);
+                ctx.lineTo(5, scroll + scrollLength - 5);
             }
             ctx.stroke();
         };
 
+        const renderContent = (
+            canvas: MotaOffscreenCanvas2D,
+            children: RenderItem[],
+            transform: Transform
+        ) => {
+            const ctx = canvas.ctx;
+            ctx.save();
+            if (direction.value === ScrollDirection.Horizontal) {
+                ctx.translate(-contentPos, 0);
+            } else {
+                ctx.translate(0, -contentPos);
+            }
+            children.forEach(v => {
+                if (v.hidden) return;
+                v.renderContent(canvas, transform);
+            });
+            ctx.restore();
+        };
+
+        //#endregion
+
+        //#region 事件监听
+
+        const wheelScroll = (delta: number, max: number) => {
+            const sign = Math.sign(delta);
+            const dx = Math.abs(delta);
+            const movement = Math.min(max, dx) * sign;
+            scrollTo(contentTarget + movement, dx > 10 ? 300 : 0);
+        };
+
         const wheel = (ev: IWheelEvent) => {
-            if (props.direction === ScrollDirection.Horizontal) {
+            if (direction.value === ScrollDirection.Horizontal) {
                 if (ev.wheelX !== 0) {
-                    scrollTo(nowScroll + ev.wheelX, 300);
+                    wheelScroll(ev.wheelX, width.value / 5);
                 } else if (ev.wheelY !== 0) {
-                    scrollTo(nowScroll + ev.wheelY, 300);
+                    wheelScroll(ev.wheelY, width.value / 5);
                 }
             } else {
-                scrollTo(nowScroll + ev.wheelY, 300);
+                wheelScroll(ev.wheelY, height.value / 5);
             }
         };
 
         const getPos = (ev: IActionEvent) => {
-            if (props.direction === ScrollDirection.Horizontal) {
+            if (direction.value === ScrollDirection.Horizontal) {
                 return ev.offsetX;
             } else {
                 return ev.offsetY;
             }
         };
 
-        let identifier: number = -1;
-        let lastPos: number = 0;
+        let identifier = -2;
+        let lastPos = 0;
+
         const down = (ev: IActionEvent) => {
             identifier = ev.identifier;
             lastPos = getPos(ev);
@@ -249,24 +350,32 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
             } else {
                 if (ev.buttons & MouseType.Left) {
                     pos = getPos(ev);
+                } else {
+                    return;
                 }
             }
             const movement = pos - lastPos;
-            scrollTo(nowScroll + movement, 1);
+
+            scrollTo(contentTarget - movement, 0);
             lastPos = pos;
         };
 
+        /** 最初滚动条在哪 */
         let scrollBefore = 0;
-        let scrollIdentifier = -1;
+        /** 本次拖动滚动条的操作标识符 */
+        let scrollIdentifier = -2;
+        /** 点击滚动条时,点击位置在平行于滚动条方向的位置 */
         let scrollDownPos = 0;
+        /** 是否是点击了滚动条区域中滚动条之外的地方,这样视为类滚轮操作 */
         let scrollMutate = false;
+        /** 点击滚动条时,点击位置垂直于滚动条方向的位置 */
         let scrollPin = 0;
 
         /**
          * 获取点击滚动条时,垂直于滚动条方向的位置
          */
         const getScrollPin = (ev: IActionEvent) => {
-            if (props.direction === ScrollDirection.Horizontal) {
+            if (direction.value === ScrollDirection.Horizontal) {
                 return ev.absoluteY;
             } else {
                 return ev.absoluteX;
@@ -274,13 +383,13 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
         };
 
         const downScroll = (ev: IActionEvent) => {
-            scrollBefore = nowScroll;
+            scrollBefore = contentTarget;
             scrollIdentifier = ev.identifier;
             const pos = getPos(ev);
             // 计算点击在了滚动条的哪个位置
-            const sEnd = nowScroll + scrollLength;
-            if (pos >= nowScroll && pos <= sEnd) {
-                scrollDownPos = pos - nowScroll;
+            const sEnd = contentTarget + scrollLength;
+            if (pos >= contentTarget && pos <= sEnd) {
+                scrollDownPos = pos - contentTarget;
                 scrollMutate = false;
                 scrollPin = getScrollPin(ev);
             } else {
@@ -304,22 +413,25 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
                 threshold = 100;
             }
             if (deltaPin > threshold) {
-                scrollTo(scrollBefore, 1);
+                scrollTo(scrollBefore, 0);
             } else {
-                scrollTo(scrollPos, 1);
+                const pos = (scrollPos / height.value) * maxLength;
+                scrollTo(pos, 0);
             }
         };
 
         const upScroll = (ev: IActionEvent) => {
             if (!scrollMutate) return;
             const pos = getPos(ev);
-            if (pos < nowScroll) {
+            if (pos < contentTarget) {
                 scrollTo(pos - 50);
             } else {
                 scrollTo(pos + 50);
             }
         };
 
+        //#endregion
+
         onMounted(() => {
             scroll.value?.root?.on('move', move);
             scroll.value?.root?.on('move', moveScroll);
@@ -328,16 +440,27 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
         onUnmounted(() => {
             scroll.value?.root?.off('move', move);
             scroll.value?.root?.off('move', moveScroll);
+            transition.ticker.destroy();
+        });
+
+        expose<ScrollExpose>({
+            scrollTo
         });
 
         return () => {
             return (
                 <container loc={props.loc} onWheel={wheel}>
-                    <container {...contentProps} ref={content} onDown={down}>
-                        {slots.default()}
-                    </container>
+                    <container-custom
+                        loc={props.loc}
+                        ref={content}
+                        onDown={down}
+                        render={renderContent}
+                    >
+                        {slots.default?.()}
+                    </container-custom>
                     <sprite
-                        {...scrollProps}
+                        nocache
+                        loc={sp.value}
                         ref={scroll}
                         render={drawScroll}
                         onDown={downScroll}
diff --git a/src/module/render/ui/statusBar.tsx b/src/module/render/ui/statusBar.tsx
index 3676062..760c896 100644
--- a/src/module/render/ui/statusBar.tsx
+++ b/src/module/render/ui/statusBar.tsx
@@ -2,6 +2,7 @@ import { GameUI } from '@/core/system';
 import { defineComponent } from 'vue';
 import { SetupComponentOptions } from '../components';
 import { ElementLocator } from '@/core/render';
+import { Scroll } from '../components/scroll';
 
 export interface ILeftHeroStatus {
     hp: number;
@@ -168,6 +169,12 @@ export const RightStatusBar = defineComponent<StatusBarProps<IRightHeroStatus>>(
             return (
                 <container loc={p.loc}>
                     <g-rect loc={[0, 0, p.loc[2], p.loc[3]]} stroke></g-rect>
+                    <Scroll loc={[0, 0, 180, 100]}>
+                        <text text="测试1" loc={[0, 0]}></text>
+                        <text text="测试2" loc={[0, 50]}></text>
+                        <text text="测试3" loc={[0, 100]}></text>
+                        <text text="测试4" loc={[0, 200]}></text>
+                    </Scroll>
                 </container>
             );
         };
diff --git a/vite.config.ts b/vite.config.ts
index df06f72..e0889d6 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -11,7 +11,8 @@ const FSHOST = 'http://127.0.0.1:3000/';
 
 const custom = [
     'container', 'image', 'sprite', 'shader', 'text', 'comment', 'custom', 
-    'layer', 'layer-group', 'animate', 'damage', 'graphics', 'icon', 'winskin'
+    'layer', 'layer-group', 'animate', 'damage', 'graphics', 'icon', 'winskin',
+    'container-custom'
 ]
 
 // https://vitejs.dev/config/