和光同尘不是墙头草头像
关注
HTML实现Word、Excel、PPT三种可切换背景,可键盘打字、和像素小人交互封面图

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 = SPRITE[0].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 (SPRITE[r][c] === '#') {

            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(show[i], area.x, area.y + i * LINE_H);

    }

 

    // 光标(闪烁)

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

      const idx = show.length - 1;

      const last = show[idx] || '';

      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 lines[lines.length - 1] += ch;

    }

  }

 

  function backspace() {

    const i = lines.length - 1;

    if (lines[i].length > 0) lines[i] = lines[i].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 = men[i];

      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 && SPRITE[r][c] === '#') {

        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 改变像素点大小。

 

 

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

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2602_95242675/article/details/167699323

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--