diff --git a/packages/oh-my-live2d/src/config/config.ts b/packages/oh-my-live2d/src/config/config.ts index 650e839..52933ab 100644 --- a/packages/oh-my-live2d/src/config/config.ts +++ b/packages/oh-my-live2d/src/config/config.ts @@ -109,6 +109,19 @@ export const DEFAULT_OPTIONS: DefaultOptions = { priority: 3, message: ['你复制了什么内容呢?记得注明出处哦~'] } + }, + menus: { + style: { + transition: 'all 500ms', + visibility: 'hidden', + opacity: 0, + position: 'absolute', + right: 0, + bottom: '10%', + zIndex: '9999', + fontSize: '26px' + }, + itemStyle: {} } }; diff --git a/packages/oh-my-live2d/src/index.ts b/packages/oh-my-live2d/src/index.ts index 4573063..f12a8b3 100644 --- a/packages/oh-my-live2d/src/index.ts +++ b/packages/oh-my-live2d/src/index.ts @@ -1,10 +1,10 @@ import './library/iconfont.js'; -import { setup } from './modules/index.js'; +import { bootstrap } from './modules/index.js'; import { loadScript } from './utils/index.js'; export * from './types/options.js'; // @ts-ignore -export const loadOml2d = setup(async () => { +export const loadOml2d = bootstrap(async () => { // await loadUmdLibrary(importType, urls); await loadScript({ url: 'https://lib.oml2d.com/complete.js', id: 'test' }); window.PIXI.utils.skipHello(); diff --git a/packages/oh-my-live2d/src/modules/application.ts b/packages/oh-my-live2d/src/modules/application.ts new file mode 100644 index 0000000..dbb11cf --- /dev/null +++ b/packages/oh-my-live2d/src/modules/application.ts @@ -0,0 +1,49 @@ +import type { InternalModel, Live2DModel } from 'pixi-live2d-display'; +import type { Application as ApplicationType } from 'pixi.js'; + +import type { PixiModule } from '../types/index.js'; + +export class Application { + app?: ApplicationType; + constructor( + // private options: DefaultOptions, + private PIXI: PixiModule + ) {} + + mount(canvasElement: HTMLCanvasElement, stageElement: HTMLElement, model?: Live2DModel): void { + if (!this.app) { + this.app = new this.PIXI.Application({ + view: canvasElement, + resolution: 2, + autoStart: true, + autoDensity: true, + backgroundAlpha: 0, + resizeTo: stageElement + }); + } + + if (model) { + this.clearAppStage(); + this.app.stage.addChild(model); + } else { + console.error('挂载模型失败'); + } + } + + unMount(): void { + this.clearAppStage(); + } + + // 清除舞台中所有模型 + clearAppStage(): void { + const childLen = this.app?.stage.children.length || 0; + + if (childLen > 0) { + this.app!.stage.removeChildren(0, childLen - 1); + } + } + + resize(): void { + this.app?.resize(); + } +} diff --git a/packages/oh-my-live2d/src/modules/globalStyle.ts b/packages/oh-my-live2d/src/modules/globalStyle.ts index 990ca66..ae16765 100644 --- a/packages/oh-my-live2d/src/modules/globalStyle.ts +++ b/packages/oh-my-live2d/src/modules/globalStyle.ts @@ -7,25 +7,42 @@ import { createElement } from '../utils/index.js'; // 全局样式 export class GlobalStyle { - styleSheet: HTMLElement; + styleSheet?: HTMLElement; // options: DefaultOptions; constructor(private options: DefaultOptions) { // this.options = options; // destroyElement(ELEMENT_ID.globalStyle); + } + + // 创建 + create(): void { this.styleSheet = createElement({ tagName: 'style', id: ELEMENT_ID.globalStyle, innerHtml: generateGlobalStyle(this.options.primaryColor) }); - document.head.append(this.styleSheet); } - - reloadStyleSheet(): void { - this.styleSheet.innerHTML = generateGlobalStyle(this.options.primaryColor); + //挂载 + mount(): void { + if (this.styleSheet) { + document.head.append(this.styleSheet); + } } - initialize(options: DefaultOptions): void { - this.options = options; + // // 初始化 + // update(options: DefaultOptions): void { + // this.options = options; + // this.reloadStyleSheet(); + // } + + initializeStyle(): void { this.reloadStyleSheet(); } + + // 重载样式表 + reloadStyleSheet(): void { + if (this.styleSheet) { + this.styleSheet.innerHTML = generateGlobalStyle(this.options.primaryColor); + } + } } diff --git a/packages/oh-my-live2d/src/modules/index.ts b/packages/oh-my-live2d/src/modules/index.ts index 9bc6c38..3c3e406 100644 --- a/packages/oh-my-live2d/src/modules/index.ts +++ b/packages/oh-my-live2d/src/modules/index.ts @@ -1,171 +1,22 @@ -// import { HitAreaFrames } from 'pixi-live2d-display/extra'; -import type { Application } from 'pixi.js'; -// import { isNumber, mergeDeep } from 'tianjie'; - -import { GlobalStyle } from './globalStyle.js'; -import { Menus } from './menus.js'; -import { Models } from './models.js'; -import { Stage } from './stage.js'; -import { StatusBar } from './status-bar.js'; -import { Tips } from './tips.js'; +import { OhMyLive2D } from './oml2d.js'; import { DEFAULT_OPTIONS } from '../config/index.js'; -import type { DefaultOptions, LoadMethod, Options, PixiLive2dDisplayModule } from '../types/index.js'; -import { checkVersion, mergeOptions, printProjectInfo } from '../utils/index.js'; - -export class OhMyLive2D { - // private stage: Stage; - // private statusBar: StatusBar; - // private tips: Tips; - // private menus: Menus; - private models: Models; - // private model: Models; // 当前模型的实例 - // private modelIndex = 0; // 当前模型索引 - // private windowSizeType: WindowSizeType = WindowSizeType.pc; // 当前窗口大小 - // private mediaQuery = window.matchMedia('screen and (max-width: 768px)'); // 窗口大小的媒体查询 - - constructor( - private options: DefaultOptions, - private pixiLive2dDisplayModule: PixiLive2dDisplayModule, - private application: Application, - private stage: Stage, - private statusBar: StatusBar, - private tips: Tips, - private menus: Menus, - private globalStyle: GlobalStyle - // private Application: ApplicationType, - ) { - this.initialize(); - this.models = new Models( - this.application, - this.pixiLive2dDisplayModule.Live2DModel, - this.options, - this.stage, - this.statusBar, - this.tips, - this.menus - ); - - // this.destroy(); - // import('../library/iconfont.js'); - // new GlobalStyle({ primaryColor: this.options.primaryColor }); // 加载全局样式 - // const defaultStatusBarColorInfo = { - // info: this.options.primaryColor, - // error: '' - // }; - // this.stage = new Stage(this.options.parentElement, this.options.transitionTime); // 实例化舞台 - // this.statusBar = new StatusBar(this.options.parentElement, defaultStatusBarColorInfo, this.options.primaryColor); - // this.tips = new Tips(this.stage.element, this.options.tips, this.options.primaryColor); // 提示框 - // this.menus = new Menus(this.stage.element); // 菜单 - // this.models = new Models( - // this.Application, - // this.pixiLive2dDisplayModule.Live2DModel, - // this.options, - // this.HitAreaFrames, - // this.stage, - // this.statusBar, - // this.tips, - // this.menus - // ); - // this.initialize(); - } - - // 初始化 - initialize(): void { - // 检查版本 - void checkVersion(); - - // 打印信息 - if (this.options.sayHello) { - printProjectInfo(); - } - - this.initializeModules(); - // 注册事件 - this.registerEvents(); - } - - // 初始化所有模块 - initializeModules(): void { - this.stage.initialize(this.options); - this.statusBar.initialize(this.options); - this.globalStyle.initialize(this.options); - this.tips.initialize(this.options); - this.menus.initialize(); - } - - updateOptions(options: Options = {}): void { - this.options = mergeOptions(this.options, options); - this.initializeModules(); - // void this.models.loadModel(); - this.models.initialize(); - } +import type { LoadMethod, Options } from '../types/index.js'; +import { mergeOptions } from '../utils/index.js'; - registerEvents(): void { - // 点击菜单按钮 - this.menus.onClickItem((name) => { - switch (name) { - case 'Rest': - this.models.switchStatus(); - - return; - // 切换模型 - case 'SwitchModel': - void this.models.loadNextModel(); - - return; - - case 'About': - window.open('https://oml2d.com'); - - return; - } - }); - - // copy 事件 - window.addEventListener('copy', () => { - this.tips.copy(); - }); - - // 出场入场动画执行结束之后的事件回调 - this.stage.onChangeSlideEnd((status) => { - if (status) { - this.tips.welcome(); - } - }); - } -} - -export const setup = (loadMethod: LoadMethod): ((options: Options) => Promise) => { - let application: Application; +export const bootstrap = (loadMethod: LoadMethod): ((options: Options) => Promise) => { let oml2d: OhMyLive2D; - const globalStyle = new GlobalStyle(DEFAULT_OPTIONS); - const stage = new Stage(DEFAULT_OPTIONS); // 实例化舞台 - const statusBar = new StatusBar(DEFAULT_OPTIONS); - const tips = new Tips(stage.element, DEFAULT_OPTIONS); // 提示框 - const menus = new Menus(stage.element); // 菜单 - const loadOml2d = async (options: Options): Promise => { const finalOptions = mergeOptions(DEFAULT_OPTIONS, options); - const { PixiLive2dDisplay, PIXI } = await loadMethod(finalOptions.importType, finalOptions.libraryUrls); + const { PixiLive2dDisplay, PIXI, HitAreaFrames } = await loadMethod(finalOptions.importType, finalOptions.libraryUrls); - if (!application) { - application = new PIXI.Application({ - view: stage.canvasElement, - resolution: 2, - autoStart: true, - autoDensity: true, - backgroundAlpha: 0, - resizeTo: stage.element - }); - } if (oml2d) { // oml2d.destroy(); - oml2d.updateOptions(); + // oml2d.updateOptions(); } else { - oml2d = new OhMyLive2D(finalOptions, PixiLive2dDisplay, application, stage, statusBar, tips, menus, globalStyle); + oml2d = new OhMyLive2D(finalOptions, PIXI, PixiLive2dDisplay, HitAreaFrames); + void oml2d.initialize(); } - // console.log(oml2d); return oml2d; }; diff --git a/packages/oh-my-live2d/src/modules/live2d.ts b/packages/oh-my-live2d/src/modules/live2d.ts new file mode 100644 index 0000000..7ec3bc6 --- /dev/null +++ b/packages/oh-my-live2d/src/modules/live2d.ts @@ -0,0 +1,79 @@ +import type { InternalModel, Live2DModel } from 'pixi-live2d-display'; +// import { MotionPreloadStrategy } from 'pixi-live2d-display'; + +import { MotionPreloadStrategy, WindowSizeType } from '../constants/index.js'; +import type { DefaultOptions, Live2DModelType, ModelOptions } from '../types/index.js'; +import { getWindowSizeType } from '../utils/index.js'; + +export class Models { + model?: Live2DModel; // 当前模型实例 + private currentModelIndex = 0; + constructor(private options: DefaultOptions) {} + + get modelIndex(): number { + return this.currentModelIndex; + } + set modelIndex(index: number) { + this.currentModelIndex = index; + } + + get currentModelOptions(): ModelOptions { + return this.options.models[this.modelIndex]; + } + + create(Live2dModel: Live2DModelType): Promise { + return new Promise((resolve, reject) => { + this.model = Live2dModel.fromSync(this.currentModelOptions.path, { + motionPreload: (this.currentModelOptions.motionPreloadStrategy as MotionPreloadStrategy) || MotionPreloadStrategy.IDLE, + onError: reject + }); + + this.model.once('load', resolve); + }); + } + + // 设置模型 + settingModel(): void { + switch (getWindowSizeType()) { + case WindowSizeType.mobile: + this.setPosition(...(this.currentModelOptions.mobilePosition || [])); + this.setScale(this.currentModelOptions.mobileScale); + break; + case WindowSizeType.pc: + this.setPosition(...(this.currentModelOptions.position || [])); + this.setScale(this.currentModelOptions.scale); + break; + } + } + // initializeStyle(): void { + // this.setScale(); + // this.setPosition(); + // } + + // 模型尺寸 + get modelSize(): { width: number; height: number } { + return { + width: this.model?.width || 0, + height: this.model?.height || 0 + }; + } + + /** + * 设置缩放比例 + * @param x + * @param y + */ + setScale(value: number = 0.1): void { + this.model?.scale.set(value, value); + } + + /** + * 设置位置 + * @param x + * @param y + */ + setPosition(x = 0, y = 0): void { + this.model!.x = x; + this.model!.y = y; + } +} diff --git a/packages/oh-my-live2d/src/modules/menus.ts b/packages/oh-my-live2d/src/modules/menus.ts index c548da1..088534e 100644 --- a/packages/oh-my-live2d/src/modules/menus.ts +++ b/packages/oh-my-live2d/src/modules/menus.ts @@ -1,38 +1,18 @@ import { mergeDeep } from 'tianjie'; import { ELEMENT_ID, MENU_ITEMS } from '../config/index.js'; -import type { CSSProperties } from '../types/index.js'; -import { createElement, setStyleForElement } from '../utils/index.js'; +import { WindowSizeType } from '../constants/index.js'; +import type { CSSProperties, DefaultOptions } from '../types/index.js'; +import { createElement, getWindowSizeType, handleCommonStyle, setStyleForElement } from '../utils/index.js'; export class Menus { - element: HTMLElement; + element?: HTMLElement; private style: CSSProperties = {}; private itemStyle: CSSProperties = {}; private clickItem?: ((name: string) => void) | ((name: string) => Promise); private menuItemList: HTMLElement[] = []; - constructor(private stageElement: HTMLElement) { - this.element = createElement({ id: ELEMENT_ID.menus, tagName: 'div', className: ELEMENT_ID.menus }); - this.createMenuItem(); - this.stageElement.append(this.element); - } - - initialize(): void { - this.setItemStyle({}); - - this.setStyle({ - transition: 'all 500ms', - visibility: 'hidden', - opacity: 0, - position: 'absolute', - right: 0, - bottom: '10%', - zIndex: '9999', - fontSize: '26px' - }); - this.stageElement.addEventListener('mouseover', () => this.setStyle({ opacity: 1, visibility: 'visible' })); - this.stageElement.addEventListener('mouseout', () => this.setStyle({ opacity: 0, visibility: 'hidden' })); - } + constructor(private options: DefaultOptions) {} createMenuItem(): void { this.menuItemList = MENU_ITEMS.map((item) => { @@ -53,20 +33,56 @@ export class Menus { return el; }); - this.element.append(...this.menuItemList); + // this.element.append(...this.menuItemList); - this.element.addEventListener('click', (e) => { - if (e.target === e.currentTarget) { - return; - } - let target = e.target as HTMLElement; + // this.element.addEventListener('click', (e) => { + // if (e.target === e.currentTarget) { + // return; + // } + // let target = e.target as HTMLElement; - while (target.parentNode !== e.currentTarget) { - target = target.parentNode as HTMLElement; - } + // while (target.parentNode !== e.currentTarget) { + // target = target.parentNode as HTMLElement; + // } - void this.clickItem?.(target.getAttribute('data-name')!); - }); + // void this.clickItem?.(target.getAttribute('data-name')!); + // }); + } + create(): void { + this.element = createElement({ id: ELEMENT_ID.menus, tagName: 'div', className: ELEMENT_ID.menus }); + this.createMenuItem(); + } + + mount(stageElement: HTMLElement): void { + if (this.element) { + stageElement.append(this.element); + this.element.append(...this.menuItemList); + stageElement.addEventListener('mouseover', () => this.setStyle({ opacity: 1, visibility: 'visible' })); + stageElement.addEventListener('mouseout', () => this.setStyle({ opacity: 0, visibility: 'hidden' })); + } + } + + reloadStyle(): void { + switch (getWindowSizeType()) { + case WindowSizeType.pc: + this.setStyle(handleCommonStyle(this.options.menus.style || {})); + this.setItemStyle(handleCommonStyle(this.options.menus.itemStyle || {})); + break; + case WindowSizeType.mobile: + this.setStyle(handleCommonStyle(this.options.menus.style || {})); + this.setItemStyle(handleCommonStyle(this.options.menus.itemStyle || {})); + break; + } + } + + initializeStyle(): void { + this.reloadStyle(); + } + + // 更新 + update(options: DefaultOptions): void { + this.options = options; + this.reloadStyle(); } onClickItem(fn: ((name) => void) | ((name) => Promise)): void { @@ -74,8 +90,10 @@ export class Menus { } setStyle(style: CSSProperties): void { - this.style = mergeDeep(this.style, style); - setStyleForElement(this.style, this.element); + if (this.element) { + this.style = mergeDeep(this.style, style); + setStyleForElement(this.style, this.element); + } } setItemStyle(style: CSSProperties): void { diff --git a/packages/oh-my-live2d/src/modules/models.ts b/packages/oh-my-live2d/src/modules/models.ts index bf3c56e..86c4892 100644 --- a/packages/oh-my-live2d/src/modules/models.ts +++ b/packages/oh-my-live2d/src/modules/models.ts @@ -1,31 +1,17 @@ import type { InternalModel, Live2DModel } from 'pixi-live2d-display'; // import { HitAreaFrames } from 'pixi-live2d-display/extra'; -import type { Application } from 'pixi.js'; +// import type { Application } from 'pixi.js'; import { mergeDeep } from 'tianjie'; -import type { Menus } from './menus.js'; -import type { Stage } from './stage.js'; -import type { StatusBar } from './status-bar.js'; -import type { Tips } from './tips.js'; import { MotionPreloadStrategy, WindowSizeType } from '../constants/index.js'; import type { CSSProperties, DefaultOptions, Live2DModelType, ModelOptions } from '../types/index.js'; -import { getWindowSizeType, handleCommonStyle, onChangeWindowSize } from '../utils/index.js'; +import { getWindowSizeType, handleCommonStyle } from '../utils/index.js'; export class Models { - private model?: Live2DModel; // 当前模型实例 + model?: Live2DModel; // 当前模型实例 private modelList: Live2DModel[] = []; // 模型实例列表 private currentModelIndex = 0; - constructor( - private application: Application, - private live2dModel: Live2DModelType, - private options: DefaultOptions, - private stage: Stage, - private statusBar: StatusBar, - private tips: Tips, - private menus: Menus - ) { - this.initialize(); - } + constructor(private options: DefaultOptions) {} private get modelsOption(): ModelOptions[] { return this.options.models; @@ -45,7 +31,7 @@ export class Models { } // 模型尺寸 - private get modelSize(): { width: number; height: number } { + get modelSize(): { width: number; height: number } { return { width: this.model?.width || 0, height: this.model?.height || 0 @@ -57,88 +43,90 @@ export class Models { return getWindowSizeType() === WindowSizeType.mobile && !this.options.mobileDisplay; } - // 初始化 - initialize(): void { - void this.loadModel(); - onChangeWindowSize(this.handleWindowSizeChange.bind(this)); - } + // // 初始化 + // initialize(): void { + // void this.loadModel(); + // onChangeWindowSize(this.handleWindowSizeChange.bind(this)); + // } - // 切换舞台状态. 休息/活动 - switchStatus(): void { - void this.stage.slideOut(); - this.tips.clear(); + // // 切换舞台状态. 休息/活动 + // switchStatus(): void { + // void this.stage.slideOut(); + // this.tips.clear(); - this.statusBar.rest(true, () => { - void this.stage.slideIn(); - void this.tips.idlePlayer?.start(); - }); - } + // this.statusBar.rest(true, () => { + // void this.stage.slideIn(); + // void this.tips.idlePlayer?.start(); + // }); + // } // 模型活动 // activity() { // } // 创建模型 - create(onError: (e: Error) => void): void { - this.model = this.live2dModel.fromSync(this.currentModelOptions.path, { - motionPreload: (this.currentModelOptions.motionPreloadStrategy as MotionPreloadStrategy) || MotionPreloadStrategy.IDLE, - onError - }); - } - - loadModel(isLoading: boolean = true): Promise { + create(Live2dModel: Live2DModelType): Promise { return new Promise((resolve, reject) => { - if (isLoading && !this.disable) { - this.statusBar.showLoading(); - } - this.create((e) => { - console.error(e); - this.statusBar.loadingError(() => { - void this.loadModel(); - }); - reject(e); + this.model = Live2dModel.fromSync(this.currentModelOptions.path, { + motionPreload: (this.currentModelOptions.motionPreloadStrategy as MotionPreloadStrategy) || MotionPreloadStrategy.IDLE, + onError: reject }); - // 如果舞台中有其他模型则移除该模型 - if (this.application.stage.children.length >= 1) { - this.application.stage.removeChildAt(0); - } - - // 所有资源加载完毕 - this.model?.once('load', () => { - this.application.stage.addChild(this.model!); - if (!this.disable) { - this.statusBar.hideLoading(); - this.handleWindowSizeChange(getWindowSizeType()); - void this.stage.slideIn(); - } else { - this.changeAttributeByWindowSize(getWindowSizeType()); - this.statusBar.rest(); - } + this.model.once('load', () => { resolve(); }); }); } - /** - * 加载下一个模型 - */ - async loadNextModel(): Promise { - this.currentModelIndex++; - if (this.currentModelIndex > this.modelsOption.length - 1) { - this.currentModelIndex = 0; - } - this.statusBar.showLoading(); - this.tips.clear(); - await this.stage.slideOut(); - await this.loadModel(false); - void this.tips.idlePlayer?.start(); - } + // loadModel(isLoading: boolean = true): Promise { + // return new Promise((resolve, reject) => { + // if (isLoading && !this.disable) { + // this.statusBar.showLoading(); + // } + // this.create((e) => { + // console.error(e); + // this.statusBar.loadingError(() => { + // void this.loadModel(); + // }); + // reject(e); + // }); + + // // 如果舞台中有其他模型则移除该模型 + // if (this.application.stage.children.length >= 1) { + // this.application.stage.removeChildAt(0); + // } + + // // 所有资源加载完毕 + // this.model?.once('load', () => { + // this.application.stage.addChild(this.model!); + // if (!this.disable) { + // this.statusBar.hideLoading(); + // this.handleWindowSizeChange(getWindowSizeType()); + // void this.stage.slideIn(); + // } else { + // this.changeAttributeByWindowSize(getWindowSizeType()); + // this.statusBar.rest(); + // } + // resolve(); + // }); + // }); + // } + + // /** + // * 加载下一个模型 + // */ + // async loadNextModel(): Promise { + // this.currentModelIndex++; + // if (this.currentModelIndex > this.modelsOption.length - 1) { + // this.currentModelIndex = 0; + // } + // this.statusBar.showLoading(); + // this.tips.clear(); + // await this.stage.slideOut(); + // await this.loadModel(false); + // void this.tips.idlePlayer?.start(); + // } - /** - * 重新加载模型 - */ - async reloadModel() {} onLoad(): void { this.model?.on('load', () => {}); } @@ -194,49 +182,49 @@ export class Models { this.setPosition(...(this.currentModelOptions.mobilePosition || [])); } - // 处理窗口大小变化时的逻辑 - private handleWindowSizeChange(windowSizeType: WindowSizeType): void { - if (this.options.mobileDisplay) { - this.changeAttributeByWindowSize(windowSizeType); - } else { - this.changeStageStatusByWindowSize(windowSizeType); - this.changeAttributeByWindowSize(windowSizeType); - } - } + // // 处理窗口大小变化时的逻辑 + // private handleWindowSizeChange(windowSizeType: WindowSizeType): void { + // if (this.options.mobileDisplay) { + // this.changeAttributeByWindowSize(windowSizeType); + // } else { + // this.changeStageStatusByWindowSize(windowSizeType); + // this.changeAttributeByWindowSize(windowSizeType); + // } + // } - // 根据窗口大小改变舞台行为 - private changeStageStatusByWindowSize(windowSizeType: WindowSizeType): void { - switch (windowSizeType) { - case WindowSizeType.mobile: - void this.stage.slideOut(); - this.tips.clear(); - this.statusBar.rest(); - break; - case WindowSizeType.pc: - void this.stage.slideIn().then(() => { - void this.tips.idlePlayer?.start(); - }); - break; - } - } + // // 根据窗口大小改变舞台行为 + // private changeStageStatusByWindowSize(windowSizeType: WindowSizeType): void { + // switch (windowSizeType) { + // case WindowSizeType.mobile: + // void this.stage.slideOut(); + // this.tips.clear(); + // this.statusBar.rest(); + // break; + // case WindowSizeType.pc: + // void this.stage.slideIn().then(() => { + // void this.tips.idlePlayer?.start(); + // }); + // break; + // } + // } - // 根据窗口大小改变舞台中各元素的样式和参数 - private changeAttributeByWindowSize(windowSizeType: WindowSizeType): void { - switch (windowSizeType) { - case WindowSizeType.mobile: - // 移动端 - this.applyMobileAttribute(); - this.stage.setStyle(this.currentMobileStageStyle); - this.tips.setStyle(this.tips.userMobileStyle); - break; - - case WindowSizeType.pc: - // pc端 - this.applyAttribute(); - this.stage.setStyle(this.currentStageStyle); - this.tips.setStyle(this.tips.userStyle); - break; - } - this.application.resize(); - } + // // 根据窗口大小改变舞台中各元素的样式和参数 + // private changeAttributeByWindowSize(windowSizeType: WindowSizeType): void { + // switch (windowSizeType) { + // case WindowSizeType.mobile: + // // 移动端 + // this.applyMobileAttribute(); + // this.stage.setStyle(this.currentMobileStageStyle); + // this.tips.setStyle(this.tips.userMobileStyle); + // break; + + // case WindowSizeType.pc: + // // pc端 + // this.applyAttribute(); + // this.stage.setStyle(this.currentStageStyle); + // this.tips.setStyle(this.tips.userStyle); + // break; + // } + // this.application.resize(); + // } } diff --git a/packages/oh-my-live2d/src/modules/oml2d.ts b/packages/oh-my-live2d/src/modules/oml2d.ts new file mode 100644 index 0000000..cb1dbbc --- /dev/null +++ b/packages/oh-my-live2d/src/modules/oml2d.ts @@ -0,0 +1,193 @@ +import { Application } from './application.js'; +import { GlobalStyle } from './globalStyle.js'; +import { Models } from './live2d.js'; +import { Menus } from './menus.js'; +// import { Models } from './models.js'; +import { Stage } from './stage.js'; +import { StatusBar } from './status-bar.js'; +import { Tips } from './tips.js'; +import type { DefaultOptions, HitAreaFramesModule, ModelOptions, PixiLive2dDisplayModule, PixiModule } from '../types/index.js'; +import { checkVersion, printProjectInfo } from '../utils/index.js'; + +export class OhMyLive2D { + private globalStyle: GlobalStyle; + private stage: Stage; + private statusBar: StatusBar; + private tips: Tips; + private menus: Menus; + private models: Models; + // private models: Models; + private application?: Application; + private currentModelIndex: number = 0; + // private model: Models; // 当前模型的实例 + // private modelIndex = 0; // 当前模型索引 + // private windowSizeType: WindowSizeType = WindowSizeType.pc; // 当前窗口大小 + // private mediaQuery = window.matchMedia('screen and (max-width: 768px)'); // 窗口大小的媒体查询 + + constructor( + private options: DefaultOptions, + private PIXI: PixiModule, + private PixiLive2dDisplay: PixiLive2dDisplayModule, + private HitAreaFrames: HitAreaFramesModule + // private Application: ApplicationType, + ) { + this.globalStyle = new GlobalStyle(options); + this.stage = new Stage(options); // 实例化舞台 + this.statusBar = new StatusBar(options); + this.tips = new Tips(options); // 提示框 + this.menus = new Menus(options); // 菜单 + this.models = new Models(options); + // this.models = new Models(options); + this.application = new Application(this.PIXI); + this.modelIndex = 0; + // new GlobalStyle({ primaryColor: this.options.primaryColor }); // 加载全局样式 + // const defaultStatusBarColorInfo = { + // info: this.options.primaryColor, + // error: '' + // }; + } + + set modelIndex(index: number) { + this.currentModelIndex = index; + this.stage.modelIndex = index; + this.models.modelIndex = index; + } + + get modelIndex(): number { + return this.currentModelIndex; + } + + /** + * 当前模型选项 + */ + get currentModelOptions(): ModelOptions { + return this.options.models[this.currentModelIndex]; + } + + /** + * 创建 + */ + create(): void { + this.globalStyle.create(); + this.stage.create(); + this.statusBar.create(); + this.menus.create(); + this.tips.create(); + } + + /** + * 挂载 + */ + mount(): void { + this.globalStyle.mount(); + this.stage.mount(); + this.statusBar.mount(); + this.menus.mount(this.stage.element!); + this.tips.mount(this.stage.element!); + } + + async loadModel(): Promise { + this.statusBar.showLoading(); + await this.models.create(this.PixiLive2dDisplay.Live2DModel); + this.application?.mount(this.stage.canvasElement!, this.stage.element!, this.models.model); + this.models.settingModel(); + this.stage.reloadStyle(this.models.modelSize); + this.application?.resize(); + this.statusBar.hideLoading(); + void this.stage.slideIn(); + } + + /** + * 初始化样式 + */ + initializeStyle(): void { + this.globalStyle.initializeStyle(); + this.statusBar.initializeStyle(); + this.tips.initializeStyle(); + this.stage.initializeStyle(); + this.menus.initializeStyle(); + } + + loadNextModel(): void {} + // onCreated(fn: () => void): void { + // fn(); + // } + + // 初始化 + async initialize(): Promise { + // 检查版本 + void checkVersion(); + + // 打印信息 + if (this.options.sayHello) { + printProjectInfo(); + } + // 创建 + this.create(); + + // 初始化样式 + this.initializeStyle(); + + // 挂载 + this.mount(); + + // 注册dom事件 + this.registerEvent(); + + // 加载模型 + await this.loadModel(); + } + + registerEvent(): void { + // 出场入场动画执行结束之后的事件回调 + this.stage.onChangeSlideEnd((status) => { + if (status) { + this.tips.welcome(); + } + }); + } + + // // 初始化所有模块 + // initializeModules(): void { + // this.stage.initialize(this.options); + // this.statusBar.initialize(this.options); + // this.globalStyle.initialize(this.options); + // this.tips.initialize(this.options); + // this.menus.initialize(); + // } + + // updateOptions(options: Options = {}): void { + // this.options = mergeOptions(this.options, options); + // this.initializeModules(); + // // void this.models.loadModel(); + // this.models.initialize(); + // } + + // registerEvents(): void { + // // 点击菜单按钮 + // this.menus.onClickItem((name) => { + // switch (name) { + // case 'Rest': + // this.models.switchStatus(); + + // return; + // // 切换模型 + // case 'SwitchModel': + // void this.models.loadNextModel(); + + // return; + + // case 'About': + // window.open('https://oml2d.com'); + + // return; + // } + // }); + + // // copy 事件 + // window.addEventListener('copy', () => { + // this.tips.copy(); + // }); + + // } +} diff --git a/packages/oh-my-live2d/src/modules/stage.ts b/packages/oh-my-live2d/src/modules/stage.ts index c826bd7..3e5d0ba 100644 --- a/packages/oh-my-live2d/src/modules/stage.ts +++ b/packages/oh-my-live2d/src/modules/stage.ts @@ -1,35 +1,81 @@ import { mergeDeep } from 'tianjie'; import { ELEMENT_ID } from '../config/index.js'; +import { WindowSizeType } from '../constants/index.js'; +import { CommonStyleType } from '../types/common.js'; import type { CSSProperties, DefaultOptions } from '../types/index.js'; -import { createElement, setStyleForElement } from '../utils/index.js'; +import { createElement, getWindowSizeType, handleCommonStyle, setStyleForElement } from '../utils/index.js'; -const enum Status { - display = 1, - hidden = 0 -} +// const enum Status { +// display = true, +// hidden = false +// } export class Stage { - element: HTMLElement; - canvasElement: HTMLCanvasElement; - status: Status = Status.hidden; - // options: DefaultOptions; + element?: HTMLElement; + canvasElement?: HTMLCanvasElement; + // status: Status = Status.hidden; + private style: CSSProperties = {}; private canvasStyle: CSSProperties = {}; - private slideChangeEnd?: (status: Status) => void; + private slideChangeEnd?: (status: boolean) => void; + private currentModelIndex = 0; + constructor(private options: DefaultOptions) {} - constructor(private options: DefaultOptions) { - // this.options = options; + create(): void { this.element = createElement({ id: ELEMENT_ID.stage, tagName: 'div' }); this.canvasElement = createElement({ id: ELEMENT_ID.canvas, tagName: 'canvas' }) as HTMLCanvasElement; - // this.initialize(); } - // 销毁 - destroy(): void { - this.element.remove(); + set modelIndex(index: number) { + this.currentModelIndex = index; + } + + get modelIndex(): number { + return this.currentModelIndex; + } + + mount(): void { + if (this.element && this.canvasElement) { + this.element.append(this.canvasElement); + this.options.parentElement.append(this.element); + } + } + + initializeStyle(): void { + this.setStyle({ + width: '0px', + height: '0px', + position: 'fixed', + left: 0, + bottom: 0, + zIndex: '9997', + transform: 'translateY(130%)' + }); + this.reloadStyle(); + } + + reloadStyle(modelSize: CommonStyleType = {}): void { + switch (getWindowSizeType()) { + case WindowSizeType.mobile: + modelSize = mergeDeep(modelSize, this.options.models[this.modelIndex].mobileStageStyle || {}); + this.setStyle(handleCommonStyle(modelSize)); + break; + case WindowSizeType.pc: + modelSize = mergeDeep(modelSize, this.options.models[this.modelIndex].stageStyle || {}); + this.setStyle(handleCommonStyle(modelSize)); + break; + } + } + + unMount(): void { + this.element?.remove(); } + reMount(): void { + this.unMount(); + this.mount(); + } // create(): void { // const stageFragment = document.createDocumentFragment(); @@ -48,52 +94,39 @@ export class Stage { // // }; // } - initialize(options: DefaultOptions): void { - this.options = options; - this.setStyle({ - width: '0px', - height: '0px', - position: 'fixed', - left: 0, - bottom: 0, - zIndex: '9997', - transform: 'translateY(130%)' - }); - this.element.append(this.canvasElement); - this.updateParentElement(); - } + // updateStyle(): void {} + // initialize(): void { + // this.element.append(this.canvasElement); + // this.updateParentElement(); + // } - updateParentElement(): void { - this.element.remove(); - this.options.parentElement.append(this.element); - } - // initStyle(): void { - // this.setStyle({ - // width: '0px', - // height: '0px', - // position: 'fixed', - // left: 0, - // bottom: 0, - // zIndex: '9997', - // transform: 'translateY(130%)' - // }); + // updateParentElement(): void { + // if (this.element) { + // this.element.remove(); + // this.options.parentElement.append(this.element); + // } // } setStyle(style: CSSProperties, callback?: () => void): void { - this.style = mergeDeep(this.style, style); - setStyleForElement(this.style, this.element); - this.setCanvasStyle({ width: '100%', height: '100%', zIndex: '9998', position: 'relative' }); - callback?.(); + if (this.element) { + this.style = mergeDeep(this.style, style); + setStyleForElement(this.style, this.element); + this.setCanvasStyle({ width: '100%', height: '100%', zIndex: '9998', position: 'relative' }); + callback?.(); + } } setCanvasStyle(style: CSSProperties): void { - this.canvasStyle = mergeDeep(this.canvasStyle, style); - setStyleForElement(this.canvasStyle, this.canvasElement); + if (this.canvasElement) { + this.canvasStyle = mergeDeep(this.canvasStyle, style); + setStyleForElement(this.canvasStyle, this.canvasElement); + } } get transitionTime(): number { return this.options.transitionTime; } + /** * 滑入 */ @@ -103,11 +136,11 @@ export class Stage { animationDuration: `${this.transitionTime}ms`, animationFillMode: 'forwards' }); - this.status = Status.display; + // this.status = Status.display; return new Promise((resolve) => { setTimeout(() => { - this.slideChangeEnd?.(this.status); + this.slideChangeEnd?.(true); resolve(); }, this.transitionTime); }); @@ -122,11 +155,11 @@ export class Stage { animationDuration: `${this.transitionTime}ms`, animationFillMode: 'forwards' }); - this.status = Status.hidden; + // this.status = Status.hidden; return new Promise((resolve) => setTimeout(() => { - this.slideChangeEnd?.(this.status); + this.slideChangeEnd?.(false); resolve(); }, this.transitionTime) ); @@ -136,13 +169,7 @@ export class Stage { * 场景的滑入滑出动画执行结束事件 * @param fn */ - onChangeSlideEnd(fn: (status: Status) => void): void { + onChangeSlideEnd(fn: (status: boolean) => void): void { this.slideChangeEnd = fn; } - - // applyOptions(): void { - - // } - - // applyMobileOptions() {} } diff --git a/packages/oh-my-live2d/src/modules/status-bar.ts b/packages/oh-my-live2d/src/modules/status-bar.ts index ba45686..46cc1c8 100644 --- a/packages/oh-my-live2d/src/modules/status-bar.ts +++ b/packages/oh-my-live2d/src/modules/status-bar.ts @@ -1,8 +1,9 @@ import { mergeDeep } from 'tianjie'; import { ELEMENT_ID } from '../config/index.js'; +import { WindowSizeType } from '../constants/index.js'; import type { CSSProperties, DefaultOptions } from '../types/index.js'; -import { createElement, handleCommonStyle, setStyleForElement } from '../utils/index.js'; +import { createElement, getWindowSizeType, handleCommonStyle, setStyleForElement } from '../utils/index.js'; const enum Status { display = 1, @@ -23,7 +24,7 @@ export type HoverActionParams = { * 状态条 */ export class StatusBar { - element: HTMLElement; + element?: HTMLElement; transitionTime = 800; status: Status = Status.hidden; @@ -31,35 +32,66 @@ export class StatusBar { private timer = 0; constructor(private options: DefaultOptions) { // this.options = options; - this.element = createElement({ id: ELEMENT_ID.statusBar, tagName: 'div', innerText: 'hello' }); + // this.element = createElement({ id: ELEMENT_ID.statusBar, tagName: 'div', innerText: 'hello' }); // this.options.parentElement.append(this.element); - // this.initialize(); } + create(): void { + this.element = createElement({ id: ELEMENT_ID.statusBar, tagName: 'div', innerText: 'hello' }); + } + mount(): void { + if (this.element) { + this.options.parentElement.append(this.element); + } + } get userStyle(): CSSProperties { return handleCommonStyle(this.options.statusBar.style || {}); } - initialize(options: DefaultOptions): void { - this.options = options; - this.setStyle(this.userStyle); - this.updateParentElement(); + reloadStyle(): void { + switch (getWindowSizeType()) { + case WindowSizeType.pc: + this.setStyle(handleCommonStyle(this.options.statusBar.style || {})); + break; + case WindowSizeType.mobile: + this.setStyle(handleCommonStyle(this.options.statusBar.style || {})); + break; + } } - updateParentElement(): void { - this.element.remove(); - this.options.parentElement.append(this.element); + // 初始化样式 + initializeStyle(): void { + this.reloadStyle(); } - // 销毁 - destroy(): void { - this.element.remove(); + + // 卸载 + unMount(): void { + this.element?.remove(); } + // 重新挂载 + reMounte(): void { + this.unMount(); + this.mount(); + } + // updateParentElement(): void { + // if (this.element) { + // this.element.remove(); + // this.options.parentElement.append(this.element); + // } + // } + // // 销毁 + // destroy(): void { + // this.element.remove(); + // } + setStyle(style: CSSProperties): void { - this.style = mergeDeep(this.style, style); - this.style.backgroundColor ||= this.stateColor.info; - setStyleForElement(style, this.element); + if (this.element) { + this.style = mergeDeep(this.style, style); + this.style.backgroundColor ||= this.stateColor.info; + setStyleForElement(style, this.element); + } } private slideIn(): Promise { @@ -85,10 +117,12 @@ export class StatusBar { animationFillMode: 'forwards' }); setTimeout(() => { - // 每次收起后移除所有事件 - this.element.onclick = null; - this.element.onmousemove = null; - this.element.onmouseout = null; + if (this.element) { + // 每次收起后移除所有事件 + this.element.onclick = null; + this.element.onmousemove = null; + this.element.onmouseout = null; + } resolve(this.status); }, this.transitionTime); }); @@ -112,18 +146,22 @@ export class StatusBar { } setHoverAction(inContent: HoverActionParams, outContent: HoverActionParams): void { - this.element.onmouseover = (): void => { - this.popup(inContent.content, inContent.state, false); - }; - this.element.onmouseout = (): void => { - this.popup(outContent.content, outContent.state, false); - }; + if (this.element) { + this.element.onmouseover = (): void => { + this.popup(inContent.content, inContent.state, false); + }; + this.element.onmouseout = (): void => { + this.popup(outContent.content, outContent.state, false); + }; + } } // 清除hover clearHoverAction(): void { - this.element.onmousemove = null; - this.element.onmouseout = null; + if (this.element) { + this.element.onmousemove = null; + this.element.onmouseout = null; + } } /** @@ -148,11 +186,11 @@ export class StatusBar { // this.element.removeEventListener('mouseout', mouseout); // this.element.removeEventListener('mouseover', mouseover); this.clearHoverAction(); - this.element.removeEventListener('click', handleClick); + this.element?.removeEventListener('click', handleClick); this.showLoading(); }; - this.element.addEventListener('click', handleClick); + this.element?.addEventListener('click', handleClick); // this.element.addEventListener('mouseover', mouseover); // this.element.addEventListener('mouseout', mouseout); } @@ -191,7 +229,7 @@ export class StatusBar { void this.slideOut(); }, delay); } else { - if (callback) { + if (callback && this.element) { this.element.onclick = callback; } } @@ -199,6 +237,8 @@ export class StatusBar { } setContent(content: string): void { - this.element.innerHTML = content; + if (this.element) { + this.element.innerHTML = content; + } } } diff --git a/packages/oh-my-live2d/src/modules/tips.ts b/packages/oh-my-live2d/src/modules/tips.ts index b20947d..e04bcbd 100644 --- a/packages/oh-my-live2d/src/modules/tips.ts +++ b/packages/oh-my-live2d/src/modules/tips.ts @@ -1,12 +1,22 @@ import { getRandomArrayItem, isFunction, mergeDeep, setIntervalAsync } from 'tianjie'; import { ELEMENT_ID } from '../config/index.js'; +import { WindowSizeType } from '../constants/index.js'; import type { IdleTimer } from '../types/common.js'; -import type { CSSProperties, DefaultOptions } from '../types/index.js'; -import { createElement, getWelcomeMessage, getWordTheDay, handleCommonStyle, setStyleForElement, sleep } from '../utils/index.js'; +import type { CSSProperties, DefaultOptions, DefaultTipsOptions } from '../types/index.js'; +import { + createElement, + getWelcomeMessage, + getWindowSizeType, + getWordTheDay, + handleCommonStyle, + setStyleForElement, + sleep +} from '../utils/index.js'; export class Tips { - private element: HTMLElement; + private element?: HTMLElement; + private contentElement?: HTMLElement; // private status: Status = Status.Hidden; // options: DefaultOptions; idlePlayer?: IdleTimer; @@ -14,43 +24,56 @@ export class Tips { private transitionTime = 1000; // 默认的消息过渡动画持续时长 private style: CSSProperties = {}; private priority = 0; // 当前优先级 - private contentElement: HTMLElement; private contentStyle: CSSProperties = {}; - constructor( - private stageElement: HTMLElement, - private options: DefaultOptions - // private tipsOptions: DefaultTipsOptions, - // private primaryColor: string - ) { - // this.options = options; - this.element = createElement({ id: ELEMENT_ID.tips, tagName: 'div' }); - this.contentElement = createElement({ id: 'oml2dTipsContent', tagName: 'div' }); - this.element.append(this.contentElement); - this.stageElement.append(this.element); - // this.initStyle(); + constructor(private options: DefaultOptions) { // 创建空闲消息播放器 this.idlePlayer = this.createIdleMessagePlayer(); } + get tipsOptions(): DefaultTipsOptions { + return this.options.tips; + } + + create(): void { + this.element = createElement({ id: ELEMENT_ID.tips, tagName: 'div' }); + this.contentElement = createElement({ id: 'oml2dTipsContent', tagName: 'div' }); + } + + mount(stageElement: HTMLElement): void { + if (this.element && this.contentElement) { + this.element.append(this.contentElement); + stageElement.append(this.element); + } + } // get tipsOptions(): DefaultTipsOptions { // return this.options.tips; // } get primaryColor(): string { return this.options.primaryColor; } - get userStyle(): CSSProperties { - return handleCommonStyle(this.options.tips.style || {}); - } - get userMobileStyle(): CSSProperties { - return handleCommonStyle(this.options.tips.mobileStyle || {}); + + // get userStyle(): CSSProperties {} + // get userMobileStyle(): CSSProperties { + // return handleCommonStyle(this.options.tips.mobileStyle || {}); + // } + + // + reloadStyle(): void { + switch (getWindowSizeType()) { + case WindowSizeType.pc: + this.setStyle(handleCommonStyle(this.options.tips.style || {})); + break; + case WindowSizeType.mobile: + this.setStyle(handleCommonStyle(this.options.tips.mobileStyle || {})); + break; + } } /** - * 初始化 + * 初始化样式 */ - initialize(options: DefaultOptions): void { - this.options = options; - this.setStyle(this.userStyle); + initializeStyle(): void { + this.reloadStyle(); this.setContentStyle({ wordBreak: 'break-all', display: '-webkit-box', @@ -66,25 +89,30 @@ export class Tips { * @param style */ setStyle(style: CSSProperties = {}): void { - this.style = mergeDeep(this.style, style); - this.style.backgroundColor ||= this.options.primaryColor; - setStyleForElement(this.style, this.element); + if (this.element) { + this.style = mergeDeep(this.style, style); + this.style.backgroundColor ||= this.options.primaryColor; + setStyleForElement(this.style, this.element); + } } setContentStyle(style: CSSProperties): void { - this.contentStyle = mergeDeep(this.contentStyle, style); - setStyleForElement(this.contentStyle, this.contentElement); + if (this.contentElement) { + this.contentStyle = mergeDeep(this.contentStyle, style); + setStyleForElement(this.contentStyle, this.contentElement); + } } private setContent(message: string): void { - this.contentElement.innerHTML = message; + if (this.contentElement) { + this.contentElement.innerHTML = message; + } } showMessage(message: string, duration = 3000, priority = 0): void { if (priority < this.priority) { return; } - this.priority = priority; if (this.closeTimer) { clearTimeout(this.closeTimer); } @@ -152,30 +180,26 @@ export class Tips { * @returns */ private createIdleMessagePlayer(): undefined | IdleTimer { - if (!this.options.tips) { - return; - } - const { message: messages, duration, priority } = this.options.tips.idleTips; let message = ''; const timer = setIntervalAsync(async () => { // 是否开启每日一言 - if (this.options.tips.idleTips.wordTheDay) { - if (isFunction(this.options.tips.idleTips.wordTheDay)) { - message = await getWordTheDay(this.options.tips.idleTips.wordTheDay); + if (this.tipsOptions.idleTips.wordTheDay) { + if (isFunction(this.tipsOptions.idleTips.wordTheDay)) { + message = await getWordTheDay(this.tipsOptions.idleTips.wordTheDay); } else { message = await getWordTheDay(); } } else { - if (isFunction(messages)) { - message = await messages(); + if (isFunction(this.tipsOptions.idleTips.message)) { + message = await this.tipsOptions.idleTips.message(); } else { - message = getRandomArrayItem(messages || []) || ''; + message = getRandomArrayItem(this.tipsOptions.idleTips.message || []) || ''; } } if (message) { - this.showMessage(message, duration, priority); - await sleep(duration); + this.showMessage(message, this.tipsOptions.idleTips.duration, this.tipsOptions.idleTips.priority); + await sleep(this.tipsOptions.idleTips.duration); } else { timer.stop(); } diff --git a/packages/oh-my-live2d/src/types/index.ts b/packages/oh-my-live2d/src/types/index.ts index 056f236..d018aa5 100644 --- a/packages/oh-my-live2d/src/types/index.ts +++ b/packages/oh-my-live2d/src/types/index.ts @@ -5,7 +5,7 @@ import type { Application } from 'pixi.js'; import { CommonStyleType } from './common.js'; import type { ModelOptions } from './model.js'; -import type { Options } from './options.js'; +import type { MenusOptions, Options } from './options.js'; import { StatusBarOptions } from './statusBar.js'; import { TipsOptions } from './tips.js'; import type { DeepRequired } from './utils.js'; @@ -26,11 +26,11 @@ export type ApplicationType = typeof Application; export type CSSProperties = CSS.Properties; -export type DefaultOptions = Omit, 'parentElement' | 'models' | 'tips' | 'statusBar'> & { +export type DefaultOptions = Omit, 'parentElement' | 'models' | 'tips' | 'statusBar' | 'menus'> & { parentElement: HTMLElement; tips: DefaultTipsOptions; statusBar: DefaultStatusBarOptions; -} & { + menus: DefaultMenusOptions; models: ModelOptions[]; }; @@ -43,8 +43,15 @@ export type DefaultStatusBarOptions = Omit, 'styl style?: CommonStyleType; }; +export type DefaultMenusOptions = Omit, 'style' | 'itemStyle'> & { + style?: CommonStyleType; + itemStyle?: CommonStyleType; +}; + export type Live2DModelType = typeof Live2DModel; +export type HitAreaFramesModule = typeof HitAreaFrames; + export type LibraryUrls = { /** * 自定义 Cubism2 SDK 地址 @@ -83,5 +90,5 @@ export type LoadMethod = ( ) => Promise<{ PIXI: PixiModule; PixiLive2dDisplay: PixiLive2dDisplayModule; - HitAreaFrames: typeof HitAreaFrames; + HitAreaFrames: HitAreaFramesModule; }>; diff --git a/packages/oh-my-live2d/src/types/menus.ts b/packages/oh-my-live2d/src/types/menus.ts index cc73ed1..0290c32 100644 --- a/packages/oh-my-live2d/src/types/menus.ts +++ b/packages/oh-my-live2d/src/types/menus.ts @@ -1,6 +1,15 @@ -import { CommonStyleType } from './common'; +import { CommonStyleType } from './common.js'; export interface MenusOptions { + /** + * 配置菜单整体样式 + * @valueType object + */ style?: CommonStyleType; + + /** + * 配置菜单每个子项按钮的样式 + * @valueType object + */ itemStyle?: CommonStyleType; } diff --git a/packages/oh-my-live2d/src/types/options.ts b/packages/oh-my-live2d/src/types/options.ts index aa1e67f..eb5cca3 100644 --- a/packages/oh-my-live2d/src/types/options.ts +++ b/packages/oh-my-live2d/src/types/options.ts @@ -1,3 +1,4 @@ +import type { MenusOptions } from './menus.js'; import type { ModelOptions } from './model.js'; import type { StatusBarOptions } from './statusBar.js'; import type { TipsOptions } from './tips.js'; @@ -85,6 +86,11 @@ export interface Options { tips?: TipsOptions; statusBar?: StatusBarOptions; + + /** + * 菜单配置 + */ + menus?: MenusOptions; /** * 为组件提供一个父元素,如果未指定则默认挂载到 body 中 * @valueType HtmlElement @@ -108,3 +114,4 @@ export interface Options { export * from './model.js'; export * from './tips.js'; export * from './statusBar.js'; +export * from './menus.js'; diff --git a/tests/vite-app/src/main.ts b/tests/vite-app/src/main.ts index 5a0cb45..93ef78b 100644 --- a/tests/vite-app/src/main.ts +++ b/tests/vite-app/src/main.ts @@ -19,10 +19,10 @@ void loadOml2d({ { path: 'https://registry.npmmirror.com/oml2d-models/latest/files/models/Pio/model.json', scale: 0.4, - position: [0, 50], + position: [0, 0], mobileScale: 0.35, stageStyle: { - height: 300 + // height: 300 } } ], @@ -39,17 +39,17 @@ void loadOml2d({ } } }).then((oml2d) => { - setTimeout(() => { - oml2d.updateOptions({ - models: [ - { - path: 'https://registry.npmmirror.com/oml2d-models/latest/files/models/Senko_Normals/senko.model3.json', - stageStyle: {}, - mobileScale: 0.08 - } - ] - }); - }, 1000); + // setTimeout(() => { + // oml2d.updateOptions({ + // models: [ + // { + // path: 'https://registry.npmmirror.com/oml2d-models/latest/files/models/Senko_Normals/senko.model3.json', + // stageStyle: {}, + // mobileScale: 0.08 + // } + // ] + // }); + // }, 1000); }); // void loadOml2d({ // models: [