HTML实现Word、Excel、PPT三种可切换背景,可键盘打字、和像素小人交互

这是HTML文档,包含Word、Excel、PPT三种可切换背景,并支持键盘打字和像素小人交互。

```html

<!DOCTYPE html>

<html lang="zh-CN">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width,initial-scale=1">

<title>办公软件 · 像素小人</title>

<style>

html,body{margin:0;height:100%;overflow:hidden;background:#111;

font-family:"Microsoft YaHei",sans-serif;}

canvas{display:block;cursor:default;}

#tabs{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);

display:flex;gap:8px;z-index:10;}

#tabs button{padding:9px 18px;border:none;border-radius:20px;

background:rgba(0,0,0,.55);color:#fff;font:13px/1 "Microsoft YaHei",sans-serif;

cursor:pointer;backdrop-filter:blur(6px);transition:.2s;white-space:nowrap;}

#tabs button:hover{background:rgba(0,0,0,.78);}

#tabs button.active{background:#4ade80;color:#101010;font-weight:700;}

#tip{position:fixed;left:14px;bottom:16px;color:rgba(255,255,255,.6);

font:12px/1.7 "Microsoft YaHei",sans-serif;pointer-events:none;z-index:10;

text-shadow:0 1px 3px rgba(0,0,0,.7);}

#kb{position:fixed;left:0;top:0;width:2px;height:2px;opacity:0;

border:0;outline:0;padding:0;background:transparent;color:transparent;}

</style>

</head>

<body>

<canvas id="cv"></canvas>

<div id="tabs">

<button data-i="0" class="active">Word 文档</button>

<button data-i="1">Excel 表格</button>

<button data-i="2">PPT 演示</button>

</div>

<div id="tip">点击小人 → 消失并补充 | 直接打字 | Tab 切换背景 | Enter 清空小人并换行</div>

<input id="kb" autocomplete="off" spellcheck="false">

<script>

(function () {

'use strict';

/* ================= 基础变量 ================= */

const cv = document.getElementById('cv');

const ctx = cv.getContext('2d');

const kb = document.getElementById('kb');

const PIXEL = 4; // 像素点边长

const MAX_MEN = 3; // 屏幕上保持的小人数量

const LINE_H = 26; // 文字行高

// 5×8 点阵小人

const SPRITE = [

'..#..',

'.###.',

'..#..',

'#####',

'..#..',

'.#.#.',

'.#.#.',

'#...#'

];

const SW = SPRITE0.length;

const SH = SPRITE.length;

let men = \[\]; // 小人数组

let stopped = false; // 回车后停止补充

let themeIndex = 0; // 当前背景

let lines = ''; // 打出的文字(每行一个字符串)

const rand = (a, b) => a + Math.random() * (b - a);

/* ================= 画布尺寸 ================= */

function resize() {

cv.width = window.innerWidth;

cv.height = window.innerHeight;

}

window.addEventListener('resize', resize);

resize();

/* ================= 小人逻辑 ================= */

function createMan() {

return {

x : rand(0, Math.max(10, cv.width - SW * PIXEL)),

y : rand(0, Math.max(10, cv.height - SH * PIXEL)),

vx: (Math.random() < 0.5 ? -1 : 1) * rand(0.6, 1.8),

vy: (Math.random() < 0.5 ? -1 : 1) * rand(0.6, 1.8)

};

}

function fillMen() {

if (stopped) return;

while (men.length < MAX_MEN) men.push(createMan());

}

function updateMen() {

const maxX = Math.max(0, cv.width - SW * PIXEL);

const maxY = Math.max(0, cv.height - SH * PIXEL);

for (const m of men) {

m.x += m.vx;

m.y += m.vy;

if (m.x < 0) { m.x = 0; m.vx = Math.abs(m.vx); }

if (m.x > maxX) { m.x = maxX; m.vx = -Math.abs(m.vx); }

if (m.y < 0) { m.y = 0; m.vy = Math.abs(m.vy); }

if (m.y > maxY) { m.y = maxY; m.vy = -Math.abs(m.vy); }

}

}

function drawMen() {

ctx.fillStyle = '#22c55e';

for (const m of men) {

for (let r = 0; r < SH; r++) {

for (let c = 0; c < SW; c++) {

if (SPRITErc === '#') {

ctx.fillRect(m.x + c * PIXEL, m.y + r * PIXEL, PIXEL, PIXEL);

}

}

}

}

}

/* ================= 通用:绘制标题栏 + 功能区 ================= */

function drawChrome(color, title) {

const W = cv.width;

// 标题栏

ctx.fillStyle = color;

ctx.fillRect(0, 0, W, 44);

ctx.fillStyle = '#ffffff';

ctx.font = 'bold 15px "Microsoft YaHei",sans-serif';

ctx.textAlign = 'left';

ctx.textBaseline = 'middle';

ctx.fillText(title, 18, 23);

// 右上角窗口按钮装饰

ctx.fillStyle = 'rgba(255,255,255,.45)';

ctx.fillRect(W - 96, 21, 12, 2);

ctx.fillRect(W - 66, 16, 12, 12);

ctx.fillStyle = 'rgba(255,255,255,.65)';

ctx.fillRect(W - 38, 21, 14, 2);

// 功能区

ctx.fillStyle = '#f7f8f9';

ctx.fillRect(0, 44, W, 46);

ctx.fillStyle = '#d9dde1';

const btnCount = Math.min(8, Math.floor((W - 40) / 50));

for (let i = 0; i < btnCount; i++) {

ctx.fillRect(18 + i * 50, 56, 36, 22);

}

ctx.fillStyle = '#e2e6e9';

ctx.fillRect(0, 90, W, 1);

}

/* ================= 背景 1:Word ================= */

function drawWord() {

const W = cv.width, H = cv.height;

ctx.fillStyle = '#e9ecef';

ctx.fillRect(0, 0, W, H);

drawChrome('#2b579a', '文档1 - Word');

// 页面

const pw = Math.min(W * 0.74, 780);

const py = 112;

const ph = Math.max(120, H - py - 26);

const px = (W - pw) / 2;

ctx.save();

ctx.shadowColor = 'rgba(0,0,0,.18)';

ctx.shadowBlur = 20;

ctx.shadowOffsetY = 5;

ctx.fillStyle = '#ffffff';

ctx.fillRect(px, py, pw, ph);

ctx.restore();

// 页面顶部一条淡色分隔,模拟页眉

ctx.fillStyle = '#eef1f4';

ctx.fillRect(px + 70, py + 34, pw - 140, 1);

return {

x: px + 70,

y: py + 58,

w: Math.max(80, pw - 140),

h: Math.max(40, ph - 110),

color: '#1f2328',

font: '16px "Microsoft YaHei",sans-serif'

};

}

/* ================= 背景 2:Excel ================= */

function colName(n) {

let s = '';

n++;

while (n > 0) {

const r = (n - 1) % 26;

s = String.fromCharCode(65 + r) + s;

n = Math.floor((n - 1) / 26);

}

return s;

}

function drawExcel() {

const W = cv.width, H = cv.height;

ctx.fillStyle = '#f1f3f4';

ctx.fillRect(0, 0, W, H);

drawChrome('#217346', '工作簿1 - Excel');

const left = 46; // 行标题宽度

const top = 118; // 表格起始 y

const colW = 92;

const rowH = LINE_H; // 与文字行高一致,视觉更整齐

const cols = Math.max(1, Math.ceil((W - left) / colW));

const rows = Math.max(1, Math.ceil((H - top) / rowH));

// 列标题

ctx.font = '12px "Microsoft YaHei",sans-serif';

ctx.textAlign = 'center';

ctx.textBaseline = 'middle';

for (let c = 0; c < cols; c++) {

const x = left + c * colW;

ctx.fillStyle = '#e8ebed';

ctx.fillRect(x, top - 24, colW, 24);

ctx.fillStyle = '#5f6368';

ctx.fillText(colName(c), x + colW / 2, top - 12);

}

// 行标题

for (let r = 0; r < rows; r++) {

const y = top + r * rowH;

ctx.fillStyle = '#e8ebed';

ctx.fillRect(0, y, left, rowH);

ctx.fillStyle = '#5f6368';

ctx.fillText(String(r + 1), left / 2, y + rowH / 2);

}

// 网格线

ctx.strokeStyle = '#d5dbe1';

ctx.lineWidth = 1;

ctx.beginPath();

for (let c = 0; c <= cols; c++) {

const x = left + c * colW + 0.5;

ctx.moveTo(x, top - 24);

ctx.lineTo(x, H);

}

for (let r = 0; r <= rows; r++) {

const y = top + r * rowH + 0.5;

ctx.moveTo(0, y);

ctx.lineTo(W, y);

}

ctx.stroke();

// 当前选中单元格高亮

ctx.strokeStyle = '#217346';

ctx.lineWidth = 2;

ctx.strokeRect(left + 1, top + 1, colW - 2, rowH - 2);

return {

x: left + 10,

y: top + 5,

w: Math.max(60, W - left - 26),

h: Math.max(40, H - top - 12),

color: '#202124',

font: '15px "Microsoft YaHei",sans-serif'

};

}

/* ================= 背景 3:PPT ================= */

function drawPPT() {

const W = cv.width, H = cv.height;

ctx.fillStyle = '#2b2b30';

ctx.fillRect(0, 0, W, H);

drawChrome('#d24726', '演示文稿1 - PowerPoint');

// 幻灯片 16:9

const areaTop = 112;

const areaH = Math.max(120, H - areaTop - 26);

let sw = Math.min(W * 0.72, 900);

let sh = sw * 9 / 16;

if (sh > areaH) {

sh = areaH;

sw = sh * 16 / 9;

}

const sx = (W - sw) / 2;

const sy = areaTop + (areaH - sh) / 2;

ctx.save();

ctx.shadowColor = 'rgba(0,0,0,.55)';

ctx.shadowBlur = 26;

ctx.shadowOffsetY = 8;

ctx.fillStyle = '#ffffff';

ctx.fillRect(sx, sy, sw, sh);

ctx.restore();

// 幻灯片底部一条橙色装饰线

ctx.fillStyle = '#d24726';

ctx.fillRect(sx, sy + sh - 6, sw, 6);

return {

x: sx + sw * 0.09,

y: sy + sh * 0.14,

w: Math.max(60, sw * 0.82),

h: Math.max(40, sh * 0.7),

color: '#222222',

font: '17px "Microsoft YaHei",sans-serif'

};

}

/* ================= 文字排版与绘制 ================= */

function wrapLines(src, maxW) {

const out = \[\];

for (const line of src) {

if (line === '') { out.push(''); continue; }

let cur = '', w = 0;

for (const ch of line) {

const cw = ctx.measureText(ch).width;

if (w + cw > maxW && cur.length > 0) {

out.push(cur);

cur = ch;

w = cw;

} else {

cur += ch;

w += cw;

}

}

out.push(cur);

}

return out;

}

function drawText(area, t) {

if (!area) return;

ctx.save();

ctx.font = area.font;

ctx.textAlign = 'left';

ctx.textBaseline = 'top';

ctx.fillStyle = area.color;

const wrapped = wrapLines(lines, area.w);

const maxLines = Math.max(1, Math.floor(area.h / LINE_H));

let show = wrapped;

if (wrapped.length > maxLines) {

show = wrapped.slice(wrapped.length - maxLines);

}

for (let i = 0; i < show.length; i++) {

ctx.fillText(showi, area.x, area.y + i * LINE_H);

}

// 光标(闪烁)

if (Math.floor(t / 530) % 2 === 0) {

const idx = show.length - 1;

const last = showidx || '';

const w = ctx.measureText(last).width;

ctx.fillRect(area.x + w + 1, area.y + idx * LINE_H + 3, 2, LINE_H - 8);

}

ctx.restore();

}

/* ================= 主循环 ================= */

function frame(t) {

let area;

if (themeIndex === 0) area = drawWord();

else if (themeIndex === 1) area = drawExcel();

else area = drawPPT();

updateMen();

drawMen(); // 小人画在背景之上

drawText(area, t); // 文字画在最上层,保证清晰

requestAnimationFrame(frame);

}

/* ================= 文本编辑 ================= */

function newLine() {

lines.push('');

if (lines.length > 300) lines.shift();

}

function insertText(str) {

for (const ch of str) {

if (ch === '\n') newLine();

else lineslines.length - 1 += ch;

}

}

function backspace() {

const i = lines.length - 1;

if (linesi.length > 0) linesi = linesi.slice(0, -1);

else if (i > 0) lines.pop();

}

/* ================= 主题切换 ================= */

function setTheme(i) {

themeIndex = i;

document.querySelectorAll('#tabs button').forEach((b, idx) => {

b.classList.toggle('active', idx === i);

});

}

document.querySelectorAll('#tabs button').forEach(btn => {

btn.addEventListener('click', () => {

setTheme(Number(btn.dataset.i));

kb.focus();

});

});

/* ================= 键盘事件 ================= */

window.addEventListener('keydown', e => {

// Tab:切换背景

if (e.key === 'Tab') {

e.preventDefault();

setTheme((themeIndex + 1) % 3);

return;

}

// Enter:清空所有小人 + 文本换行

if (e.key === 'Enter') {

e.preventDefault();

if (men.length > 0) { stopped = true; men.length = 0; }

newLine();

return;

}

// Backspace:删除

if (e.key === 'Backspace') {

e.preventDefault();

backspace();

return;

}

// 其余可打印字符交给 input 事件(兼容中文输入法)

});

// 用隐藏输入框捕获输入,天然支持中文输入法

kb.addEventListener('input', e => {

if (e.isComposing) return; // 拼音组合中,忽略

const v = kb.value;

if (v) {

insertText(v);

kb.value = '';

}

});

/* ================= 鼠标点击:命中即消失 ================= */

cv.addEventListener('mousedown', e => {

const rect = cv.getBoundingClientRect();

const mx = e.clientX - rect.left;

const my = e.clientY - rect.top;

for (let i = men.length - 1; i >= 0; i--) {

const m = meni;

const c = Math.floor((mx - m.x) / PIXEL);

const r = Math.floor((my - m.y) / PIXEL);

if (r >= 0 && r < SH && c >= 0 && c < SW && SPRITErc === '#') {

men.splice(i, 1);

fillMen();

break;

}

}

kb.focus(); // 保证键盘输入不中断

});

/* ================= 启动 ================= */

fillMen();

requestAnimationFrame(frame);

// 初始聚焦(若被浏览器拦截,点击画布后即可输入)

setTimeout(() => kb.focus(), 100);

})();

</script>

</body>

</html>

```

玩法与输入体验

三种办公背景一键切换,打字和点小人互不干扰。

· 背景切换:底部按钮或按 Tab 键在 Word、Excel、PPT 间循环切换,每种背景都有对应的界面细节。

· 打字输入:点击画面任意处激活隐藏输入框,直接敲键盘即可在背景的"文档区"看到文字,支持中文输入法、退格和换行。

· 小人交互:点击任意像素小人,它会消失并随机补充一个新小人,总数始终维持 3 个;按 Enter 键清空所有小人并换行。

· 文字排版:文字会按当前编辑区域的宽度自动换行,光标闪烁提示输入位置。


优化建议: 可以调整代码开头的 MAX_MEN 变量改变小人总数,修改 rand(0.6, 1.8) 中的数值控制移动速度,或调整 PIXEL 改变像素点大小。

仅供参考用,实际使用还需实验。

相关推荐
西瓜很甜哟1 小时前
golang实现大文件分片上传
后端·golang·html
闭包不眠1 小时前
Word转HTML后体积不对,裁掉的截图原图还在
前端·图像处理·人工智能·计算机视觉·html·word
Am-Chestnuts1 小时前
Mermaid图导Word乱?DS随心转清晰
ai·word
qq_466302451 小时前
word表格复制粘贴到另一个word,左缩进2
word
lank_M2 小时前
Word裁剪图片转HTML走样,该在渲染层修还是入口修
前端·图像处理·人工智能·计算机视觉·html·word
志尊宝12 小时前
Vue3 零基础每日笔记(071):暗黑模式与主题切换——CSS 变量 + Element Plus 暗黑方案
前端·css·笔记·vue·html·前端开发·软件开发
米柆15 小时前
HTML5 audio /video 完整事件监听模板
前端·html·html5
嘟嘟同学和妮妮同学18 小时前
【升级】一个中国历史可视化站:皇帝时间线梳理、古代思想流派、诗词鉴赏 (2)
前端·html
Eiceblue1 天前
使用 Python 实现 Excel 行列转置(保留单元格样式)
前端·python·excel