Three.js-based Minecraft skin viewer for LeiCraftClient, with support for LeiCraft cosmetics (Blockbench models attached to the player's body parts). It is a fork of skinview3d, kept in sync with upstream (currently v3.4.2, three.js r156), and keeps skinview3d's API.
- Bun runtime, package manager and test runner
- TypeScript (strict, ESM), Biome formatter/linter
- tsc for the ES modules, Rollup + SWC for the browser bundle
- AGPL-3.0, with MIT-licensed upstream code (see THIRD_PARTY_NOTICES.md)
bun run build writes bundles/skinview3d.bundle.js, a single file with three.js included that
defines the global skinview3d (a drop-in replacement for the V1 bundle). Pipelines on main
also keep it as an artifact.
<canvas id="skin_container"></canvas>
<script src="skinview3d.bundle.js"></script>
<script>
const skinViewer = new skinview3d.SkinViewer({
canvas: document.getElementById("skin_container"),
width: 300,
height: 400,
skin: "https://example.com/skin.png",
});
skinViewer.loadCape("https://example.com/cape.png");
skinViewer.loadCosmetics("https://example.com/item/texture.png", "https://example.com/item/model.cfg");
skinViewer.animation = new skinview3d.WalkingAnimation();
</script>The package @leicraftmc/skinviewerjs (libs/ with type declarations, plus bundles/,
assets/ and the vendored skinview-utils/) depends on three ^0.156.0. It is published to the
GitLab npm registry of git.leicraftmc.de. Point the @leicraftmc scope at the instance-level
endpoint in your project's .npmrc (the project is public, reading needs no token):
@leicraftmc:registry=https://git.leicraftmc.de/api/v4/packages/npm/bun add @leicraftmc/skinviewerjsimport { SkinViewer, WalkingAnimation } from "@leicraftmc/skinviewerjs";bun pm pack builds a local tarball (leicraftmc-skinviewerjs-<version>.tgz) instead; pipelines
on main keep it as an artifact.
For everything that is not LeiCraft-specific (animations, name tags, ears, panoramas, camera, offscreen rendering) see the skinview3d README.
| API | Description |
|---|---|
loadCosmetics(texture, config) |
Adds a cosmetic. texture is an image URL or source, config the model config. Can be called several times to stack cosmetics. |
loadCosmetics(null) / resetCosmetics() |
Removes the cosmetics. |
cosmetics option |
[{ source, config }, …] loaded by the SkinViewer constructor. |
playerObject.cosmetics |
The loaded CosmeticsObjects. |
config is fetched when it is an http(s):// URL, an absolute path (/…) or starts with ./.
Anything else, including a relative path like img/model.cfg, is parsed as the JSON content itself.
Cosmetics use Blockbench's "PlayerItem" JSON export (.cfg). The parser reads:
textureSize:[width, height]of the texturemodels[]:attachTo(head,body,rightArm,leftArm,rightLeg,leftLeg),translate,rotate(degrees),boxes, and nestedsubmodelswith the same fieldsboxes[]:coordinates([x, y, z, width, height, depth]), optionalsizeAdd, and eithertextureOffset(Minecraft box UV layout) or per-faceuvNorth,uvEast,uvSouth,uvWest,uvUp,uvDown
invertAxis is ignored: the parser always mirrors the x and z axes, and the existing models are
built for that. Models attached to body are moved up by 6 units. examples/img/ has three example models.
These behaviors are unchanged from the V1 production viewer:
resetCosmetics()removes one cosmetic group per body part, so a part with several cosmetic models keeps all but one.- Cosmetic textures are not disposed when cosmetics are reset.
resetSkin()/loadSkin(null)shows a transparent skin instead of hiding the player (upstream hides it), so cosmetics stay visible.
bun install
bun run dev # rebuild the bundle on change and serve http://localhost:12540/examples/| Script | Description |
|---|---|
bun run build |
ES modules (libs/) and browser bundle (bundles/) |
bun pm pack |
Clean build, then the package tarball |
bun publish |
Done by CI: a v<version> tag publishes the package (.gitlab/ci/release.yml) |
bun run dev |
Bundle watch mode plus the examples server (scripts/serve.ts, port 12540) |
bun test |
Cosmetics parser and geometry snapshot tests |
bun run typecheck |
TypeScript check |
bun run check / format / lint |
Biome |
The examples page (examples/index.html) lets you switch the example cosmetics and animations.
examples/offscreen-render.html renders skins to images.
- Bump
"version"inpackage.json(semver; the bundle API is the compatibility contract). - Commit, then tag
v<version>and push the tag. - The
publish:packagejob publishes@leicraftmc/skinviewerjs@<version>to the GitLab npm registry. The website picks it up withbun update @leicraftmc/skinviewerjs.
- Diff the upstream tags, for example
git diff v3.4.2 <new tag> -- src/. src/animation.ts,src/nametag.tsandsrc/skinview3d.tshave no LeiCraft changes. Copy upstream's files over them unchanged; Biome ignores them, so they stay byte-identical.- Merge
src/model.tsandsrc/viewer.tsby hand, keeping the cosmetics code listed in THIRD_PARTY_NOTICES.md, then runbun run format. Upstream importsskinview-utilsfrom npm; here it is the vendored../skinview-utils/index.js. - Keep
threeon the version upstream uses, and update its pin in.gitlab/renovate.json. - Run
bun test. A changed geometry snapshot means the cosmetics render differently; compare the result against the previous bundle in a browser before updating the snapshot.
AGPL-3.0, see LICENSE. Code from skinview3d, skinview-utils and three.js stays under the MIT license, see THIRD_PARTY_NOTICES.md.