fix: 滚动条的相关问题 & feat: 自定义容器渲染

This commit is contained in:
unanmed 2025-02-22 14:58:21 +08:00
parent 6238c8f00a
commit 8bc2a00bd2
9 changed files with 314 additions and 110 deletions

View File

@ -1,3 +1,4 @@
import { ElementNamespace, ComponentInternalInstance } from 'vue';
import { MotaOffscreenCanvas2D } from '../fx/canvas2d'; import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { ActionType, EventProgress, ActionEventMap } from './event'; import { ActionType, EventProgress, ActionEventMap } from './event';
import { import {
@ -14,7 +15,6 @@ export class Container<E extends EContainerEvent = EContainerEvent>
extends RenderItem<E | EContainerEvent> extends RenderItem<E | EContainerEvent>
implements IRenderChildable implements IRenderChildable
{ {
children: Set<RenderItem> = new Set();
sortedChildren: RenderItem[] = []; sortedChildren: RenderItem[] = [];
private needSort: boolean = false; private needSort: boolean = false;
@ -133,7 +133,53 @@ export class Container<E extends EContainerEvent = EContainerEvent>
destroy(): void { destroy(): void {
super.destroy(); super.destroy();
this.children.forEach(v => { 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);
}
}

View File

@ -362,6 +362,11 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
this._transform.bind(this); this._transform.bind(this);
this.cache = this.requireCanvas(); this.cache = this.requireCanvas();
this.cache.withGameScale(true); 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 { size(width: number, height: number): void {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.cache.size(width, height); if (this.enableCache) {
this.cache.size(width, height);
}
this.update(this); this.update(this);
} }
@ -480,13 +487,17 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
setHD(hd: boolean): void { setHD(hd: boolean): void {
this.highResolution = hd; this.highResolution = hd;
this.cache.setHD(hd); if (this.enableCache) {
this.cache.setHD(hd);
}
this.update(this); this.update(this);
} }
setAntiAliasing(anti: boolean): void { setAntiAliasing(anti: boolean): void {
this.antiAliasing = anti; this.antiAliasing = anti;
this.cache.setAntiAliasing(anti); if (this.enableCache) {
this.cache.setAntiAliasing(anti);
}
this.update(this); this.update(this);
} }
@ -540,7 +551,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
} }
/** /**
* *
*/ */
getBoundingRect(): DOMRectReadOnly { getBoundingRect(): DOMRectReadOnly {
if (this.type === 'absolute') { if (this.type === 'absolute') {

View File

@ -13,6 +13,7 @@ import {
BezierProps, BezierProps,
CirclesProps, CirclesProps,
CommentProps, CommentProps,
ConatinerCustomProps,
ContainerProps, ContainerProps,
CustomProps, CustomProps,
DamageProps, DamageProps,
@ -84,6 +85,10 @@ declare module 'vue/jsx-runtime' {
export interface IntrinsicElements { export interface IntrinsicElements {
sprite: TagDefine<SpriteProps, ESpriteEvent>; sprite: TagDefine<SpriteProps, ESpriteEvent>;
container: TagDefine<ContainerProps, EContainerEvent>; container: TagDefine<ContainerProps, EContainerEvent>;
'container-custom': TagDefine<
ConatinerCustomProps,
EContainerEvent
>;
shader: TagDefine<ShaderProps, EShaderEvent>; shader: TagDefine<ShaderProps, EShaderEvent>;
text: TagDefine<TextProps, ETextEvent>; text: TagDefine<TextProps, ETextEvent>;
image: TagDefine<ImageProps, EImageEvent>; image: TagDefine<ImageProps, EImageEvent>;

View File

@ -1,7 +1,7 @@
import { logger } from '@/core/common/logger'; import { logger } from '@/core/common/logger';
import { ERenderItemEvent, RenderItem, RenderItemPosition } from '../item'; import { ERenderItemEvent, RenderItem, RenderItemPosition } from '../item';
import { ElementNamespace, VNodeProps } from 'vue'; import { ElementNamespace, VNodeProps } from 'vue';
import { Container } from '../container'; import { Container, ContainerCustom } from '../container';
import { MotaRenderer } from '../render'; import { MotaRenderer } from '../render';
import { Sprite } from '../sprite'; import { Sprite } from '../sprite';
import { import {
@ -75,8 +75,13 @@ const standardElement = (
return (_0: any, _1: any, props?: any) => { return (_0: any, _1: any, props?: any) => {
if (!props) return new Item('static'); if (!props) return new Item('static');
else { else {
const { type = 'static', cache = true, fall = false } = props; const {
return new Item(type, cache, fall); 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) => { return (_0: any, _1: any, props?: any) => {
if (!props) return new Item('static'); if (!props) return new Item('static');
else { else {
const { type = 'static', cache = false, fall = false } = props; const {
return new Item(type, cache, fall); type = 'static',
cache = false,
fall = false,
nocache = true
} = props;
return new Item(type, cache && !nocache, fall);
} }
}; };
}; };
@ -124,15 +134,17 @@ const se = (
const { const {
type = position, type = position,
cache = defaultCache, cache = defaultCache,
fall = defautFall fall = defautFall,
nocache = !defaultCache
} = props; } = props;
return new Item(type, cache, fall); return new Item(type, cache && !nocache, fall);
} }
}; };
}; };
// Default elements // Default elements
tagMap.register('container', standardElement(Container)); tagMap.register('container', standardElement(Container));
tagMap.register('container-custom', standardElement(ContainerCustom));
tagMap.register('template', standardElement(Container)); tagMap.register('template', standardElement(Container));
tagMap.register('mota-renderer', (_0, _1, props) => { tagMap.register('mota-renderer', (_0, _1, props) => {
return new MotaRenderer(props?.id); return new MotaRenderer(props?.id);

View File

@ -8,6 +8,7 @@ import {
import type { EnemyCollection } from '@/game/enemy/damage'; import type { EnemyCollection } from '@/game/enemy/damage';
import { ILineProperty } from '../preset/graphics'; import { ILineProperty } from '../preset/graphics';
import { ElementAnchor, ElementLocator } from '../utils'; import { ElementAnchor, ElementLocator } from '../utils';
import { CustomContainerRenderFn } from '../container';
export interface CustomProps { export interface CustomProps {
_item: (props: BaseProps) => RenderItem; _item: (props: BaseProps) => RenderItem;
@ -27,7 +28,10 @@ export interface BaseProps {
hidden?: boolean; hidden?: boolean;
transform?: Transform; transform?: Transform;
type?: RenderItemPosition; type?: RenderItemPosition;
/** 是否启用缓存,用处较少,主要用于一些默认不启用缓存的元素的特殊优化 */
cache?: boolean; cache?: boolean;
/** 是否不启用缓存,优先级大于 cache用处较少主要用于一些特殊优化 */
nocache?: boolean;
fall?: boolean; fall?: boolean;
id?: string; id?: string;
alpha?: number; alpha?: number;
@ -49,6 +53,10 @@ export interface SpriteProps extends BaseProps {
export interface ContainerProps extends BaseProps {} export interface ContainerProps extends BaseProps {}
export interface ConatinerCustomProps extends ContainerProps {
render?: CustomContainerRenderFn;
}
export interface GL2Props extends BaseProps {} export interface GL2Props extends BaseProps {}
export interface ShaderProps extends BaseProps {} export interface ShaderProps extends BaseProps {}

View File

@ -7,7 +7,6 @@ import {
import { MotaOffscreenCanvas2D } from '../fx/canvas2d'; import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { Transform } from './transform'; import { Transform } from './transform';
import { ElementNamespace, ComponentInternalInstance } from 'vue'; import { ElementNamespace, ComponentInternalInstance } from 'vue';
import { ActionType, EventProgress, ActionEventMap } from './event';
export interface ESpriteEvent extends ERenderItemEvent {} export interface ESpriteEvent extends ERenderItemEvent {}
@ -43,14 +42,6 @@ export class Sprite<
this.update(this); this.update(this);
} }
protected propagateEvent<T extends ActionType>(
type: T,
_progress: EventProgress,
event: ActionEventMap[T]
): void {
this.parent?.bubbleEvent(type, event);
}
patchProp( patchProp(
key: string, key: string,
prevValue: any, prevValue: any,

View File

@ -1,10 +1,10 @@
import { import {
computed, computed,
defineComponent, defineComponent,
nextTick,
onMounted, onMounted,
onUnmounted, onUnmounted,
onUpdated, onUpdated,
reactive,
ref, ref,
SlotsType, SlotsType,
VNode, VNode,
@ -13,11 +13,10 @@ import {
import { SetupComponentOptions } from './types'; import { SetupComponentOptions } from './types';
import { import {
Container, Container,
ContainerProps,
ElementLocator, ElementLocator,
RenderItem, RenderItem,
Sprite, Sprite,
SpriteProps Transform
} from '@/core/render'; } from '@/core/render';
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d'; import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { hyper, Transition } from 'mutate-animate'; import { hyper, Transition } from 'mutate-animate';
@ -29,9 +28,18 @@ export const enum ScrollDirection {
Vertical Vertical
} }
interface ScrollProps { export interface ScrollExpose {
direction: ScrollDirection; /**
*
* @param y
* @param time
*/
scrollTo(y: number, time?: number): void;
}
export interface ScrollProps {
loc: ElementLocator; loc: ElementLocator;
hor?: boolean;
noscroll?: boolean; noscroll?: boolean;
/** /**
* *
@ -45,22 +53,47 @@ type ScrollSlots = SlotsType<{
}>; }>;
const scrollProps = { const scrollProps = {
props: ['direction', 'noscroll'] props: ['hor', 'noscroll', 'loc', 'padHeight']
} satisfies SetupComponentOptions<ScrollProps, {}, string, ScrollSlots>; } satisfies SetupComponentOptions<ScrollProps, {}, string, ScrollSlots>;
/** 滚动条图示的最短长度 */ /** 滚动条图示的最短长度 */
const SCROLL_MIN_LENGTH = 20; const SCROLL_MIN_LENGTH = 20;
/** 滚动条图示的宽度 */ /** 滚动条图示的宽度 */
const SCROLL_WIDTH = 10; 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>( export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
(props, { slots }) => { (props, { slots, expose }) => {
const scrollProps: SpriteProps = reactive({ /** 滚动条的定位 */
loc: [0, 0, 0, 0] const sp = ref<ElementLocator>([0, 0, 1, 1]);
});
const contentProps: ContainerProps = reactive({
loc: [0, 0, 0, 0]
});
const listenedChild: Set<RenderItem> = new Set(); const listenedChild: Set<RenderItem> = new Set();
const areaMap: Map<RenderItem, [number, number]> = new Map(); 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 width = computed(() => props.loc[2] ?? 200);
const height = computed(() => props.loc[3] ?? 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 maxLength = 0;
/** 滚动条的长度 */
let scrollLength = SCROLL_MIN_LENGTH; let scrollLength = SCROLL_MIN_LENGTH;
const transition = new Transition(); const transition = new Transition();
transition.value.scroll = 0; transition.value.scroll = 0;
transition.value.showScroll = 0;
transition.mode(hyper('sin', 'out')).absolute(); transition.mode(hyper('sin', 'out')).absolute();
//#region 滚动操作
transition.ticker.add(() => { transition.ticker.add(() => {
if (transition.value.scroll !== nowScroll) { if (scrollPos !== scrollTarget) {
showScroll = transition.value.scroll; scrollPos = transition.value.scroll;
scroll.value?.update(); content.value?.update();
}
if (contentPos !== contentTarget) {
contentPos = transition.value.showScroll;
checkAllItem();
updatePosition();
content.value?.update(); 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 * @param time
*/ */
const scrollTo = (y: number, time: number = 1) => { const scrollTo = (y: number, time: number = 0) => {
const target = clamp(y, 0, maxLength); if (maxLength < height.value) return;
transition.time(time).transition('scroll', target); const max = maxLength - height.value;
nowScroll = y; 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) => { 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]); areaMap.set(item, [rect.left - width.value, rect.right]);
} else { } else {
areaMap.set(item, [rect.top - height.value, rect.bottom]); areaMap.set(item, [rect.top - height.value, rect.bottom]);
@ -131,13 +175,20 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
return; return;
} }
const [min, max] = area; const [min, max] = area;
if (nowScroll > min - 10 && nowScroll < max + 10) { if (contentPos > min - 10 && contentPos < max + 10) {
item.show(); item.show();
} else { } else {
item.hide(); item.hide();
} }
}; };
/**
*
*/
const checkAllItem = () => {
content.value?.children.forEach(v => checkItem(v));
};
/** /**
* *
*/ */
@ -147,15 +198,46 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
checkItem(item); 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 = () => { const updateScroll = () => {
if (!content.value) return; if (!content.value || updating) return;
updating = true;
nextTick(() => {
updating = false;
});
let max = 0; let max = 0;
listenedChild.forEach(v => v.off('transform', onTransform)); listenedChild.forEach(v => v.off('transform', onTransform));
listenedChild.clear(); listenedChild.clear();
areaMap.clear(); areaMap.clear();
content.value.children.forEach(v => { content.value.children.forEach(v => {
const rect = v.getBoundingRect(); const rect = v.getBoundingRect();
if (props.direction === ScrollDirection.Horizontal) { if (direction.value === ScrollDirection.Horizontal) {
if (rect.right > max) { if (rect.right > max) {
max = rect.right; max = rect.right;
} }
@ -166,76 +248,95 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
} }
v.on('transform', onTransform); v.on('transform', onTransform);
listenedChild.add(v); listenedChild.add(v);
checkItem(v);
}); });
maxLength = max + (props.padHeight ?? 0); maxLength = max + (props.padHeight ?? 0);
if (props.direction === ScrollDirection.Horizontal) { updatePosition();
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];
}
scroll.value?.update(); scroll.value?.update();
}; };
watch(() => props.loc, updateScroll);
onUpdated(updateScroll); onUpdated(updateScroll);
onMounted(updateScroll); onMounted(updateScroll);
onUnmounted(() => { onUnmounted(() => {
listenedChild.forEach(v => v.off('transform', onTransform)); listenedChild.forEach(v => v.off('transform', onTransform));
}); });
//#endregion
//#region 渲染滚动
const drawScroll = (canvas: MotaOffscreenCanvas2D) => { const drawScroll = (canvas: MotaOffscreenCanvas2D) => {
if (props.noscroll) return; if (props.noscroll) return;
const ctx = canvas.ctx; const ctx = canvas.ctx;
ctx.lineCap = 'round'; ctx.lineCap = 'round';
ctx.lineWidth = 6; ctx.lineWidth = 3;
ctx.strokeStyle = '#fff'; ctx.strokeStyle = SCROLL_COLOR;
ctx.beginPath(); ctx.beginPath();
if (props.direction === ScrollDirection.Horizontal) { const scroll = transition.value.scroll;
ctx.moveTo(nowScroll + 5, 5); if (direction.value === ScrollDirection.Horizontal) {
ctx.lineTo(nowScroll + scrollLength + 5, 5); ctx.moveTo(scroll + 5, 5);
ctx.lineTo(scroll + scrollLength - 5, 5);
} else { } else {
ctx.moveTo(5, nowScroll + 5); ctx.moveTo(5, scroll + 5);
ctx.lineTo(5, nowScroll + scrollLength + 5); ctx.lineTo(5, scroll + scrollLength - 5);
} }
ctx.stroke(); 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) => { const wheel = (ev: IWheelEvent) => {
if (props.direction === ScrollDirection.Horizontal) { if (direction.value === ScrollDirection.Horizontal) {
if (ev.wheelX !== 0) { if (ev.wheelX !== 0) {
scrollTo(nowScroll + ev.wheelX, 300); wheelScroll(ev.wheelX, width.value / 5);
} else if (ev.wheelY !== 0) { } else if (ev.wheelY !== 0) {
scrollTo(nowScroll + ev.wheelY, 300); wheelScroll(ev.wheelY, width.value / 5);
} }
} else { } else {
scrollTo(nowScroll + ev.wheelY, 300); wheelScroll(ev.wheelY, height.value / 5);
} }
}; };
const getPos = (ev: IActionEvent) => { const getPos = (ev: IActionEvent) => {
if (props.direction === ScrollDirection.Horizontal) { if (direction.value === ScrollDirection.Horizontal) {
return ev.offsetX; return ev.offsetX;
} else { } else {
return ev.offsetY; return ev.offsetY;
} }
}; };
let identifier: number = -1; let identifier = -2;
let lastPos: number = 0; let lastPos = 0;
const down = (ev: IActionEvent) => { const down = (ev: IActionEvent) => {
identifier = ev.identifier; identifier = ev.identifier;
lastPos = getPos(ev); lastPos = getPos(ev);
@ -249,24 +350,32 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
} else { } else {
if (ev.buttons & MouseType.Left) { if (ev.buttons & MouseType.Left) {
pos = getPos(ev); pos = getPos(ev);
} else {
return;
} }
} }
const movement = pos - lastPos; const movement = pos - lastPos;
scrollTo(nowScroll + movement, 1);
scrollTo(contentTarget - movement, 0);
lastPos = pos; lastPos = pos;
}; };
/** 最初滚动条在哪 */
let scrollBefore = 0; let scrollBefore = 0;
let scrollIdentifier = -1; /** 本次拖动滚动条的操作标识符 */
let scrollIdentifier = -2;
/** 点击滚动条时,点击位置在平行于滚动条方向的位置 */
let scrollDownPos = 0; let scrollDownPos = 0;
/** 是否是点击了滚动条区域中滚动条之外的地方,这样视为类滚轮操作 */
let scrollMutate = false; let scrollMutate = false;
/** 点击滚动条时,点击位置垂直于滚动条方向的位置 */
let scrollPin = 0; let scrollPin = 0;
/** /**
* *
*/ */
const getScrollPin = (ev: IActionEvent) => { const getScrollPin = (ev: IActionEvent) => {
if (props.direction === ScrollDirection.Horizontal) { if (direction.value === ScrollDirection.Horizontal) {
return ev.absoluteY; return ev.absoluteY;
} else { } else {
return ev.absoluteX; return ev.absoluteX;
@ -274,13 +383,13 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
}; };
const downScroll = (ev: IActionEvent) => { const downScroll = (ev: IActionEvent) => {
scrollBefore = nowScroll; scrollBefore = contentTarget;
scrollIdentifier = ev.identifier; scrollIdentifier = ev.identifier;
const pos = getPos(ev); const pos = getPos(ev);
// 计算点击在了滚动条的哪个位置 // 计算点击在了滚动条的哪个位置
const sEnd = nowScroll + scrollLength; const sEnd = contentTarget + scrollLength;
if (pos >= nowScroll && pos <= sEnd) { if (pos >= contentTarget && pos <= sEnd) {
scrollDownPos = pos - nowScroll; scrollDownPos = pos - contentTarget;
scrollMutate = false; scrollMutate = false;
scrollPin = getScrollPin(ev); scrollPin = getScrollPin(ev);
} else { } else {
@ -304,22 +413,25 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
threshold = 100; threshold = 100;
} }
if (deltaPin > threshold) { if (deltaPin > threshold) {
scrollTo(scrollBefore, 1); scrollTo(scrollBefore, 0);
} else { } else {
scrollTo(scrollPos, 1); const pos = (scrollPos / height.value) * maxLength;
scrollTo(pos, 0);
} }
}; };
const upScroll = (ev: IActionEvent) => { const upScroll = (ev: IActionEvent) => {
if (!scrollMutate) return; if (!scrollMutate) return;
const pos = getPos(ev); const pos = getPos(ev);
if (pos < nowScroll) { if (pos < contentTarget) {
scrollTo(pos - 50); scrollTo(pos - 50);
} else { } else {
scrollTo(pos + 50); scrollTo(pos + 50);
} }
}; };
//#endregion
onMounted(() => { onMounted(() => {
scroll.value?.root?.on('move', move); scroll.value?.root?.on('move', move);
scroll.value?.root?.on('move', moveScroll); scroll.value?.root?.on('move', moveScroll);
@ -328,16 +440,27 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
onUnmounted(() => { onUnmounted(() => {
scroll.value?.root?.off('move', move); scroll.value?.root?.off('move', move);
scroll.value?.root?.off('move', moveScroll); scroll.value?.root?.off('move', moveScroll);
transition.ticker.destroy();
});
expose<ScrollExpose>({
scrollTo
}); });
return () => { return () => {
return ( return (
<container loc={props.loc} onWheel={wheel}> <container loc={props.loc} onWheel={wheel}>
<container {...contentProps} ref={content} onDown={down}> <container-custom
{slots.default()} loc={props.loc}
</container> ref={content}
onDown={down}
render={renderContent}
>
{slots.default?.()}
</container-custom>
<sprite <sprite
{...scrollProps} nocache
loc={sp.value}
ref={scroll} ref={scroll}
render={drawScroll} render={drawScroll}
onDown={downScroll} onDown={downScroll}

View File

@ -2,6 +2,7 @@ import { GameUI } from '@/core/system';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { SetupComponentOptions } from '../components'; import { SetupComponentOptions } from '../components';
import { ElementLocator } from '@/core/render'; import { ElementLocator } from '@/core/render';
import { Scroll } from '../components/scroll';
export interface ILeftHeroStatus { export interface ILeftHeroStatus {
hp: number; hp: number;
@ -168,6 +169,12 @@ export const RightStatusBar = defineComponent<StatusBarProps<IRightHeroStatus>>(
return ( return (
<container loc={p.loc}> <container loc={p.loc}>
<g-rect loc={[0, 0, p.loc[2], p.loc[3]]} stroke></g-rect> <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> </container>
); );
}; };

View File

@ -11,7 +11,8 @@ const FSHOST = 'http://127.0.0.1:3000/';
const custom = [ const custom = [
'container', 'image', 'sprite', 'shader', 'text', 'comment', '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/ // https://vitejs.dev/config/