feat: 新增 page3 演职员表并拆分多页资源
- 新增 page3.html:无「天」背景的演职员表与火山场景 - 重构为页面专属 CSS/JS 与共用的 text-scene 渲染引擎 - PDF 导出支持 --page 参数
This commit is contained in:
1 parent
b9ed01d91b
commit
319ba1890a
11 files changed
+539
-258
No files matched your search
@@ -0,0 +1,137 @@
|
||||
/* 文字画共享渲染引擎
|
||||
页面脚本调用 TextScene.render({...}) 提供数据即可,示例见 page1.js。
|
||||
网格坐标:x 0–cols-1,y 0–rows-1;空白格自动填充天空字符。 */
|
||||
|
||||
(() => {
|
||||
const BIRD_WORDS = {
|
||||
sparrow: '麻雀',
|
||||
magpie: '喜鹊',
|
||||
crow: '乌鸦',
|
||||
};
|
||||
|
||||
function render({
|
||||
cols,
|
||||
rows,
|
||||
skyChar = '天',
|
||||
skyClass = 'sky',
|
||||
clouds = [],
|
||||
sun = [],
|
||||
birds = [],
|
||||
antennas = [],
|
||||
buildings = [],
|
||||
glyphs = [],
|
||||
title,
|
||||
container = '.scene',
|
||||
}) {
|
||||
if (!cols || !rows) throw new Error('TextScene.render 需要 cols 与 rows');
|
||||
|
||||
// skyChar 传空字符串时不铺背景字,空格仍占满整格
|
||||
const blank = skyChar
|
||||
? { ch: skyChar, cls: skyClass }
|
||||
: { ch: ' ', cls: 'void' };
|
||||
|
||||
const grid = Array.from({ length: rows }, () =>
|
||||
Array.from({ length: cols }, () => ({ ...blank }))
|
||||
);
|
||||
|
||||
const put = (x, y, ch, cls) => {
|
||||
if (x < 0 || x >= cols || y < 0 || y >= rows) {
|
||||
throw new Error(`元素超出网格范围:(${x}, ${y})`);
|
||||
}
|
||||
grid[y][x] = { ch, cls };
|
||||
};
|
||||
|
||||
const putRow = (y, x, n, ch, cls) => {
|
||||
for (let i = 0; i < n; i++) put(x + i, y, ch, cls);
|
||||
};
|
||||
|
||||
for (const { x, y, tone } of clouds) {
|
||||
const cls = `cloud cloud-${tone}`;
|
||||
putRow(y, x + 1, 3, '云', cls);
|
||||
putRow(y + 1, x, 5, '云', cls);
|
||||
putRow(y + 2, x + 1, 3, '云', cls);
|
||||
}
|
||||
|
||||
sun.forEach(({ y, x, w }, i) => {
|
||||
const tone = `sun-t${i + 1}`;
|
||||
for (let dx = 0; dx < w; dx++) put(x + dx, y, '日', `sun ${tone}`);
|
||||
});
|
||||
|
||||
for (const { x, y, kind } of birds) {
|
||||
const word = BIRD_WORDS[kind];
|
||||
if (!word) throw new Error(`未知鸟种:${kind}`);
|
||||
const [a, b] = [...word];
|
||||
put(x, y, a, `bird-${kind}`);
|
||||
put(x + 1, y, b, `bird-${kind}`);
|
||||
}
|
||||
|
||||
for (const { x, y, h, cls = 'ant' } of antennas) {
|
||||
for (let i = 0; i < h; i++) put(x, y + i, '线', cls);
|
||||
}
|
||||
|
||||
for (const { n, x, y, w, h } of buildings) {
|
||||
for (let j = 0; j < h; j++) putRow(y + j, x, w, '楼', `bld bld-${n}`);
|
||||
}
|
||||
|
||||
for (const { text, x, y, cls = 'glyph', vertical = false } of glyphs) {
|
||||
[...text].forEach((ch, i) => {
|
||||
put(vertical ? x : x + i, vertical ? y + i : y, ch, cls);
|
||||
});
|
||||
}
|
||||
|
||||
if (title) {
|
||||
for (let y = title.top; y < title.top + title.height; y++) {
|
||||
for (let x = title.side; x < cols - title.side; x++) {
|
||||
grid[y][x] = { ch: ' ', cls: 'cut' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 把同 class 的连续字符合并成一个 span,减少 DOM 节点 */
|
||||
const html = grid
|
||||
.map((row) => {
|
||||
let line = '';
|
||||
for (let i = 0; i < cols; ) {
|
||||
let j = i + 1;
|
||||
while (j < cols && row[j].cls === row[i].cls) j++;
|
||||
const text = row.slice(i, j).map((cell) => cell.ch).join('');
|
||||
line += `<span class="${row[i].cls}">${text}</span>`;
|
||||
i = j;
|
||||
}
|
||||
return `<div class="line">${line}</div>`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
const scene = document.querySelector(container);
|
||||
if (!scene) throw new Error(`未找到容器:${container}`);
|
||||
scene.innerHTML = html;
|
||||
|
||||
if (!title) return;
|
||||
|
||||
const panel = document.createElement('div');
|
||||
panel.className = 'title-panel';
|
||||
panel.style.top = `${title.top}em`;
|
||||
panel.style.left = `${title.side}em`;
|
||||
panel.style.width = `${cols - title.side * 2}em`;
|
||||
panel.style.height = `${title.height}em`;
|
||||
|
||||
const titleEl = document.createElement('h1');
|
||||
titleEl.className = 'title';
|
||||
|
||||
const { text, emphasis } = title;
|
||||
if (emphasis && text.includes(emphasis)) {
|
||||
const [before, after] = text.split(emphasis);
|
||||
const em = document.createElement('span');
|
||||
em.className = 'title-em';
|
||||
em.textContent = emphasis;
|
||||
titleEl.append(before, em, after);
|
||||
} else {
|
||||
titleEl.textContent = text;
|
||||
}
|
||||
|
||||
panel.append(titleEl);
|
||||
scene.append(panel);
|
||||
}
|
||||
|
||||
window.TextScene = { render };
|
||||
})();
|
||||
Reference in new issue
Block a user