首页 > 解决方案 > 为什么画布中的文本不能垂直居中

问题描述

    CanvasRenderingContext2D.prototype.roundRect = function (
        x,
        y,
        width,
        height,
        radius
    ) {
        if (width < 2 * radius) radius = width / 2;
        if (height < 2 * radius) radius = height / 2;
        this.beginPath();
        this.moveTo(x + radius, y);
        this.arcTo(x + width, y, x + width, y + height, radius);
        this.arcTo(x + width, y + height, x, y + height, radius);
        this.arcTo(x, y + height, x, y, radius);
        this.arcTo(x, y, x + width, y, radius);
        this.closePath();
        return this;
    };

let generateCanvas = function (config) {
        var tmpCanvas = document.createElement("canvas");
        var tmpContext = tmpCanvas.getContext("2d");
        tmpContext.font = tmpContext.font.replace(/\d+px/, config.fontSize);
        var textWidth = tmpContext.measureText(config.text).width || 10;
        tmpCanvas = null;
        var textCanvas = document.createElement("canvas");
        var textContext = textCanvas.getContext("2d");
        textCanvas.height = config.height;
        textCanvas.width = textWidth;
        textCanvas.width = textWidth + 2 * config.padding;
        textContext.lineWidth = config.lineWidth;
        textContext.strokeStyle = config.lineColor;
        textContext.fillStyle = config.backgroundColor;
        textContext.roundRect(
                0,
                0,
                textCanvas.width,
                textCanvas.height,
                config.rounded
            );
            textContext.stroke();
            textContext.fill();
        textContext.fillStyle = config.color;
        textContext.font = textContext.font.replace(/\d+px/, config.fontSize);
        textContext.textAlign = config.textAlign;
        textContext.textBaseline = config.textBaseline;
        textContext.fillText(
            config.text,
            textCanvas.width / 2,
            config.height / 2
        );
        return textCanvas;
    };

我传递给 generateCanvas 函数的配置是:

{
                            text: "测试名称测试名称测试名称测试名称",
                            height: 40,
                            fontSize: "24px",
                            textAlign: "center",
                            textBaseline: "middle",
                            color: "#F9A403",
                            backgroundColor: "white",
                            lineColor: "transparent",
                            rounded: 25,
                            padding: 10,
                        }

为什么上述函数生成的图像中的文字没有垂直居中?roundRect 是一个自定义函数,实现了使用canvas绘制圆角矩形,有大神帮忙看看这个问题。

标签: javascripthtml5-canvas

解决方案


"middle"textBaseline基于em-square。这意味着它适用于拉丁字形,但不适用于表意字形,它需要一个以表意为中心的基线值,而 Canvas API 还没有。

这是规格中的图形表示:

em 方格的顶部大致位于字体中字形的顶部,悬挂基线是一些像 आ 这样的字形锚定的位置,中间是 em 方格顶部和 em 方格底部之间的中间位置, 字母基线是 Á、ÿ、f 和 Ω 等字符的锚定位置,表意下基线是诸如私和达之类的字形锚定的位置,而 em 正方形的底部大致位于一种字体。 由于字形延伸到 em 方格之外,边界框的顶部和底部可能远离这些基线。

但所有的希望都没有落空,在过去的几年里,所有的浏览器在TextMetrics接口的实现上都取得了一些进展,我们终于可以从所有主流浏览器获得足够的信息来测量被测量文本的高度。
这意味着我们可以根据正在渲染的字形实现真正居中的垂直对齐:

const text_input = document.querySelector("input[type='text']");
const padding_input = document.querySelector("input[type='range']");
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");;
const font = "60px sans-serif";
let padding_x = 10;
let padding_y = 10;

text_input.oninput = padding_input.oninput =
  (evt) => drawText(text_input.value);

drawText(text_input.value);

// returns an easier to use BBox like object
// from a TextMetrics object
function getTextBBox( ctx, text ) {
  const metrics = ctx.measureText( text );
  const left = metrics.actualBoundingBoxLeft * -1;
  const top = metrics.actualBoundingBoxAscent * -1;
  const right = metrics.actualBoundingBoxRight;
  const bottom = metrics.actualBoundingBoxDescent;
  const width = right - left;
  const height = bottom - top;
  return { left, top, right, bottom, width, height };
}

function drawText( text ) {
  // we set only the font
  // other values like textBaseline and textAlign
  // are left to their default
  ctx.font = font;
  const bbox = getTextBBox(ctx, text);
  const padding = +padding_input.value;
  canvas.width = bbox.width + padding;
  canvas.height = bbox.height + padding;
  ctx.font = font;
  ctx.fillStyle = "#F9A403";

  const middle_x = bbox.left + (bbox.width / 2);
  const middle_y = bbox.top + (bbox.height / 2);
  const left = canvas.width / 2 - middle_x;
  const top = canvas.height / 2 - middle_y;

  ctx.fillText(text, left, top);

  // draw the middle line for reference  
  ctx.fillStyle = "red";
  ctx.fillRect(0, canvas.height/2-1, canvas.width, 2);
}
canvas { border: 1px solid; }
<label>Text content: <input type="text" value="测试名称测试名称测试名称测试名称"></label><br>
<label>Padding: <input type="range" min="0" max="100"></label><br>
<canvas></canvas>


推荐阅读