commit e9f068e6f72327834dacb8ba4430e5d46ab09947 Author: 袁涛 Date: Tue Aug 12 00:13:12 2025 +0800 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..5e9509a --- /dev/null +++ b/README.md @@ -0,0 +1,165 @@ +# DOM 渲染引擎 + +这个轻量级 JavaScript 渲染引擎使用 DOM 元素创建 2D 场景,支持精灵、容器、文本、HTML 和 SVG 等元素,提供简单直观的 API 进行场景管理。 + +## 功能特性 + +- 🎭 创建可自定义尺寸的舞台容器 +- 🖼️ 支持加载和显示图像精灵 +- 📦 创建容器元素用于分组管理 +- ✏️ 文本元素创建和样式设置 +- 🌐 支持原生 HTML 内容嵌入 +- 🎨 SVG 矢量图形支持 +- 📍 精确控制元素位置和尺寸 +- 🎭 简单的事件绑定机制 + +## 快速开始 + +### 基本用法 + +```javascript +// 创建引擎实例 +const game = new Engine('#game-container', { + width: 800, + height: 600, + background: '#f0f0f0' +}); + +// 加载资源 +game.Load('character.png') + .then(img => { + // 创建精灵 + const player = game.CreateSprite(img, 100, 100, 50, 50); + + // 添加到舞台 + game.stage.add(player); + + // 绑定点击事件 + player.on('click', () => { + player.setPosition(200, 150); + }); + }); +``` + +## API 参考 + +### 引擎初始化 +```javascript +const engine = new Engine(elementSelector, options); +``` +| 参数 | 类型 | 默认值 | 说明 | +|------|------|---------|------| +| `elementSelector` | string | `'body'` | 挂载舞台的 DOM 选择器 | +| `options.width` | number | 800 | 舞台宽度(px) | +| `options.height` | number | 600 | 舞台高度(px) | +| `options.background` | string | `'transparent'` | 舞台背景色 | + +### 资源加载 +```javascript +engine.Load(imageUrl) + .then(img => { /* 使用图像 */ }) + .catch(error => { /* 处理错误 */ }); +``` + +### 创建元素 + +#### 精灵 (CreateSprite) +```javascript +const sprite = engine.CreateSprite(imgElement, x, y, width, height); +``` +- 支持位置和尺寸控制 +- 支持事件绑定 + +#### 容器 (CreateBox) +```javascript +const container = engine.CreateBox(x, y, width, height); +``` +- 用于元素分组管理 +- 支持嵌套结构 + +#### 文本 (CreateText) +```javascript +const text = engine.CreateText('Hello', 50, 50); +text.setColor('#ff0000'); +text.setFont('bold 24px Arial'); +``` + +#### HTML 元素 (CreateHtml) +```javascript +const html = engine.CreateHtml('', 300, 200); +``` + +#### SVG 元素 (CreateSvg) +```javascript +const svg = engine.CreateSvg(400, 300, 100, 100, ''); +``` + +### 元素通用方法 +所有创建的元素支持以下方法: + +| 方法 | 说明 | 示例 | +|------|------|------| +| `setPosition(x, y)` | 设置元素位置 | `sprite.setPosition(150, 200)` | +| `setSize(width, height)` | 设置元素尺寸 | `box.setSize(100, 80)` | +| `setStyle(cssText)` | 添加自定义样式 | `text.setStyle('opacity: 0.8;')` | +| `add(child)` | 添加子元素 | `container.add(sprite)` | +| `remove(child)` | 移除子元素 | `container.remove(sprite)` | +| `on(event, callback)` | 绑定事件 | `sprite.on('click', handleClick)` | +| `off(event, callback)` | 解绑事件 | `sprite.off('click', handleClick)` | +| `hide()` | 隐藏元素 | `sprite.hide()` | +| `show()` | 显示元素 | `sprite.show()` | + +### 销毁引擎 +```javascript +engine.Destroy(); +``` + +## 使用示例 + +### 创建游戏角色 +```javascript +const game = new Engine('#game', { + width: 1024, + height: 768 +}); + +game.Load('player.png').then(playerImg => { + const player = game.CreateSprite(playerImg, 512, 384, 64, 64); + + // 添加角色名标签 + const nameTag = game.CreateText('Player 1', 0, -20); + nameTag.setColor('gold'); + nameTag.setFont('bold 14px sans-serif'); + + player.add(nameTag); + game.stage.add(player); +}); +``` + +### 创建 UI 面板 +```javascript +const panel = game.CreateBox(50, 50, 200, 300); +panel.setStyle('background: rgba(0,0,0,0.7); border-radius: 10px;'); + +const title = game.CreateText('控制面板', 20, 20); +title.setStyle('font-size: 20px; color: white;'); + +const button = game.CreateHtml( + '', + 50, 80 +); + +panel.add(title); +panel.add(button); +game.stage.add(panel); +``` + +## 注意事项 + +1. 所有元素位置基于舞台左上角(0,0)计算 +2. 使用 `Destroy()` 方法会完全移除舞台并清理内存 +3. 图像元素自动添加 `user-drag: none` 防止拖动 +4. 舞台默认启用硬件加速 (`transform: translateZ(0)`) +5. 每个引擎实例有唯一ID标识 (`data-engine-id`) + +这个轻量级引擎适合创建简单的 2D 界面和游戏原型,使用原生 DOM 操作,无需额外依赖。 \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..fa52c74 --- /dev/null +++ b/index.js @@ -0,0 +1,354 @@ +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 = document.querySelector(element) + this.stage = document.createElement('div') + // 引擎ID + const engineId = new Date().getTime() + // 转换为16进制 + const engineIdHex = engineId.toString(16) + this.id = engineIdHex + this.stage.setAttribute('data-engine-id', this.id) + // 设置舞台样式 + this.stage.style.width = `${this.options.width}px` + this.stage.style.height = `${this.options.height}px` + this.stage.style.position = 'relative' + this.stage.style.userSelect = 'none' + this.stage.style.overflow = 'hidden' + this.stage.style.background = this.options.background + // 启用硬件加速 + this.stage.style.transform = 'translateZ(0)' + + // 添加子元素 + this.stage.add = function (child) { + child && this.appendChild(child) + } + // 移除子元素 + this.stage.remove = function (child) { + child && this.removeChild(child) + } + + this.element.appendChild(this.stage) + return this + } + // 加载资源 + Load(src = '') { + return new Promise((resolve, reject) => { + let img = new Image() + img.src = src + img.onload = () => { + resolve(img) + } + img.onerror = e => { + reject(e) + } + }) + } + // 创建精灵 + CreateSprite(img, x = 0, y = 0, width = 0, height = 0) { + const div = document.createElement('div') + const imgElement = document.createElement('img') + imgElement.src = img.src + imgElement.style.cssText = '-webkit-user-drag: none;user-drag: none;' + div.style.position = 'absolute' + + div.setAttribute('data-sprite-id', this.id) + div.appendChild(imgElement) + this.images.push(imgElement) + + // 设置图片位置 + div.setPosition = function (x = 0, y = 0) { + div.style.left = `${x}px` + div.style.top = `${y}px` + div.x = x + div.y = y + } + div.setPosition(x, y) + // 设置图片尺寸 + div.setSize = function (width = 0, height = 0) { + if (width) { + if (typeof width === 'number') { + div.style.width = `${width}px` + } else { + div.style.width = width + } + } + if (height) { + if (typeof height === 'number') { + div.style.height = `${height}px` + } else { + div.style.height = height + } + } + div.width = width + div.height = height + } + div.setSize(width, height) + // 设置样式 + div.setStyle = function (style) { + div.style.cssText += style + } + // 添加子元素 + div.add = function (child) { + child && div.appendChild(child) + } + // 移除子元素 + div.remove = function (child) { + child && div.removeChild(child) + } + // 添加事件 + div.on = function (event, callback) { + div.addEventListener(event, callback) + } + // 移除事件 + div.off = function (event, callback) { + div.removeEventListener(event, callback) + } + // 添加动画 + div.animate = function (keyframes) { + div.style.keyframes = keyframes + } + // 设置别名 + div.name = function (name) { + div.setAttribute('data-name', name) + } + // 隐藏图片 + div.hide = function () { + div.style.display = 'none' + } + // 显示图片 + div.show = function () { + div.style.display = 'block' + } + return div + } + // 创建容器 + CreateBox(x = 0, y = 0, width = 0, height = 0) { + const div = document.createElement('div') + div.style.position = 'absolute' + div.setAttribute('data-box-id', this.id) + + // 设置容器位置 + div.setPosition = function (x = 0, y = 0) { + div.style.left = `${x}px` + div.style.top = `${y}px` + div.x = x + div.y = y + } + div.setPosition(x, y) + // 设置容器尺寸 + div.setSize = function (width = 0, height = 0) { + if (width) { + if (typeof width === 'number') { + div.style.width = `${width}px` + } else { + div.style.width = width + } + } + if (height) { + if (typeof height === 'number') { + div.style.height = `${height}px` + } else { + div.style.height = height + } + } + div.width = width + div.height = height + } + div.setSize(width, height) + // 设置容器样式 + div.setStyle = function (style) { + div.style.cssText += style + } + // 添加子元素 + div.add = function (child) { + child && div.appendChild(child) + } + // 移除子元素 + div.remove = function (child) { + child && div.removeChild(child) + } + // 添加事件 + div.on = function (event, callback) { + div.addEventListener(event, callback) + } + // 移除事件 + div.off = function (event, callback) { + div.removeEventListener(event, callback) + } + // 隐藏容器 + div.hide = function () { + div.style.display = 'none' + } + // 显示容器 + div.show = function () { + div.style.display = 'block' + } + return div + } + // 创建文本 + CreateText(text = '', x = 0, y = 0) { + const div = document.createElement('div') + div.style.position = 'absolute' + div.setAttribute('data-text-id', this.id) + + // 设置文本位置 + div.setPosition = function (x = 0, y = 0) { + div.style.left = `${x}px` + div.style.top = `${y}px` + div.x = x + div.y = y + } + div.setPosition(x, y) + // 设置文本内容 + div.set = function (text = '') { + div.innerText = text + } + div.set(text) + // 设置文本颜色 + div.setColor = function (color = '#000') { + div.style.color = color + } + div.setColor(color) + // 设置文本字体 + div.setFont = function (font = '16px sans-serif') { + div.style.font = font + } + // 设置样式 + div.setStyle = function (style = 'color: #000;') { + div.style.cssText += style + } + // 添加子元素 + div.add = function (child) { + child && div.appendChild(child) + } + // 移除子元素 + div.remove = function (child) { + child && div.removeChild(child) + } + return div + } + // 创建Html文本 + CreateHtml(html = '', x = 0, y = 0) { + const div = document.createElement('div') + div.style.position = 'absolute' + div.setAttribute('data-html-id', this.id) + + // 设置Html位置 + div.setPosition = function (x = 0, y = 0) { + div.style.left = `${x}px` + div.style.top = `${y}px` + div.x = x + div.y = y + } + div.setPosition(x, y) + // 设置Html内容 + div.set = function (html = '') { + div.innerHTML = html + } + // 获取Html内容 + div.get = function () { + return div.innerHTML + } + div.set(html) + // 设置样式 + div.setStyle = function (style = 'color: #000;') { + div.style.cssText += style + } + div.setStyle() + // 获取尺寸 + div.getSize = function () { + const { width, height } = div.getBoundingClientRect() + return { width, height } + } + // 添加子元素 + div.add = function (child) { + child && div.appendChild(child) + } + // 移除子元素 + div.remove = function (child) { + child && div.removeChild(child) + } + return div + } + // 创建svg + CreateSvg(x = 0, y = 0, width = 0, height = 0, content = '') { + const div = document.createElement('div') + div.innerHTML = content + div.style.position = 'absolute' + div.setAttribute('data-svg-id', this.id) + + // 设置svg位置 + div.setPosition = function (x = 0, y = 0) { + div.style.left = `${x}px` + div.style.top = `${y}px` + div.x = x + div.y = y + } + div.setPosition(x, y) + // 设置svg尺寸 + div.setSize = function (width = 0, height = 0) { + if (width) { + if (typeof width === 'number') { + div.style.width = `${width}px` + } else { + div.style.width = width + } + } + if (height) { + if (typeof height === 'number') { + div.style.height = `${height}px` + } else { + div.style.height = height + } + } + div.width = width + div.height = height + } + div.setSize(width, height) + // 设置样式 + div.setStyle = function (style) { + div.style.cssText += style + } + // 设置svg内容 + div.setContent = function (content = '') { + div.innerHTML = content + } + // 添加事件 + div.on = function (event, callback) { + div.addEventListener(event, callback) + } + // 移除事件 + div.off = function (event, callback) { + div.removeEventListener(event, callback) + } + return div + } + // 销毁舞台 + Destroy() { + this.element.removeChild(this.stage) + // 内存回收 + this.images = null + this.stage = null + this.element = null + this.options = null + this.width = null + this.height = null + this.id = null + } +}