You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

小尺寸HTML Canvas文字像素化:先放大绘制再缩小可行吗?

解决小尺寸Canvas像素字体模糊问题

你当前的场景:

  • 主Canvas固定为256x232像素,通过CSS缩放至窗口大小,整体像素风格清晰
  • 使用像素字体(the-legend-of-zelda-nes.ttf)绘制文字时,8px字号因Canvas尺寸太小导致渲染模糊;放大字号(如1000px)虽清晰,但尺寸远超Canvas范围无法适配

解决方案是使用离屏Canvas预渲染大尺寸文字,再将其缩小绘制到主Canvas上,既保留字体清晰度,又不破坏主Canvas的像素风格。


实现思路

  1. 创建离屏Canvas,以大尺寸绘制文字(比如原字号的N倍)
  2. 将离屏Canvas的内容按比例缩小N倍,绘制到主Canvas对应位置
  3. 主Canvas始终保持原始尺寸,确保CSS缩放后的整体像素风格不受影响

修改后的完整代码

HTML

<canvas id="game-canvas" width="256" height="232"></canvas>

CSS

html,
body {
  padding: 0;
  margin: 0;
  overflow: hidden;
  image-rendering: pixelated;
  background: black;
}

canvas {
  background: black;
  width: 100vh;
  margin: auto;
  display: block;
}

JavaScript

const canvas = document.querySelector('#game-canvas');
const ctx = canvas.getContext('2d');

// 文字缩放倍数:数值越大文字越清晰,建议10-20倍(平衡性能与清晰度)
const TEXT_SCALE = 10;

// 工具函数:生成缩放后的文字图像
function getScaledTextImage(text, baseFontSize, fontFace, fillStyle = 'white', textAlign = 'center') {
  const offscreenCanvas = document.createElement('canvas');
  const offscreenCtx = offscreenCanvas.getContext('2d');
  
  // 计算离屏Canvas尺寸:基于放大后的字号和文字实际宽度
  const scaledFontSize = baseFontSize * TEXT_SCALE;
  offscreenCtx.font = `${scaledFontSize}px ${fontFace}`;
  const textWidth = offscreenCtx.measureText(text).width;
  offscreenCanvas.width = textWidth;
  offscreenCanvas.height = scaledFontSize * 1.2; // 预留行高空间
  
  // 重新设置上下文参数,绘制大尺寸清晰文字
  offscreenCtx.font = `${scaledFontSize}px ${fontFace}`;
  offscreenCtx.textAlign = textAlign;
  offscreenCtx.fillStyle = fillStyle;
  offscreenCtx.fillText(text, textWidth / 2, offscreenCanvas.height * 0.8); // 垂直居中微调
  
  return offscreenCanvas;
}

const draw = () => {
    // 保留原有场景绘制逻辑
    mainScene.draw(ctx, 0, 0);

    const numRupees = 0;
    const numKeys = 0;
    const numBombs = 0;
    const baseFontSize = 8;
    const fontFace = 'tloz';

    // 绘制缩放后的文字到主Canvas
    const rupeeTextImg = getScaledTextImage('X' + numRupees, baseFontSize, fontFace);
    ctx.drawImage(rupeeTextImg, gridCells(13), gridCells(2.75), rupeeTextImg.width / TEXT_SCALE, rupeeTextImg.height / TEXT_SCALE);

    const keyTextImg = getScaledTextImage('X' + numKeys, baseFontSize, fontFace);
    ctx.drawImage(keyTextImg, gridCells(13), gridCells(4.75), keyTextImg.width / TEXT_SCALE, keyTextImg.height / TEXT_SCALE);

    const bombTextImg = getScaledTextImage('X' + numBombs, baseFontSize, fontFace);
    ctx.drawImage(bombTextImg, gridCells(13), gridCells(5.75), bombTextImg.width / TEXT_SCALE, bombTextImg.height / TEXT_SCALE);

    const line1TextImg = getScaledTextImage("It's dangerous to go", baseFontSize, fontFace);
    ctx.drawImage(line1TextImg, canvas.width / 2 - line1TextImg.width / TEXT_SCALE / 2, canvas.height / 2 - 15, line1TextImg.width / TEXT_SCALE, line1TextImg.height / TEXT_SCALE);

    const line2TextImg = getScaledTextImage("alone! Take this.", baseFontSize, fontFace);
    ctx.drawImage(line2TextImg, canvas.width / 2 - line2TextImg.width / TEXT_SCALE / 2, canvas.height / 2 - 7.5, line2TextImg.width / TEXT_SCALE, line2TextImg.height / TEXT_SCALE);
};

// 加载字体并初始化绘制
const tlozFont = new FontFace('tloz', 'url(./public/fonts/the-legend-of-zelda-nes.ttf)');

tlozFont.load().then(function (font) {
    document.fonts.add(font);
    document.body.classList.add('fonts-loaded');
    console.log('Font loaded.');
    // 若为动画场景,此处替换为requestAnimationFrame循环调用draw
    draw();
});

关键说明

  • 离屏Canvas动态计算尺寸,避免不必要的资源浪费
  • 通过drawImage的缩放参数,将大尺寸文字精准缩小到主Canvas的目标位置,保持像素对齐
  • 主Canvas始终维持256x232的原始尺寸,CSS缩放时整体像素风格依然清晰

内容的提问来源于stack exchange,提问作者runni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 12:23:10