feat:加上 Vue 学习站与可嵌入机芯
站点按 bloub 三栏做自定义、动画时间线和设置,支持序列导出。 形体只留常见几种并配中文名;动画页右栏拉满时间线旁空位,滚动条隐藏。 机芯补 GROK.create / <grok-bot>,以及暂停冻结。
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
/* L2 — public factory + <grok-bot>. Skin: eyes / shapes / playlist / palette. */
|
||||
(function (g) {
|
||||
const ATTRS = ["state", "shape", "color", "size", "paused", "follow", "mode", "emphasis"];
|
||||
|
||||
function mergeSkin(opts = {}) {
|
||||
const base = opts.geo || g.GROK_GEO;
|
||||
return {
|
||||
...base,
|
||||
eyes: opts.eyes || base.eyes,
|
||||
shapes: opts.shapes ? { ...base.shapes, ...opts.shapes } : base.shapes,
|
||||
palette: opts.palette ? { ...base.palette, ...opts.palette } : base.palette,
|
||||
solids: opts.solids || base.solids,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveSvg(target) {
|
||||
if (!target) throw new Error("GROK.create: missing target");
|
||||
if (typeof target === "string") {
|
||||
const el = document.querySelector(target);
|
||||
if (!el) throw new Error("GROK.create: no match for " + target);
|
||||
target = el;
|
||||
}
|
||||
if (target.tagName === "SVG" || target instanceof SVGSVGElement) return target;
|
||||
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
target.appendChild(svg);
|
||||
return svg;
|
||||
}
|
||||
|
||||
function createCharacter(target, opts = {}) {
|
||||
const svg = resolveSvg(target);
|
||||
const geo = mergeSkin(opts);
|
||||
const playlist = opts.playlist
|
||||
? { ...g.GROK_TABLES.EYE_PLAYLIST, ...opts.playlist }
|
||||
: g.GROK_TABLES.EYE_PLAYLIST;
|
||||
const bot = new g.GrokCharacter(svg, { mode: "hold", ...opts, geo, playlist });
|
||||
return {
|
||||
bot,
|
||||
svg,
|
||||
setState: (name, o) => bot.setState(name, o),
|
||||
setShape: (name) => bot.setShape(name),
|
||||
setColor: (id, scheme) => bot.setColor(id, scheme),
|
||||
setPaused: (v) => bot.setPaused(v),
|
||||
setMode: (m) => bot.setMode(m),
|
||||
setFollowPointer: (v) => bot.setFollowPointer(v),
|
||||
setEmphasis: (v) => bot.setEmphasis(v),
|
||||
spin: (n) => bot.spinOnce(n),
|
||||
bounce: () => bot.bounceOnce(),
|
||||
burst: () => bot.burstOnce(),
|
||||
destroy: () => bot.destroy(),
|
||||
snapshot: () => bot.snapshot(),
|
||||
};
|
||||
}
|
||||
|
||||
class GrokBotElement extends HTMLElement {
|
||||
static get observedAttributes() { return ATTRS; }
|
||||
|
||||
connectedCallback() {
|
||||
if (this._handle) return;
|
||||
const root = this.attachShadow({ mode: "open" });
|
||||
const box = document.createElement("div");
|
||||
box.setAttribute("part", "disk");
|
||||
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svg.setAttribute("role", "img");
|
||||
svg.setAttribute("aria-label", this.getAttribute("label") || "character");
|
||||
box.appendChild(svg);
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `:host{display:inline-block;line-height:0}div{width:100%;height:100%}svg{width:100%;height:100%;overflow:visible}`;
|
||||
root.append(style, box);
|
||||
this._applySize();
|
||||
this._handle = createCharacter(svg, this._opts());
|
||||
this.bot = this._handle.bot;
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this._handle?.destroy();
|
||||
this._handle = null;
|
||||
this.bot = null;
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, prev, next) {
|
||||
if (!this._handle || prev === next) return;
|
||||
this._applySize();
|
||||
if (name === "state") this._handle.setState(next || "idle");
|
||||
else if (name === "shape") this._handle.setShape(next || "blob");
|
||||
else if (name === "color") this._handle.setColor(next || "black");
|
||||
else if (name === "paused") this._handle.setPaused(this.hasAttribute("paused"));
|
||||
else if (name === "follow") this._handle.setFollowPointer(next === "true");
|
||||
else if (name === "emphasis") this._handle.setEmphasis(this.hasAttribute("emphasis"));
|
||||
else if (name === "mode") this._handle.setMode(next || "hold");
|
||||
}
|
||||
|
||||
_applySize() {
|
||||
const n = Number(this.getAttribute("size"));
|
||||
if (n > 0) {
|
||||
this.style.width = `${n}px`;
|
||||
this.style.height = `${n}px`;
|
||||
}
|
||||
}
|
||||
|
||||
_opts() {
|
||||
const size = Number(this.getAttribute("size"));
|
||||
return {
|
||||
state: this.getAttribute("state") || "idle",
|
||||
shape: this.getAttribute("shape") || "blob",
|
||||
color: this.getAttribute("color") || "black",
|
||||
sizePx: size > 0 ? size : null,
|
||||
paused: this.hasAttribute("paused"),
|
||||
followPointer: this.getAttribute("follow") === "true",
|
||||
emphasis: this.hasAttribute("emphasis"),
|
||||
mode: this.getAttribute("mode") || "hold",
|
||||
loginWrap: this.getAttribute("login") !== "false",
|
||||
};
|
||||
}
|
||||
|
||||
setState(name) { this._handle?.setState(name); }
|
||||
setShape(name) { this._handle?.setShape(name); }
|
||||
setColor(id) { this._handle?.setColor(id); }
|
||||
setPaused(v) { this._handle?.setPaused(v); }
|
||||
spin(n) { this._handle?.spin(n); }
|
||||
bounce() { this._handle?.bounce(); }
|
||||
destroy() { this._handle?.destroy(); }
|
||||
}
|
||||
|
||||
function define(name = "grok-bot") {
|
||||
if (!customElements.get(name)) customElements.define(name, GrokBotElement);
|
||||
return name;
|
||||
}
|
||||
|
||||
define();
|
||||
g.GROK = {
|
||||
create: createCharacter,
|
||||
define,
|
||||
mergeSkin,
|
||||
Character: g.GrokCharacter,
|
||||
};
|
||||
})(window);
|
||||
Reference in New Issue
Block a user