npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@brmtech/spine

v0.1.7

Published

Framework-agnostic Spine WebGL renderer with shared canvas layers.

Readme

@brmtech/spine

Framework-agnostic Spine WebGL renderer with shared canvas layers.

中文 | English

中文文档

@brmtech/spine 是一个不绑定框架的 Spine WebGL 渲染器。原生 DOM、Vue、React 和其他前端框架都使用同一个核心 API:拿到宿主元素,然后调用 createSpine

安装

pnpm add @brmtech/spine @esotericsoftware/spine-webgl

快速开始

import { createSpine } from "@brmtech/spine";

const host = document.querySelector("#hero") as HTMLElement;

const spine = createSpine(host, "spine/hero/Hero", {
  baseUrl: "https://cdn.example.com/assets/",
  animation: "Idle",
});

await spine.ready;
spine.play("Attack", { loop: false });

框架用法

| 场景 | 获取宿主元素 | 创建方式 | | -------- | ------------------------------------------------ | ---------------------------------------------------------- | | 原生 DOM | document.querySelector('#hero') as HTMLElement | createSpine(host, 'spine/hero/Hero', options) | | Vue 3 | 模板 ref + onMounted | createSpine(hostRef.value, 'spine/hero/Hero', options) | | React | useRef<HTMLDivElement>() + useEffect | createSpine(hostRef.current, 'spine/hero/Hero', options) |

Vue 3

<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from "vue";
import { createSpine, type SpineInstance } from "@brmtech/spine";

const hostRef = ref<HTMLElement | null>(null);
const spineRef = ref<SpineInstance | null>(null);

onMounted(() => {
  if (!hostRef.value) return;
  spineRef.value = createSpine(hostRef.value, "spine/hero/Hero", {
    animation: "Idle",
  });
});

onBeforeUnmount(() => spineRef.value?.destroy());
</script>

<template>
  <div ref="hostRef" class="hero" />
</template>

React

import { useEffect, useRef } from "react";
import { createSpine, type SpineInstance } from "@brmtech/spine";

export function HeroSpine() {
  const hostRef = useRef<HTMLDivElement | null>(null);
  const spineRef = useRef<SpineInstance | null>(null);

  useEffect(() => {
    if (!hostRef.current) return;

    const spine = createSpine(hostRef.current, "spine/hero/Hero", {
      animation: "Idle",
    });
    spineRef.current = spine;

    return () => spine.destroy();
  }, []);

  return <div ref={hostRef} style={{ width: 160, height: 160 }} />;
}

资源地址规则

| 规则 | 说明 | 示例 | | -------------- | -------------------------------------------------- | ----------------------------------------------------- | | 推荐使用 src | src 是不带 .json / .atlas 的资源路径 | spine/hero/Hero | | 默认文件推导 | 自动加载 {src}.json{src}.atlas | spine/hero/Hero.jsonspine/hero/Hero.atlas | | baseUrl | 拼到相对资源路径前面 | https://cdn.example.com/assets/ + spine/hero/Hero | | 绝对 URL | src 是绝对 URL 或以 / 开头时,不拼接 baseUrl | https://cdn.example.com/Hero | | query/hash | 会保留 query 和 hash,并把扩展名加在 query 前 | Hero?v=1#main -> Hero.json?v=1#main | | 完整地址 | jsonUrl / atlasUrl 可直接指定完整文件地址 | jsonUrl: 'hero.json' | | 旧字段 | url 兼容旧代码,新代码建议用 src | url: 'spine/hero/Hero' | | 自定义规则 | resolveUrl 只用于特殊命名规则 | (src, kind) => ... |

导出 API

| 名称 | 类型 | 说明 | | ----------------------------------------------- | -------- | ---------------------------------------------------- | | createSpine(host, src, options?) | 函数 | 创建 Spine 实例,推荐写法 | | createSpine(host, options) | 函数 | 对象写法,适合把所有参数放在一个对象里 | | createSpineManager(options?) | 函数 | 创建独立 manager,用于自定义共享层、根节点或像素比例 | | getDefaultSpineManager(options?) | 函数 | 获取默认 manager;首次调用时会创建 | | destroyDefaultSpineManager() | 函数 | 销毁默认 manager 和它管理的共享 canvas | | SpineManager | 类 | manager 类,适合高级接入 | | resolveSpineUrls(options) | 工具函数 | 根据资源参数解析出 jsonUrl / atlasUrl | | defaultResolveUrl(src, kind, context) | 工具函数 | 默认资源地址解析函数 | | computeBounds(skeleton) | 工具函数 | 计算当前骨骼 bounds | | computeStableBounds(skeleton, animationState) | 工具函数 | 采样动画并计算稳定 bounds | | updateWorldTransform(skeleton) | 工具函数 | 兼容不同 spine-webgl 版本的世界矩阵更新方法 |

createSpine 签名

| 签名 | 推荐度 | 说明 | | ------------------------------------------------------ | ------ | ---------------------------------------------------- | | createSpine(host, src, options?) | 推荐 | 最简洁的创建方式 | | createSpine(host, options) | 推荐 | 所有参数都放在对象里 | | createSpine(host, src, options, legacyManagerConfig) | 兼容 | 旧用法兼容;新代码把 manager 放进 options | | createSpine(host, options, legacyManagerConfig) | 兼容 | 旧用法兼容;新代码把 managerOptions 放进 options |

createSpine 参数

| 参数 | 类型 | 必填 | 默认值 | 说明 | | --------------------- | -------------------------- | -------------- | ------ | ----------------------------------------------- | | host | HTMLElement | 是 | - | 承载动画的 DOM 元素,渲染器会跟随它的位置和尺寸 | | src | string | 字符串签名必填 | - | 资源路径,不包含 .json.atlas | | options | SpineCreateOptions | 否 | {} | 创建参数 | | legacyManagerConfig | CreateSpineManagerConfig | 否 | {} | 旧版 manager 参数,兼容保留 |

SpineCreateOptions

资源参数

| 参数 | 类型 | 必填 | 默认值 | 说明 | | ------------ | ------------------ | ---- | ------------------- | ------------------------------------------------------ | | src | string | 否 | - | 推荐资源路径,不包含 .json.atlas | | baseUrl | string | 否 | - | 相对资源路径的前缀,常用于 CDN 或静态资源目录 | | jsonUrl | string | 否 | - | JSON 文件地址;提供后覆盖 src / url 的 JSON 推导 | | atlasUrl | string | 否 | - | atlas 文件地址;提供后覆盖 src / url 的 atlas 推导 | | url | string | 否 | - | 旧版资源基础地址;兼容保留,新代码建议用 src | | resolveUrl | SpineUrlResolver | 否 | defaultResolveUrl | 自定义资源地址解析函数 |

动画参数

| 参数 | 类型 | 必填 | 默认值 | 说明 | | ------------- | --------- | ---- | ------------------- | ----------------------------------------------------- | | animation | string | 否 | skeleton 第一个动画 | 初始动画名;不存在时自动回退 | | loop | boolean | 否 | true | 初始动画是否循环 | | disableLoop | boolean | 否 | false | 旧配置兼容项;为 true 时关闭循环,优先级低于 loop | | speed | number | 否 | 1 | 播放速度 | | paused | boolean | 否 | false | 加载完成后是否暂停 |

外观参数

| 参数 | 类型 | 必填 | 默认值 | 说明 | | ---------- | -------- | ---- | ------ | ------------------------------------------------ | | skin | string | 否 | - | 初始皮肤名 | | skins | string | 否 | - | skin 的兼容别名 | | scale | number | 否 | 1 | 显示缩放;数值越大显示越大 | | overflow | number | 否 | 0.5 | 宿主元素外额外渲染区域,用于避免动画超出后被裁掉 |

渲染参数

| 参数 | 类型 | 必填 | 默认值 | 说明 | | ---------------- | --------------------- | ---- | ------------ | ------------------------------------------------------ | | layer | SpineLayer | 否 | low | 共享渲染层名;内置 lowhigh,也可传自定义字符串 | | exclusive | boolean | 否 | false | 是否在宿主元素内创建独立 canvas | | manager | SpineManager | 否 | 默认 manager | 指定自定义 manager | | managerOptions | SpineManagerOptions | 否 | - | 默认 manager 首次创建时使用的配置 |

回调参数

| 参数 | 类型 | 必填 | 默认值 | 说明 | | ------------ | ----------------------------------- | ---- | ------ | ---------------------------- | | onLoad | (instance: SpineInstance) => void | 否 | - | 资源和骨骼加载完成后触发 | | onError | (error: unknown) => void | 否 | - | 资源加载或骨骼创建失败时触发 | | onComplete | (entry: spine.TrackEntry) => void | 否 | - | 当前动画完成时触发 |

SpineInstance

属性

| 属性 | 类型 | 说明 | | ---------------- | ------------------------------ | ------------------------------------------- | | id | string | 实例 ID | | host | HTMLElement | 传入的宿主元素 | | ready | Promise<SpineInstance> | 加载完成后 resolve;加载失败会 reject | | skeleton | spine.Skeleton \| null | 加载后的 skeleton,未完成时为 null | | animationState | spine.AnimationState \| null | 加载后的 animation state,未完成时为 null |

方法

| 方法 | 参数 | 返回值 | 说明 | | --------------- | -------------------------------------------------------------- | ---------- | -------------------------------------------------------------------- | | play | animationName?: string, options?: SpinePlayOptions | void | 播放动画;不传动画名时重播当前动画 | | pause | - | void | 暂停动画时间推进,但保留当前画面 | | resume | - | void | 恢复播放 | | setSpeed | speed: number | void | 设置播放速度 | | setSkin | skinName: string | void | 切换皮肤,并重新计算 bounds | | setNumberSkin | skinName: string, attachmentName?: string, slotName?: string | void | 兼容数字皮肤场景;默认 attachment 为 Number,slot 为 SmartNumber | | getSkins | - | string[] | 返回 skeleton 中所有皮肤名 | | destroy | - | void | 销毁实例,注销渲染条目并释放独立 canvas |

SpinePlayOptions

| 参数 | 类型 | 必填 | 默认值 | 说明 | | ------------- | --------- | ---- | ------- | ---------------------- | | loop | boolean | 否 | true | 本次播放是否循环 | | pausedAfter | boolean | 否 | false | 设置动画后是否立即暂停 |

spine.play("Attack", { loop: false });
spine.play("Idle", { loop: true });
spine.play(undefined, { pausedAfter: true });

SpineManagerOptions

| 参数 | 类型 | 必填 | 默认值 | 说明 | | --------------------- | ---------------------------------- | ---- | --------------------- | ------------------------------- | | root | HTMLElement | 否 | document.body | 共享 canvas 的父节点 | | layers | Record<string, number \| string> | 否 | { low: 1, high: 5 } | 共享层 z-index 配置 | | maxDevicePixelRatio | number | 否 | 2 | WebGL backing buffer 的最大 DPR | | keepAlive | boolean | 否 | false | 没有实例时是否继续保持渲染循环 |

const manager = createSpineManager({
  layers: {
    background: 1,
    foreground: 20,
  },
  maxDevicePixelRatio: 2,
});

createSpine(host, "spine/hero/Hero", {
  layer: "foreground",
  manager,
});

类型

| 类型 | 定义 | 说明 | | -------------------------- | ------------------------------------------------------------------ | ------------------------- | | SpineLayer | 'low' \| 'high' \| string | 渲染层名称 | | SpineUrlResolver | (src, kind, context) => string | 自定义资源解析函数类型 | | SpineUrls | { jsonUrl: string; atlasUrl: string } | 解析后的资源地址 | | SpineRenderLayer | 内部接口 | 内部渲染层结构 | | SpineLoadedData | 内部接口 | 内部加载结果结构 | | CreateSpineManagerConfig | { manager?: SpineManager; managerOptions?: SpineManagerOptions } | 兼容旧签名的 manager 配置 |

注意事项

| 项目 | 说明 | | ------------ | ----------------------------------------------------------------- | | Spine 运行时 | 本包不实现 Spine 运行时,底层使用 @esotericsoftware/spine-webgl | | 框架入口 | 所有框架都通过核心入口 @brmtech/spine 使用,不提供框架专属入口 | | 共享 canvas | 适合页面内多处 Spine 动画 | | exclusive | 适合需要 canvas 跟随宿主元素隔离渲染的场景 |


English Guide

@brmtech/spine is a framework-agnostic Spine WebGL renderer. Native DOM, Vue, React, and other frontend frameworks all use the same core API: get a host element, then call createSpine.

Install

pnpm add @brmtech/spine @esotericsoftware/spine-webgl

Quick Start

import { createSpine } from "@brmtech/spine";

const host = document.querySelector("#hero") as HTMLElement;

const spine = createSpine(host, "spine/hero/Hero", {
  baseUrl: "https://cdn.example.com/assets/",
  animation: "Idle",
});

await spine.ready;
spine.play("Attack", { loop: false });

Framework Usage

| Scenario | How to Get Host | Creation | | ---------- | ------------------------------------------------ | ---------------------------------------------------------- | | Native DOM | document.querySelector('#hero') as HTMLElement | createSpine(host, 'spine/hero/Hero', options) | | Vue 3 | template ref + onMounted | createSpine(hostRef.value, 'spine/hero/Hero', options) | | React | useRef<HTMLDivElement>() + useEffect | createSpine(hostRef.current, 'spine/hero/Hero', options) |

Vue 3

<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from "vue";
import { createSpine, type SpineInstance } from "@brmtech/spine";

const hostRef = ref<HTMLElement | null>(null);
const spineRef = ref<SpineInstance | null>(null);

onMounted(() => {
  if (!hostRef.value) return;
  spineRef.value = createSpine(hostRef.value, "spine/hero/Hero", {
    animation: "Idle",
  });
});

onBeforeUnmount(() => spineRef.value?.destroy());
</script>

<template>
  <div ref="hostRef" class="hero" />
</template>

React

import { useEffect, useRef } from "react";
import { createSpine, type SpineInstance } from "@brmtech/spine";

export function HeroSpine() {
  const hostRef = useRef<HTMLDivElement | null>(null);
  const spineRef = useRef<SpineInstance | null>(null);

  useEffect(() => {
    if (!hostRef.current) return;

    const spine = createSpine(hostRef.current, "spine/hero/Hero", {
      animation: "Idle",
    });
    spineRef.current = spine;

    return () => spine.destroy();
  }, []);

  return <div ref={hostRef} style={{ width: 160, height: 160 }} />;
}

Resource URL Rules

| Rule | Description | Example | | ----------------- | -------------------------------------------------------------------- | ----------------------------------------------------- | | Prefer src | src is a resource path without .json / .atlas | spine/hero/Hero | | Default inference | Loads {src}.json and {src}.atlas automatically | spine/hero/Hero.json, spine/hero/Hero.atlas | | baseUrl | Prepended to relative resource paths | https://cdn.example.com/assets/ + spine/hero/Hero | | Absolute URL | baseUrl is not prepended when src is absolute or starts with / | https://cdn.example.com/Hero | | Query/hash | Query and hash are preserved, extension is inserted before query | Hero?v=1#main -> Hero.json?v=1#main | | Full URLs | jsonUrl / atlasUrl can point to exact files | jsonUrl: 'hero.json' | | Legacy field | url is kept for old code; prefer src | url: 'spine/hero/Hero' | | Custom rule | resolveUrl is only for unusual naming rules | (src, kind) => ... |

Exports

| Name | Type | Description | | ----------------------------------------------- | -------- | ----------------------------------------------------------------------- | | createSpine(host, src, options?) | function | Creates a Spine instance. Recommended form | | createSpine(host, options) | function | Object form for putting all options in one object | | createSpineManager(options?) | function | Creates an independent manager for custom layers, roots, or pixel ratio | | getDefaultSpineManager(options?) | function | Returns the default manager, creating it on first use | | destroyDefaultSpineManager() | function | Destroys the default manager and its shared canvases | | SpineManager | class | Manager class for advanced integrations | | resolveSpineUrls(options) | utility | Resolves resource options to jsonUrl / atlasUrl | | defaultResolveUrl(src, kind, context) | utility | Default resource resolver | | computeBounds(skeleton) | utility | Computes current skeleton bounds | | computeStableBounds(skeleton, animationState) | utility | Samples animation frames and computes stable bounds | | updateWorldTransform(skeleton) | utility | Compatibility helper for different spine-webgl world-transform APIs |

createSpine Signatures

| Signature | Recommendation | Description | | ------------------------------------------------------ | -------------- | ----------------------------------------------------------------------- | | createSpine(host, src, options?) | Recommended | Shortest creation form | | createSpine(host, options) | Recommended | Puts all options in one object | | createSpine(host, src, options, legacyManagerConfig) | Compatibility | Old manager argument; new code should put manager in options | | createSpine(host, options, legacyManagerConfig) | Compatibility | Old manager argument; new code should put managerOptions in options |

createSpine Parameters

| Parameter | Type | Required | Default | Description | | --------------------- | -------------------------- | ---------------------------- | ------- | -------------------------------------------------------------------------------- | | host | HTMLElement | Yes | - | DOM element that hosts the animation; the renderer follows its position and size | | src | string | Required in string signature | - | Resource path without .json or .atlas | | options | SpineCreateOptions | No | {} | Creation options | | legacyManagerConfig | CreateSpineManagerConfig | No | {} | Legacy manager argument kept for compatibility |

SpineCreateOptions

Resource Options

| Option | Type | Required | Default | Description | | ------------ | ------------------ | -------- | ------------------- | ---------------------------------------------------------------------- | | src | string | No | - | Recommended resource path without .json or .atlas | | baseUrl | string | No | - | Prefix for relative resource paths, usually a CDN or static asset root | | jsonUrl | string | No | - | JSON file URL; overrides JSON inference from src / url | | atlasUrl | string | No | - | atlas file URL; overrides atlas inference from src / url | | url | string | No | - | Legacy base resource URL; kept for compatibility. Prefer src | | resolveUrl | SpineUrlResolver | No | defaultResolveUrl | Custom resource resolver |

Animation Options

| Option | Type | Required | Default | Description | | ------------- | --------- | -------- | ------------------------ | ------------------------------------------------------------------------- | | animation | string | No | First skeleton animation | Initial animation name; falls back automatically | | loop | boolean | No | true | Whether the initial animation loops | | disableLoop | boolean | No | false | Compatibility flag; disables loop when true, lower priority than loop | | speed | number | No | 1 | Playback speed | | paused | boolean | No | false | Whether to pause after load |

Appearance Options

| Option | Type | Required | Default | Description | | ---------- | -------- | -------- | ------- | --------------------------------------------------- | | skin | string | No | - | Initial skin name | | skins | string | No | - | Compatibility alias for skin | | scale | number | No | 1 | Visual scale; larger values render larger | | overflow | number | No | 0.5 | Extra render area around the host to avoid clipping |

Rendering Options

| Option | Type | Required | Default | Description | | ---------------- | --------------------- | -------- | --------------- | -------------------------------------------------------------------------------- | | layer | SpineLayer | No | low | Shared render layer name; built-ins are low and high, custom strings allowed | | exclusive | boolean | No | false | Creates a dedicated canvas inside the host | | manager | SpineManager | No | Default manager | Custom manager instance | | managerOptions | SpineManagerOptions | No | - | Options used when the default manager is created for the first time |

Callbacks

| Option | Type | Required | Default | Description | | ------------ | ----------------------------------- | -------- | ------- | -------------------------------------------- | | onLoad | (instance: SpineInstance) => void | No | - | Called after assets and skeleton are ready | | onError | (error: unknown) => void | No | - | Called when assets or skeleton creation fail | | onComplete | (entry: spine.TrackEntry) => void | No | - | Called when the current animation completes |

SpineInstance

Properties

| Property | Type | Description | | ---------------- | ------------------------------ | ------------------------------------------------------- | | id | string | Instance ID | | host | HTMLElement | Host element passed to createSpine | | ready | Promise<SpineInstance> | Resolves after load, rejects on load failure | | skeleton | spine.Skeleton \| null | Loaded skeleton, or null before load completes | | animationState | spine.AnimationState \| null | Loaded animation state, or null before load completes |

Methods

| Method | Parameters | Return | Description | | --------------- | -------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------- | | play | animationName?: string, options?: SpinePlayOptions | void | Plays an animation; without a name, replays the current animation | | pause | - | void | Pauses animation time while keeping the current frame visible | | resume | - | void | Resumes playback | | setSpeed | speed: number | void | Sets playback speed | | setSkin | skinName: string | void | Changes skin and recomputes bounds | | setNumberSkin | skinName: string, attachmentName?: string, slotName?: string | void | Compatibility helper for number-skin use cases. Defaults: attachment Number, slot SmartNumber | | getSkins | - | string[] | Returns all skin names from the skeleton | | destroy | - | void | Destroys the instance, unregisters it, and releases its dedicated canvas if any |

SpinePlayOptions

| Option | Type | Required | Default | Description | | ------------- | --------- | -------- | ------- | -------------------------------------------------------- | | loop | boolean | No | true | Whether this playback loops | | pausedAfter | boolean | No | false | Whether to pause immediately after setting the animation |

spine.play("Attack", { loop: false });
spine.play("Idle", { loop: true });
spine.play(undefined, { pausedAfter: true });

SpineManagerOptions

| Option | Type | Required | Default | Description | | --------------------- | ---------------------------------- | -------- | --------------------- | ------------------------------------------------------------ | | root | HTMLElement | No | document.body | Parent element for shared canvases | | layers | Record<string, number \| string> | No | { low: 1, high: 5 } | z-index map for shared layers | | maxDevicePixelRatio | number | No | 2 | Maximum DPR for WebGL backing buffers | | keepAlive | boolean | No | false | Keeps the render loop alive when no instances are registered |

const manager = createSpineManager({
  layers: {
    background: 1,
    foreground: 20,
  },
  maxDevicePixelRatio: 2,
});

createSpine(host, "spine/hero/Hero", {
  layer: "foreground",
  manager,
});

Types

| Type | Definition | Description | | -------------------------- | ------------------------------------------------------------------ | ------------------------------------ | | SpineLayer | 'low' \| 'high' \| string | Render layer name | | SpineUrlResolver | (src, kind, context) => string | Custom resource resolver type | | SpineUrls | { jsonUrl: string; atlasUrl: string } | Resolved resource URLs | | SpineRenderLayer | internal interface | Internal render-layer structure | | SpineLoadedData | internal interface | Internal loaded-data structure | | CreateSpineManagerConfig | { manager?: SpineManager; managerOptions?: SpineManagerOptions } | Manager config for legacy signatures |

Notes

| Item | Description | | --------------- | ------------------------------------------------------------------------------------------- | | Spine runtime | This package does not implement Spine itself. It uses @esotericsoftware/spine-webgl | | Framework entry | All frameworks use the core @brmtech/spine entry. No framework-specific entry is provided | | Shared canvas | Useful for many animations on one page | | exclusive | Useful when a canvas should stay isolated inside its host element |