Skip to content

Instantly share code, notes, and snippets.

@mitian233
Created February 8, 2025 14:20
Show Gist options
  • Save mitian233/a3575e7d01b5d3295fa972c3a5bf2f0d to your computer and use it in GitHub Desktop.
Save mitian233/a3575e7d01b5d3295fa972c3a5bf2f0d to your computer and use it in GitHub Desktop.
use-spine.ts
import { spine } from './spine-widget/index.js';
const spineDir = '/spine/';
const idleMaxTime = 60000;
const edge = 40;
interface model {
name: string;
skin: string;
animations: {
start: string;
idle: string[];
};
};
interface config {
enable: boolean;
models: model[];
styles: {
widget: {
x: string;
y: string;
}
};
}
interface anmDefination {
name: string;
loop: boolean;
}
class SpineModel {
private config: config;
private model: model;
private urlPrefix: string;
private skin: string;
private json: string;
private atlas: string;
private widget: any;
private widgetElement: HTMLElement | null;
private triggerEvents: string[];
private lastInteractTime: number;
private localX: number;
private localY: number;
constructor(widgetDiv = "#spine-widget", config: config) {
this.config = config;
this.model = config.models[Math.floor(Math.random() * config.models.length)];
this.urlPrefix = spineDir + this.model.name;
this.skin = this.model.skin;
this.json = this.urlPrefix + '/data.json';
this.atlas = this.urlPrefix + '/data.atlas';
this.widget = null;
this.widgetElement = document.querySelector(widgetDiv);
this.triggerEvents = ['mousedown', 'touchstart', 'scroll'];
this.lastInteractTime = Date.now();
this.localX = 0;
this.localY = 0;
this.load();
}
load() {
this.config.styles.widget.x && this.widgetElement?.style.setProperty('x', this.config.styles.widget.x);
this.config.styles.widget.y && this.widgetElement?.style.setProperty('y', this.config.styles.widget.y);
new spine.SpineWidget(this.widgetElement!, {
animation: this.model.animations.start,
atlas: this.atlas,
json: this.json,
skin: this.skin,
backgroundColor: '#00000000',
alpha: true,
loop: false,
fitToCanvas: true,
success: this.successCallback.bind(this),
})
}
successCallback(w: any) {
this.widget = w;
this.initWidgetActions();
this.initDragging();
this.triggerEvents.forEach((i) =>
window.addEventListener(i, this.changeIdleAnimation.bind(this))
);
}
initWidgetActions() {
this.widget.canvas.onclick = this.interact.bind(this);
this.widget.state.addListener({
complete: (t: { loop: boolean; animation: { name: string } }) => {
t.loop || this.isIdle()
? this.playRandAnimation({
name: t.animation.name,
loop: true,
})
: this.playRandAnimation(this.getAnimationList('idle'));
},
});
}
isIdle() {
let currentAnm = this.widget.state.tracks[0].animation;
for (const i of this.getAnimationList('idle')) {
if (currentAnm.name === i.name) {
return !0;
}
}
return !1;
}
playRandAnimation(anm: anmDefination | anmDefination[]) {
if (Array.isArray(anm)) {
let a = anm[Math.floor(Math.random() * anm.length)];
this.widget.state.setAnimation(0, a.name, a.loop);
} else {
this.widget.state.setAnimation(0, anm.name, anm.loop);
}
}
getAnimationList(name: string) {
if (name === 'start') {
return [{
name: this.model.animations.start,
loop: !1,
}]
} else {
return this.model.animations.idle.map((n: string) => ({
name: n,
loop: !1,
}));
}
}
interact() {
!this.isIdle()
? console.warn('too much manipulations')
: ((this.lastInteractTime = Date.now()),
this.playRandAnimation(
this.widget.skeleton.data.animations.map((n: anmDefination) => ({
name: n.name,
loop: !1,
}))
));
}
changeIdleAnimation() {
let time = Date.now(),
interval = time - this.lastInteractTime;
if (this.isIdle() && interval >= idleMaxTime) {
(this.lastInteractTime = time), this.playRandAnimation(this.getAnimationList('idle'));
}
}
initDragging() {
function i(t: TouchEvent | MouseEvent): { x: number; y: number } {
let e = document.documentElement.scrollLeft,
i = document.documentElement.scrollTop;
if ('targetTouches' in t && t.targetTouches.length > 0) {
e += t.targetTouches[0].clientX;
i += t.targetTouches[0].clientY;
} else if ('clientX' in t && 'clientY' in t) {
e += t.clientX;
i += t.clientY;
}
return {
x: e,
y: i,
};
}
function e(t: MouseEvent | TouchEvent): void {
t.cancelable && t.preventDefault();
}
const n = (t: number, e: number): void => {
(t = Math.max(0 - edge, t)),
(e = Math.max(0, e)),
(t = Math.min(document.body.clientWidth + edge - this.widgetElement!.clientWidth, t)),
(e = Math.min(document.body.clientHeight - this.widgetElement!.clientHeight, e)),
(this.widgetElement!.style.left = t + 'px'),
(this.widgetElement!.style.top = e + 'px');
},
o = (t: TouchEvent | MouseEvent): void => {
let { x: e, y: top } = i(t);
(this.localX = e - this.widgetElement!.offsetLeft),
(this.localY = top - this.widgetElement!.offsetTop);
},
s = (t: MouseEvent | TouchEvent): void => {
let { x: e, y: top } = i(t);
n(e - this.localX, top - this.localY),
window?.getSelection && window.getSelection()!.removeAllRanges();// : document.getSelection.empty()
},
t = {
passive: true,
},
a = {
passive: false,
};
this.widgetElement!.addEventListener('mousedown', (t: MouseEvent) => {
o(t), document.addEventListener('mousemove', s);
}),
this.widgetElement!.addEventListener(
'touchstart',
(t: TouchEvent) => {
o(t), document.addEventListener('touchmove', e, a);
},
t
),
this.widgetElement!.addEventListener('touchmove', s, t),
document.addEventListener('mouseup', () => document.removeEventListener('mousemove', s)),
this.widgetElement!.addEventListener('touchend', () =>
document.removeEventListener('touchmove', e)
),
window.addEventListener('resize', () => {
let t = this.widgetElement!.style;
let e, i;
t.left &&
t.top &&
((e = Number.parseInt(t.left.substring(0, t.left.length - 2))),
(i = Number.parseInt(t.top.substring(0, t.top.length - 2))),
n(e, i));
});
}
}
export { SpineModel };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment