class BaseElement { constructor(elementType = 'div', engineId) { this.element = document.createElement(elementType) this.element.style.position = 'absolute' this.engineId = engineId this.x = 0 this.y = 0 this.width = 0 this.height = 0 } setPosition(x = 0, y = 0) { this.element.style.left = `${x}px` this.element.style.top = `${y}px` this.x = x this.y = y } setSize(width = 0, height = 0) { if (width) { this.element.style.width = typeof width === 'number' ? `${width}px` : width this.width = width } if (height) { this.element.style.height = typeof height === 'number' ? `${height}px` : height this.height = height } } setStyle(style) { Object.assign(this.element.style, style) } add(child) { child && this.element.appendChild(child.element || child) } remove(child) { child && this.element.removeChild(child.element || child) } on(event, callback) { this.element.addEventListener(event, callback) } off(event, callback) { this.element.removeEventListener(event, callback) } hide() { this.element.style.display = 'none' } show() { this.element.style.display = 'block' } } class Sprite extends BaseElement { constructor(img, x, y, width, height, engineId) { super('div', engineId) this.imgElement = document.createElement('img') this.imgElement.src = img.src this.imgElement.style.cssText = '-webkit-user-drag: none; user-drag: none;' this.element.appendChild(this.imgElement) this.setPosition(x, y) this.setSize(width, height) this.element.setAttribute('data-sprite-id', engineId) // 精灵特有方法 this.name = name => this.element.setAttribute('data-name', name) this.animate = keyframes => (this.element.style.animation = keyframes) } } class Box extends BaseElement { constructor(x, y, width, height, engineId) { super('div', engineId) this.setPosition(x, y) this.setSize(width, height) this.element.setAttribute('data-box-id', engineId) } } class TextElement extends BaseElement { constructor(text, x, y, engineId) { super('div', engineId) this.element.innerText = text this.setPosition(x, y) this.element.setAttribute('data-text-id', engineId) // 文本特有方法 this.set = text => (this.element.innerText = text) this.setColor = color => (this.element.style.color = color) this.setFont = font => (this.element.style.font = font) } } class HtmlElement extends BaseElement { constructor(html, x, y, engineId) { super('div', engineId) this.element.innerHTML = html this.setPosition(x, y) this.element.setAttribute('data-html-id', engineId) // HTML特有方法 this.set = html => (this.element.innerHTML = html) this.get = () => this.element.innerHTML this.getSize = () => { const rect = this.element.getBoundingClientRect() return { width: rect.width, height: rect.height } } } } class SvgElement extends BaseElement { constructor(x, y, width, height, content, engineId) { super('div', engineId) this.element.innerHTML = content this.setPosition(x, y) this.setSize(width, height) this.element.setAttribute('data-svg-id', engineId) // SVG特有方法 this.setContent = content => (this.element.innerHTML = content) } } class Engine { constructor(element = document.body, options = {}) { const defaultOptions = { width: 800, height: 600, background: 'transparent', } this.options = { ...defaultOptions, ...options } this.images = [] this.width = this.options.width this.height = this.options.height this.element = typeof element === 'string' ? document.querySelector(element) : element this.id = Date.now().toString(16) this.createStage() } createStage() { this.stage = document.createElement('div') this.stage.setAttribute('data-engine-id', this.id) Object.assign(this.stage.style, { width: `${this.options.width}px`, height: `${this.options.height}px`, position: 'relative', userSelect: 'none', overflow: 'hidden', background: this.options.background, transform: 'translateZ(0)', }) // 添加舞台操作方法 this.stage.add = child => child && this.stage.appendChild(child.element || child) this.stage.remove = child => child && this.stage.removeChild(child.element || child) this.element.appendChild(this.stage) } load(src = '') { return new Promise((resolve, reject) => { const img = new Image() img.src = src img.onload = () => resolve(img) img.onerror = reject this.images.push(img) }) } createSprite(img, x = 0, y = 0, width = 0, height = 0) { return new Sprite(img, x, y, width, height, this.id) } createBox(x = 0, y = 0, width = 0, height = 0) { return new Box(x, y, width, height, this.id) } createText(text = '', x = 0, y = 0) { return new TextElement(text, x, y, this.id) } createHtml(html = '', x = 0, y = 0) { return new HtmlElement(html, x, y, this.id) } createSvg(x = 0, y = 0, width = 0, height = 0, content = '') { return new SvgElement(x, y, width, height, content, this.id) } destroy() { if (this.stage && this.stage.parentNode) { this.stage.parentNode.removeChild(this.stage) } // 清理所有图像引用 this.images.forEach(img => { img.onload = null img.onerror = null img.src = '' }) // 清除所有引用 this.images = [] this.stage = null this.element = null this.options = null } }