mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-19 17:16:08 +08:00
feat: Scroll 组件
This commit is contained in:
parent
ac5eefdb02
commit
6238c8f00a
@ -2,7 +2,7 @@ import { isNil } from 'lodash-es';
|
|||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||||
import { Ticker, TickerFn } from 'mutate-animate';
|
import { Ticker, TickerFn } from 'mutate-animate';
|
||||||
import { Transform } from './transform';
|
import { ITransformUpdatable, Transform } from './transform';
|
||||||
import { logger } from '../common/logger';
|
import { logger } from '../common/logger';
|
||||||
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
||||||
import { transformCanvas } from './utils';
|
import { transformCanvas } from './utils';
|
||||||
@ -189,6 +189,7 @@ export interface ERenderItemEvent extends ERenderItemActionEvent {
|
|||||||
beforeRender: [transform: Transform];
|
beforeRender: [transform: Transform];
|
||||||
afterRender: [transform: Transform];
|
afterRender: [transform: Transform];
|
||||||
destroy: [];
|
destroy: [];
|
||||||
|
transform: [item: RenderItem, transform: Transform];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TickerDelegation {
|
interface TickerDelegation {
|
||||||
@ -211,7 +212,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
IRenderFrame,
|
IRenderFrame,
|
||||||
IRenderTickerSupport,
|
IRenderTickerSupport,
|
||||||
IRenderChildable,
|
IRenderChildable,
|
||||||
IRenderVueSupport
|
IRenderVueSupport,
|
||||||
|
ITransformUpdatable
|
||||||
{
|
{
|
||||||
/** 渲染的全局ticker */
|
/** 渲染的全局ticker */
|
||||||
static ticker: Ticker = new Ticker();
|
static ticker: Ticker = new Ticker();
|
||||||
@ -519,6 +521,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
//#region 功能方法
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前元素的绝对位置(不建议使用,因为应当很少会有获取绝对位置的需求)
|
* 获取当前元素的绝对位置(不建议使用,因为应当很少会有获取绝对位置的需求)
|
||||||
*/
|
*/
|
||||||
@ -535,6 +539,28 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取到可以包围这个元素的最小矩形
|
||||||
|
*/
|
||||||
|
getBoundingRect(): DOMRectReadOnly {
|
||||||
|
if (this.type === 'absolute') {
|
||||||
|
return new DOMRectReadOnly(0, 0, this.width, this.height);
|
||||||
|
}
|
||||||
|
const tran = this.transformFallThrough
|
||||||
|
? this.fallTransform
|
||||||
|
: this._transform;
|
||||||
|
if (!tran) return new DOMRectReadOnly(0, 0, this.width, this.height);
|
||||||
|
const [x1, y1] = tran.transformed(0, 0);
|
||||||
|
const [x2, y2] = tran.transformed(this.width, 0);
|
||||||
|
const [x3, y3] = tran.transformed(0, this.height);
|
||||||
|
const [x4, y4] = tran.transformed(this.width, this.height);
|
||||||
|
const left = Math.min(x1, x2, x3, x4);
|
||||||
|
const right = Math.max(x1, x2, x3, x4);
|
||||||
|
const top = Math.min(y1, y2, y3, y4);
|
||||||
|
const bottom = Math.max(y1, y2, y3, y4);
|
||||||
|
return new DOMRectReadOnly(left, top, right - left, bottom - top);
|
||||||
|
}
|
||||||
|
|
||||||
update(item: RenderItem<any> = this): void {
|
update(item: RenderItem<any> = this): void {
|
||||||
if (this.cacheDirty) return;
|
if (this.cacheDirty) return;
|
||||||
this.cacheDirty = true;
|
this.cacheDirty = true;
|
||||||
@ -542,6 +568,13 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
this.parent?.update(item);
|
this.parent?.update(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateTransform() {
|
||||||
|
this.update();
|
||||||
|
this.emit('transform', this, this._transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
//#region 动画帧与 ticker
|
//#region 动画帧与 ticker
|
||||||
|
|
||||||
requestBeforeFrame(fn: () => void): void {
|
requestBeforeFrame(fn: () => void): void {
|
||||||
@ -639,6 +672,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
this.checkRoot();
|
this.checkRoot();
|
||||||
this._root?.connect(this);
|
this._root?.connect(this);
|
||||||
this.canvases.forEach(v => v.activate());
|
this.canvases.forEach(v => v.activate());
|
||||||
|
this._transform.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -653,6 +687,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
parent.requestSort();
|
parent.requestSort();
|
||||||
parent.update();
|
parent.update();
|
||||||
this.canvases.forEach(v => v.deactivate());
|
this.canvases.forEach(v => v.deactivate());
|
||||||
|
this._transform.bind();
|
||||||
if (!success) return false;
|
if (!success) return false;
|
||||||
this._root?.disconnect(this);
|
this._root?.disconnect(this);
|
||||||
this._root = void 0;
|
this._root = void 0;
|
||||||
@ -1115,6 +1150,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
this.emit('destroy');
|
this.emit('destroy');
|
||||||
this.removeAllListeners();
|
this.removeAllListeners();
|
||||||
this.cache.delete();
|
this.cache.delete();
|
||||||
|
this.canvases.forEach(v => v.delete());
|
||||||
|
this.canvases.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,6 @@ export const { createApp, render } = createRenderer<RenderItem, RenderItem>({
|
|||||||
|
|
||||||
remove: function (el: RenderItem<ERenderItemEvent>): void {
|
remove: function (el: RenderItem<ERenderItemEvent>): void {
|
||||||
el.remove();
|
el.remove();
|
||||||
el.destroy();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createElement: function (
|
createElement: function (
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { gameKey, Hotkey } from '@/core/main/custom/hotkey';
|
import { gameKey, Hotkey } from '@/core/main/custom/hotkey';
|
||||||
import { Animation, Ticker, Transition } from 'mutate-animate';
|
import { Animation, Ticker, Transition } from 'mutate-animate';
|
||||||
import { onMounted, onUnmounted } from 'vue';
|
import { onMounted, onUnmounted } from 'vue';
|
||||||
|
import { ERenderItemEvent, RenderItem } from '../item';
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
|
||||||
const ticker = new Ticker();
|
const ticker = new Ticker();
|
||||||
|
|
||||||
@ -60,3 +62,13 @@ export function useKey(noScope: boolean = false): KeyUsing {
|
|||||||
return [gameKey, sym];
|
return [gameKey, sym];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function onEvent<
|
||||||
|
T extends ERenderItemEvent,
|
||||||
|
K extends EventEmitter.EventNames<T>
|
||||||
|
>(item: RenderItem<T>, key: K, listener: EventEmitter.EventListener<T, K>) {
|
||||||
|
item.on(key, listener);
|
||||||
|
onUnmounted(() => {
|
||||||
|
item.off(key, listener);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { mat3, ReadonlyMat3, ReadonlyVec3, vec2, vec3 } from 'gl-matrix';
|
import { mat3, ReadonlyMat3, ReadonlyVec3, vec2, vec3 } from 'gl-matrix';
|
||||||
|
|
||||||
export interface ITransformUpdatable {
|
export interface ITransformUpdatable {
|
||||||
update(): void;
|
updateTransform?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Transform {
|
export class Transform {
|
||||||
@ -48,7 +48,7 @@ export class Transform {
|
|||||||
this.scaleX *= x;
|
this.scaleX *= x;
|
||||||
this.scaleY *= y;
|
this.scaleY *= y;
|
||||||
this.modified = true;
|
this.modified = true;
|
||||||
this.bindedObject?.update();
|
this.bindedObject?.updateTransform?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,7 +59,7 @@ export class Transform {
|
|||||||
this.x += x;
|
this.x += x;
|
||||||
this.y += y;
|
this.y += y;
|
||||||
this.modified = true;
|
this.modified = true;
|
||||||
this.bindedObject?.update();
|
this.bindedObject?.updateTransform?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,7 +73,7 @@ export class Transform {
|
|||||||
this.rad -= n * Math.PI * 2;
|
this.rad -= n * Math.PI * 2;
|
||||||
}
|
}
|
||||||
this.modified = true;
|
this.modified = true;
|
||||||
this.bindedObject?.update();
|
this.bindedObject?.updateTransform?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,7 +84,7 @@ export class Transform {
|
|||||||
this.scaleX = x;
|
this.scaleX = x;
|
||||||
this.scaleY = y;
|
this.scaleY = y;
|
||||||
this.modified = true;
|
this.modified = true;
|
||||||
this.bindedObject?.update();
|
this.bindedObject?.updateTransform?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,7 +95,7 @@ export class Transform {
|
|||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.modified = true;
|
this.modified = true;
|
||||||
this.bindedObject?.update();
|
this.bindedObject?.updateTransform?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,7 +105,7 @@ export class Transform {
|
|||||||
mat3.rotate(this.mat, this.mat, rad - this.rad);
|
mat3.rotate(this.mat, this.mat, rad - this.rad);
|
||||||
this.rad = rad;
|
this.rad = rad;
|
||||||
this.modified = true;
|
this.modified = true;
|
||||||
this.bindedObject?.update();
|
this.bindedObject?.updateTransform?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,7 +131,7 @@ export class Transform {
|
|||||||
mat3.fromValues(a, b, 0, c, d, 0, e, f, 1)
|
mat3.fromValues(a, b, 0, c, d, 0, e, f, 1)
|
||||||
);
|
);
|
||||||
this.calAttributes();
|
this.calAttributes();
|
||||||
this.bindedObject?.update();
|
this.bindedObject?.updateTransform?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -153,7 +153,7 @@ export class Transform {
|
|||||||
) {
|
) {
|
||||||
mat3.set(this.mat, a, b, 0, c, d, 0, e, f, 1);
|
mat3.set(this.mat, a, b, 0, c, d, 0, e, f, 1);
|
||||||
this.calAttributes();
|
this.calAttributes();
|
||||||
this.bindedObject?.update();
|
this.bindedObject?.updateTransform?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ElementLocator, Sprite } from '@/core/render';
|
import { ElementLocator, Sprite } from '@/core/render';
|
||||||
import { defineComponent, onMounted, ref, watch } from 'vue';
|
import { defineComponent, ref, watch } from 'vue';
|
||||||
import { SetupComponentOptions } from './types';
|
import { SetupComponentOptions } from './types';
|
||||||
|
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||||
|
|
||||||
interface ProgressProps {
|
interface ProgressProps {
|
||||||
/** 进度条的位置 */
|
/** 进度条的位置 */
|
||||||
@ -20,8 +21,7 @@ const progressProps = {
|
|||||||
export const Progress = defineComponent<ProgressProps>(props => {
|
export const Progress = defineComponent<ProgressProps>(props => {
|
||||||
const element = ref<Sprite>();
|
const element = ref<Sprite>();
|
||||||
|
|
||||||
onMounted(() => {
|
const render = (canvas: MotaOffscreenCanvas2D) => {
|
||||||
element.value?.setRenderFn(canvas => {
|
|
||||||
const { ctx } = canvas;
|
const { ctx } = canvas;
|
||||||
const width = props.loc[2] ?? 200;
|
const width = props.loc[2] ?? 200;
|
||||||
const height = props.loc[3] ?? 200;
|
const height = props.loc[3] ?? 200;
|
||||||
@ -29,14 +29,13 @@ export const Progress = defineComponent<ProgressProps>(props => {
|
|||||||
ctx.fillRect(0, 0, width, height);
|
ctx.fillRect(0, 0, width, height);
|
||||||
ctx.fillStyle = props.success ?? 'green';
|
ctx.fillStyle = props.success ?? 'green';
|
||||||
ctx.fillRect(0, 0, width * props.progress, height);
|
ctx.fillRect(0, 0, width * props.progress, height);
|
||||||
});
|
};
|
||||||
});
|
|
||||||
|
|
||||||
watch(props, () => {
|
watch(props, () => {
|
||||||
element.value?.update();
|
element.value?.update();
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
return <sprite ref={element} loc={props.loc}></sprite>;
|
return <sprite ref={element} loc={props.loc} render={render}></sprite>;
|
||||||
};
|
};
|
||||||
}, progressProps);
|
}, progressProps);
|
||||||
|
351
src/module/render/components/scroll.tsx
Normal file
351
src/module/render/components/scroll.tsx
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
import {
|
||||||
|
computed,
|
||||||
|
defineComponent,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
onUpdated,
|
||||||
|
reactive,
|
||||||
|
ref,
|
||||||
|
SlotsType,
|
||||||
|
VNode,
|
||||||
|
watch
|
||||||
|
} from 'vue';
|
||||||
|
import { SetupComponentOptions } from './types';
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
ContainerProps,
|
||||||
|
ElementLocator,
|
||||||
|
RenderItem,
|
||||||
|
Sprite,
|
||||||
|
SpriteProps
|
||||||
|
} from '@/core/render';
|
||||||
|
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||||
|
import { hyper, Transition } from 'mutate-animate';
|
||||||
|
import { clamp } from 'lodash-es';
|
||||||
|
import { IActionEvent, IWheelEvent, MouseType } from '@/core/render/event';
|
||||||
|
|
||||||
|
export const enum ScrollDirection {
|
||||||
|
Horizontal,
|
||||||
|
Vertical
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ScrollProps {
|
||||||
|
direction: ScrollDirection;
|
||||||
|
loc: ElementLocator;
|
||||||
|
noscroll?: boolean;
|
||||||
|
/**
|
||||||
|
* 滚动到最下方(最右方)时的填充大小,如果默认的高度计算方式有误,
|
||||||
|
* 那么可以调整此参数来修复错误
|
||||||
|
*/
|
||||||
|
padHeight?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScrollSlots = SlotsType<{
|
||||||
|
default: () => VNode | VNode[];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const scrollProps = {
|
||||||
|
props: ['direction', 'noscroll']
|
||||||
|
} satisfies SetupComponentOptions<ScrollProps, {}, string, ScrollSlots>;
|
||||||
|
|
||||||
|
/** 滚动条图示的最短长度 */
|
||||||
|
const SCROLL_MIN_LENGTH = 20;
|
||||||
|
/** 滚动条图示的宽度 */
|
||||||
|
const SCROLL_WIDTH = 10;
|
||||||
|
|
||||||
|
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]
|
||||||
|
});
|
||||||
|
|
||||||
|
const listenedChild: Set<RenderItem> = new Set();
|
||||||
|
const areaMap: Map<RenderItem, [number, number]> = new Map();
|
||||||
|
const content = ref<Container>();
|
||||||
|
const scroll = ref<Sprite>();
|
||||||
|
|
||||||
|
const width = computed(() => props.loc[2] ?? 200);
|
||||||
|
const height = computed(() => props.loc[3] ?? 200);
|
||||||
|
|
||||||
|
let showScroll = 0;
|
||||||
|
let nowScroll = 0;
|
||||||
|
let maxLength = 0;
|
||||||
|
let scrollLength = SCROLL_MIN_LENGTH;
|
||||||
|
|
||||||
|
const transition = new Transition();
|
||||||
|
transition.value.scroll = 0;
|
||||||
|
transition.mode(hyper('sin', 'out')).absolute();
|
||||||
|
|
||||||
|
transition.ticker.add(() => {
|
||||||
|
if (transition.value.scroll !== nowScroll) {
|
||||||
|
showScroll = transition.value.scroll;
|
||||||
|
scroll.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 动画时长
|
||||||
|
*/
|
||||||
|
const scrollTo = (y: number, time: number = 1) => {
|
||||||
|
const target = clamp(y, 0, maxLength);
|
||||||
|
transition.time(time).transition('scroll', target);
|
||||||
|
nowScroll = y;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算一个元素会在画面上显示的区域
|
||||||
|
*/
|
||||||
|
const getArea = (item: RenderItem, rect: DOMRectReadOnly) => {
|
||||||
|
if (props.direction === ScrollDirection.Horizontal) {
|
||||||
|
areaMap.set(item, [rect.left - width.value, rect.right]);
|
||||||
|
} else {
|
||||||
|
areaMap.set(item, [rect.top - height.value, rect.bottom]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查一个元素是否需要显示,不需要则隐藏
|
||||||
|
*/
|
||||||
|
const checkItem = (item: RenderItem) => {
|
||||||
|
const area = areaMap.get(item);
|
||||||
|
if (!area) {
|
||||||
|
item.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [min, max] = area;
|
||||||
|
if (nowScroll > min - 10 && nowScroll < max + 10) {
|
||||||
|
item.show();
|
||||||
|
} else {
|
||||||
|
item.hide();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当一个元素的矩阵发生变换时执行,检查其显示区域
|
||||||
|
*/
|
||||||
|
const onTransform = (item: RenderItem) => {
|
||||||
|
const rect = item.getBoundingRect();
|
||||||
|
getArea(item, rect);
|
||||||
|
checkItem(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateScroll = () => {
|
||||||
|
if (!content.value) return;
|
||||||
|
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 (rect.right > max) {
|
||||||
|
max = rect.right;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (rect.bottom > max) {
|
||||||
|
max = rect.bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.on('transform', onTransform);
|
||||||
|
listenedChild.add(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];
|
||||||
|
}
|
||||||
|
scroll.value?.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
onUpdated(updateScroll);
|
||||||
|
onMounted(updateScroll);
|
||||||
|
onUnmounted(() => {
|
||||||
|
listenedChild.forEach(v => v.off('transform', onTransform));
|
||||||
|
});
|
||||||
|
|
||||||
|
const drawScroll = (canvas: MotaOffscreenCanvas2D) => {
|
||||||
|
if (props.noscroll) return;
|
||||||
|
const ctx = canvas.ctx;
|
||||||
|
ctx.lineCap = 'round';
|
||||||
|
ctx.lineWidth = 6;
|
||||||
|
ctx.strokeStyle = '#fff';
|
||||||
|
ctx.beginPath();
|
||||||
|
if (props.direction === ScrollDirection.Horizontal) {
|
||||||
|
ctx.moveTo(nowScroll + 5, 5);
|
||||||
|
ctx.lineTo(nowScroll + scrollLength + 5, 5);
|
||||||
|
} else {
|
||||||
|
ctx.moveTo(5, nowScroll + 5);
|
||||||
|
ctx.lineTo(5, nowScroll + scrollLength + 5);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
const wheel = (ev: IWheelEvent) => {
|
||||||
|
if (props.direction === ScrollDirection.Horizontal) {
|
||||||
|
if (ev.wheelX !== 0) {
|
||||||
|
scrollTo(nowScroll + ev.wheelX, 300);
|
||||||
|
} else if (ev.wheelY !== 0) {
|
||||||
|
scrollTo(nowScroll + ev.wheelY, 300);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scrollTo(nowScroll + ev.wheelY, 300);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPos = (ev: IActionEvent) => {
|
||||||
|
if (props.direction === ScrollDirection.Horizontal) {
|
||||||
|
return ev.offsetX;
|
||||||
|
} else {
|
||||||
|
return ev.offsetY;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let identifier: number = -1;
|
||||||
|
let lastPos: number = 0;
|
||||||
|
const down = (ev: IActionEvent) => {
|
||||||
|
identifier = ev.identifier;
|
||||||
|
lastPos = getPos(ev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const move = (ev: IActionEvent) => {
|
||||||
|
if (ev.identifier !== identifier) return;
|
||||||
|
let pos = 0;
|
||||||
|
if (ev.touch) {
|
||||||
|
pos = getPos(ev);
|
||||||
|
} else {
|
||||||
|
if (ev.buttons & MouseType.Left) {
|
||||||
|
pos = getPos(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const movement = pos - lastPos;
|
||||||
|
scrollTo(nowScroll + movement, 1);
|
||||||
|
lastPos = pos;
|
||||||
|
};
|
||||||
|
|
||||||
|
let scrollBefore = 0;
|
||||||
|
let scrollIdentifier = -1;
|
||||||
|
let scrollDownPos = 0;
|
||||||
|
let scrollMutate = false;
|
||||||
|
let scrollPin = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取点击滚动条时,垂直于滚动条方向的位置
|
||||||
|
*/
|
||||||
|
const getScrollPin = (ev: IActionEvent) => {
|
||||||
|
if (props.direction === ScrollDirection.Horizontal) {
|
||||||
|
return ev.absoluteY;
|
||||||
|
} else {
|
||||||
|
return ev.absoluteX;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const downScroll = (ev: IActionEvent) => {
|
||||||
|
scrollBefore = nowScroll;
|
||||||
|
scrollIdentifier = ev.identifier;
|
||||||
|
const pos = getPos(ev);
|
||||||
|
// 计算点击在了滚动条的哪个位置
|
||||||
|
const sEnd = nowScroll + scrollLength;
|
||||||
|
if (pos >= nowScroll && pos <= sEnd) {
|
||||||
|
scrollDownPos = pos - nowScroll;
|
||||||
|
scrollMutate = false;
|
||||||
|
scrollPin = getScrollPin(ev);
|
||||||
|
} else {
|
||||||
|
scrollMutate = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const moveScroll = (ev: IActionEvent) => {
|
||||||
|
if (ev.identifier !== scrollIdentifier) return;
|
||||||
|
const pos = getPos(ev);
|
||||||
|
const scrollPos = pos - scrollDownPos;
|
||||||
|
let deltaPin = 0;
|
||||||
|
let threshold = 0;
|
||||||
|
if (ev.touch) {
|
||||||
|
const pin = getScrollPin(ev);
|
||||||
|
deltaPin = Math.abs(pin - scrollPin);
|
||||||
|
threshold = 200;
|
||||||
|
} else {
|
||||||
|
const pin = getScrollPin(ev);
|
||||||
|
deltaPin = Math.abs(pin - scrollPin);
|
||||||
|
threshold = 100;
|
||||||
|
}
|
||||||
|
if (deltaPin > threshold) {
|
||||||
|
scrollTo(scrollBefore, 1);
|
||||||
|
} else {
|
||||||
|
scrollTo(scrollPos, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const upScroll = (ev: IActionEvent) => {
|
||||||
|
if (!scrollMutate) return;
|
||||||
|
const pos = getPos(ev);
|
||||||
|
if (pos < nowScroll) {
|
||||||
|
scrollTo(pos - 50);
|
||||||
|
} else {
|
||||||
|
scrollTo(pos + 50);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
scroll.value?.root?.on('move', move);
|
||||||
|
scroll.value?.root?.on('move', moveScroll);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
scroll.value?.root?.off('move', move);
|
||||||
|
scroll.value?.root?.off('move', moveScroll);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
return (
|
||||||
|
<container loc={props.loc} onWheel={wheel}>
|
||||||
|
<container {...contentProps} ref={content} onDown={down}>
|
||||||
|
{slots.default()}
|
||||||
|
</container>
|
||||||
|
<sprite
|
||||||
|
{...scrollProps}
|
||||||
|
ref={scroll}
|
||||||
|
render={drawScroll}
|
||||||
|
onDown={downScroll}
|
||||||
|
onUp={upScroll}
|
||||||
|
></sprite>
|
||||||
|
</container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
scrollProps
|
||||||
|
);
|
@ -122,11 +122,7 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
|
|||||||
<image image={mdefIcon} loc={iconLoc(3)}></image>
|
<image image={mdefIcon} loc={iconLoc(3)}></image>
|
||||||
<text text={f(s.mdef)} loc={textLoc(3)} font={font1}></text>
|
<text text={f(s.mdef)} loc={textLoc(3)} font={font1}></text>
|
||||||
<image image={moneyIcon} loc={iconLoc(4)}></image>
|
<image image={moneyIcon} loc={iconLoc(4)}></image>
|
||||||
<text
|
<text text={f(s.money)} loc={textLoc(4)} font={font1} />
|
||||||
text={f(s.money)}
|
|
||||||
loc={textLoc(4)}
|
|
||||||
font={font1}
|
|
||||||
></text>
|
|
||||||
<image image={expIcon} loc={iconLoc(5)}></image>
|
<image image={expIcon} loc={iconLoc(5)}></image>
|
||||||
<text text={f(s.exp)} loc={textLoc(5)} font={font1}></text>
|
<text text={f(s.exp)} loc={textLoc(5)} font={font1}></text>
|
||||||
<text
|
<text
|
||||||
|
Loading…
Reference in New Issue
Block a user