first commit
This commit is contained in:
165
README.md
Normal file
165
README.md
Normal file
@@ -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('<button>Click</button>', 300, 200);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### SVG 元素 (CreateSvg)
|
||||||
|
```javascript
|
||||||
|
const svg = engine.CreateSvg(400, 300, 100, 100, '<circle cx="50" cy="50" r="40"/>');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 元素通用方法
|
||||||
|
所有创建的元素支持以下方法:
|
||||||
|
|
||||||
|
| 方法 | 说明 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| `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(
|
||||||
|
'<button style="padding: 8px 16px; background: blue; color: white;">开始</button>',
|
||||||
|
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 操作,无需额外依赖。
|
354
index.js
Normal file
354
index.js
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user