UNPKG

minecards-renderer

Version:

A library for displaying interactive 3D collectible cards for Telegram bot Minecrads and their web interface.

73 lines (72 loc) 4.01 kB
import * as h from "skin3d"; class l { constructor(e, t) { this.container = e, this.data = t, this.elements = {}, this.skinViewer = null, this.animationFrameId = null; const i = this.container.getBoundingClientRect(); this.width = i.width, this.height = i.height, this.container.style.perspective = "1500px", this.handleMouseMove = this.handleMouseMove.bind(this), this.handleMouseLeave = this.handleMouseLeave.bind(this), this.init(); } async init() { this.createDOM(), await this.applyData(), this.setupSkinViewer(), this.setupInteractions(); } createDOM() { this.container.innerHTML = ` <div class="card"> <div class="glare"></div> <canvas class="card-skin-viewer"></canvas> <img class="card-pack-logo" alt="Pack Logo"> <div class="card-sticker-layer"></div> </div> `, this.elements.card = this.container.querySelector(".card"), this.elements.skinCanvas = this.container.querySelector(".card-skin-viewer"), this.elements.packLogo = this.container.querySelector(".card-pack-logo"), this.elements.stickerLayer = this.container.querySelector(".card-sticker-layer"); } async applyData() { if (this.elements.card.classList.add(`rarity-${this.data.rarity}`), this.elements.card.style.backgroundImage = `url('${this.data.backgroundImage}')`, this.elements.packLogo.src = this.data.packLogoImage, this.data.stickerUrl) try { const e = await fetch(this.data.stickerUrl); if (!e.ok) throw new Error(`HTTP error! status: ${e.status}`); const t = await e.text(); this.elements.stickerLayer.innerHTML = t; } catch (e) { console.error(`Could not load sticker from ${this.data.stickerUrl}:`, e); } } setupSkinViewer() { this.skinViewer = new h.View({ canvas: this.elements.skinCanvas, width: this.width, height: this.height, background: null }), this.skinViewer.controls.enableRotate = !1, this.skinViewer.controls.enableZoom = !1; const e = this.height / 560; this.skinViewer.camera.fov = 75 - this.width / this.height * 10, this.skinViewer.camera.updateProjectionMatrix(), this.skinViewer.camera.position.set(-25 * e, 35 * e, 50 * e), this.skinViewer.camera.lookAt(0, 0, 0), this.skinViewer.loadSkin(this.data.skinImage).then(() => { const t = this.skinViewer.playerObject; t.scale.set(e, e, e); const i = t.skin; i.leftArm.rotation.x = -Math.PI / 4, i.rightArm.rotation.x = Math.PI / 4, i.leftLeg.rotation.x = Math.PI / 5, i.rightLeg.rotation.x = -Math.PI / 5; const n = Math.PI / 6, a = 0.5, s = (r) => { if (!this.skinViewer) return; const o = n * Math.sin(r / 1e3 * a); i.rotation.y = o, this.animationFrameId = requestAnimationFrame(s); }; this.animationFrameId = requestAnimationFrame(s); }).catch((t) => { console.error("Failed to load skin:", t); }); } handleMouseMove(e) { const t = this.container.getBoundingClientRect(), i = e.clientX - t.left, n = e.clientY - t.top, a = t.width / 2, s = t.height / 2, r = (n - s) / s * -10, o = (i - a) / a * 10; this.elements.card.style.transform = `rotateX(${r}deg) rotateY(${o}deg)`; } handleMouseLeave() { this.elements.card.style.transform = "rotateX(0deg) rotateY(0deg)"; } setupInteractions() { this.container.addEventListener("mousemove", this.handleMouseMove), this.container.addEventListener("mouseleave", this.handleMouseLeave); } destroy() { console.log("Destroying card instance..."), this.container.removeEventListener("mousemove", this.handleMouseMove), this.container.removeEventListener("mouseleave", this.handleMouseLeave), this.animationFrameId && cancelAnimationFrame(this.animationFrameId), this.skinViewer && typeof this.skinViewer.dispose == "function" && this.skinViewer.dispose(), this.skinViewer = null, this.container.innerHTML = "", this.elements = {}; } } export { l as Card };