45

这个问题说明了一切。我一直在四处寻找并开始担心这是不可能的。

我有这个画布元素,我正在向其中绘制文本。我想设置类似于 CSSletter-spacing属性的字母间距。我的意思是在绘制字符串时增加字母之间的像素数量。

我绘制文本的代码是这样的,ctx 是画布上下文变量。

ctx.font = "3em sheepsans";
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.fillText("Blah blah text", 1024 / 2, 768 / 2);

我尝试ctx.letterSpacing = "2px";在绘图之前添加,但无济于事。有没有办法通过简单的设置来做到这一点,还是我必须制作一个函数来单独绘制每个字符并考虑间距?

4

12 回答 12

50

您无法设置字母间距属性,但您可以通过在字符串中的每个字母之间插入各种空白之一来在画布中实现更宽的字母间距。例如

ctx.font = "3em sheepsans";
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillStyle = "rgb(255, 255, 255)";
var ctext = "Blah blah text".split("").join(String.fromCharCode(8202))
ctx.fillText(ctext, 1024 / 2, 768 / 2);

这将在每个字母之间插入一个头发空间。

使用 8201(而不是 8202)将插入稍宽的细空间

有关更多空白选项,请参阅此 Unicode 空间列表

与手动定位每个字母相比,此方法将帮助您更轻松地保留字体的字距,但是您无法通过这种方式收紧字母间距。

于 2013-02-20T22:46:57.140 回答
25

我不确定它是否应该工作(根据规范),但在某些浏览器(Chrome)中,您可以letter-spacing在元素本身上设置 CSS 属性<canvas>,它将应用于在上下文中绘制的所有文本。(适用于 Chrome v56,不适用于 Firefox v51 或 IE v11。)

letter-spacing请注意,在 Chrome v56 中,您必须在每次更改样式后重新获取 canvas 2d 上下文(并重新设置您关心的任何值) ;间距似乎已融入您获得的 2d 上下文中。

示例:https ://jsfiddle.net/hg4pbsne/1/

var inp = document.querySelectorAll('input'),
    can = document.querySelector('canvas'),
    ctx = can.getContext('2d');
    can.width = can.offsetWidth;

[].forEach.call(inp,function(inp){ inp.addEventListener('input', redraw, false) });
redraw();

function redraw(){
  ctx.clearRect(0,0,can.width,can.height);
  can.style.letterSpacing = inp[0].value + 'px';

  ctx = can.getContext('2d');
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.font = '4em sans-serif';
  ctx.fillText('Hello', can.width/2, can.height*1/4);
  
  can.style.letterSpacing = inp[1].value + 'px';
  ctx = can.getContext('2d');
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.font = '4em sans-serif';
  ctx.fillText('World', can.width/2, can.height*3/4);
};
canvas { background:white }
canvas, label { display:block; width:400px; margin:0.5em auto }
<canvas></canvas>
<label>hello spacing: <input type="range" min="-20" max="40" value="1" step="0.1"></label>
<label>world spacing: <input type="range" min="-20" max="40" value="1" step="0.1"></label>


原始的跨浏览器答案:

这是不可能的; HTML5 Canvas 不具备 HTML 中 CSS 的所有文本转换功能。我建议您应该为每种用途结合适当的技术。使用与 Canvas 甚至 SVG 分层的 HTML,每个都做它最擅长的事情。

另请注意,鉴于存在字母字距调整对和像素对齐的字体提示,“滚动你自己的”(使用自定义偏移量绘制每个字符)将对大多数字体产生不良结果。

于 2012-01-21T19:07:48.243 回答
9

您不能将字母间距设置为 Canvas 上下文的属性。只能手动做间距才能达到效果,不好意思。(如,手动绘制每个字母,将每个字母上的 x 增加一些像素量)

作为记录,您可以使用设置一些文本属性,ctx.font但字母间距不是其中之一。您可以设置的是:“font-style font-variant font-weight font-size/line-height font-family”

例如,您可以在技术上编写ctx.font = "bold normal normal 12px/normal Verdana"(或任何其中的任何遗漏),它会正确解析。

于 2012-01-21T19:05:59.997 回答
7

为了允许“字母字距调整对”等,我写了以下内容。它应该考虑到这一点,粗略的测试表明确实如此。如果您对此有任何意见,那么我会指出我关于该主题的问题(在 HTML 画布中添加字母间距

基本上它使用 measureText() 来获取整个字符串的宽度,然后删除字符串的第一个字符并测量剩余字符串的宽度,并使用差异来计算正确的定位 - 因此考虑到字距对和类似。有关更多伪代码,请参见给定的链接。

这是HTML:

<canvas id="Test1" width="800px" height="200px"><p>Your browser does not support canvas.</p></canvas>

这是代码:

this.fillTextWithSpacing = function(context, text, x, y, spacing)
{
    //Start at position (X, Y).
    //Measure wAll, the width of the entire string using measureText()
    wAll = context.measureText(text).width;

    do
    {
    //Remove the first character from the string
    char = text.substr(0, 1);
    text = text.substr(1);

    //Print the first character at position (X, Y) using fillText()
    context.fillText(char, x, y);

    //Measure wShorter, the width of the resulting shorter string using measureText().
    if (text == "")
        wShorter = 0;
    else
        wShorter = context.measureText(text).width;

    //Subtract the width of the shorter string from the width of the entire string, giving the kerned width of the character, wChar = wAll - wShorter
    wChar = wAll - wShorter;

    //Increment X by wChar + spacing
    x += wChar + spacing;

    //wAll = wShorter
    wAll = wShorter;

    //Repeat from step 3
    } while (text != "");
}

演示/眼球测试代码:

element1 = document.getElementById("Test1");
textContext1 = element1.getContext('2d');

textContext1.font = "72px Verdana, sans-serif";
textContext1.textAlign = "left";
textContext1.textBaseline = "top";
textContext1.fillStyle = "#000000";

text = "Welcome to go WAVE";
this.fillTextWithSpacing(textContext1, text, 0, 0, 0);
textContext1.fillText(text, 0, 100);

理想情况下,我会向它抛出多个随机字符串并进行逐像素比较。我也不确定 Verdana 的默认字距调整有多好,尽管我知道它比 Arial 更好 - 对其他字体的建议值得尝试接受。

所以......到目前为止它看起来不错。事实上,它看起来很完美。仍然希望有人会指出过程中的任何缺陷。

与此同时,我会把它放在这里,让其他人看看他们是否正在寻找解决方案。

于 2015-12-03T04:51:21.533 回答
3

这是一些咖啡脚本,可让您像这样将字距设置为您的上下文

tctx = tcanv.getContext('2d')
tctx.kerning = 10
tctx.fillStyle = 'black'
tctx.fillText 'Hello World!', 10, 10

支持代码是:

_fillText = CanvasRenderingContext2D::fillText
CanvasRenderingContext2D::fillText = (str, x, y, args...) ->

  # no kerning? default behavior
  return _fillText.apply this, arguments unless @kerning?

  # we need to remember some stuff as we loop
  offset = 0

  _.each str, (letter) =>

    _fillText.apply this, [
      letter
      x + offset + @kerning
      y
    ].concat args # in case any additional args get sent to fillText at any time

    offset += @measureText(letter).width + @kerning

javascript将是

var _fillText,
  __slice = [].slice;

_fillText = CanvasRenderingContext2D.prototype.fillText;

CanvasRenderingContext2D.prototype.fillText = function() {
  var args, offset, str, x, y,
    _this = this;

  str = arguments[0], x = arguments[1], y = arguments[2], args = 4 <= arguments.length ? __slice.call(arguments, 3) : [];
  if (this.kerning == null) {
    return _fillText.apply(this, arguments);
  }
  offset = 0;

  return _.each(str, function(letter) {
    _fillText.apply(_this, [letter, x + offset + _this.kerning, y].concat(args));
    offset += _this.measureText(letter).width + _this.kerning;
  });
};
于 2013-03-19T19:44:44.553 回答
3

不对。您可以在 css 中为 canvas 元素添加 letter-spacing 属性,它可以完美运行。无需复杂的解决方法。我现在才在我的画布项目中弄清楚了。即:画布{宽度:480px;高度:350px;边距:30px 自动 0;填充:15px 0 0 0;背景:粉红色;显示:块;边框:2px 虚线棕色;字母间距:0.1em;}

于 2016-11-07T16:31:31.420 回答
1

这可能是一个老问题,但它仍然是相关的。我接受了 Patrick Matte 对 James Carlyle-Clarke 响应的扩展,并得到了一些我认为效果很好的东西,因为它仍然是普通的旧 Javascript。这个想法是测量两个连续字符之间的空间并“添加”到它。是的,负数有效。

这是我所拥有的(大量评论版本):

function fillTextWithSpacing (context, text, x, y, spacing) {
    // Total width is needed to adjust the starting X based on text alignment.
    const total_width = context.measureText (text).width + spacing * (text.length - 1);

    // We need to save the current text alignment because we have to set it to
    // left for our calls to fillText() draw in the places we expect. Don't
    // worry, we're going to set it back at the end.
    const align = context.textAlign;
    context.textAlign = "left";

    // Nothing to do for left alignment, but adjustments are needed for right
    // and left. Justify defeats the purpose of manually adjusting character
    // spacing, and it requires a width to be known.
    switch (align) {
        case "right":
            x -= total_width;
            break;
        case "center":
            x -= total_width / 2;
            break;
    }

    // We have some things to keep track of and the C programmer in me likes
    // declarations on their own and in groups.
    let offset, pair_width, char_width, char_next_width, pair_spacing, char, char_next;

    // We're going to step through the text one character at a time, but we
    // can't use for(... of ...) because we need to be able to look ahead.
    for (offset = 0; offset < text.length; offset = offset + 1) {
        // Easy on the eyes later
        char = text.charAt (offset);
        // Default the spacing between the "pair" of characters to 0. We need
        // for the last character.
        pair_spacing = 0;
        // Check to see if there's at least one more character after this one.
        if (offset + 1 < text.length) {
            // This is always easier on the eyes
            char_next = text.charAt (offset + 1);
            // Measure to the total width of both characters, including the
            // spacing between them... even if it's negative.
            pair_width = context.measureText (char + char_next).width;
            // Measure the width of just the current character.
            char_width = context.measureText (char).width;
            // Measure the width of just the next character.
            char_next_width = context.measureText (char_next).width;
            // We can determine the kerning by subtracting the width of each
            // character from the width of both characters together.
            pair_spacing = pair_width - char_width - char_next_width;
        }

        // Draw the current character
        context.fillText (char, x, y);
        // Advanced the X position by adding the current character width, the
        // spacing between the current and next characters, and the manual
        // spacing adjustment (negatives work).
        x = x + char_width + pair_spacing + spacing;
    }

    // Set the text alignment back to the original value.
    context.textAlign = align;

    // Profit
}

这是一个演示:

let canvas = document.getElementById ("canvas");
canvas.width = 600;
canvas.height = 150;
let context = canvas.getContext ("2d");

function fillTextWithSpacing (context, text, x, y, spacing) {
    const total_width = context.measureText (text).width + spacing * (text.length - 1);

    const align = context.textAlign;
    context.textAlign = "left";

    switch (align) {
        case "right":
            x -= total_width;
            break;
        case "center":
            x -= total_width / 2;
            break;
    }

    let offset, pair_width, char_width, char_next_width, pair_spacing, char, char_next;

    for (offset = 0; offset < text.length; offset = offset + 1) {
        char = text.charAt (offset);
        pair_spacing = 0;
        if (offset + 1 < text.length) {
            char_next = text.charAt (offset + 1);
            pair_width = context.measureText (char + char_next).width;
            char_width = context.measureText (char).width;
            char_next_width = context.measureText (char_next).width;
            pair_spacing = pair_width - char_width - char_next_width;
        }

        context.fillText (char, x, y);
        x = x + char_width + pair_spacing + spacing;
    }

    context.textAlign = align;
}

function update () {
    let
        font = document.getElementById ("font").value,
        size = parseInt (document.getElementById ("size").value, 10),
        weight = parseInt (document.getElementById ("weight").value, 10),
        italic = document.getElementById ("italic").checked,
        spacing = parseInt (document.getElementById ("spacing").value, 10),
        text = document.getElementById ("text").value;

    context.textAlign = "center";
    context.textBaseline = "alphabetic";
    context.fillStyle = "#404040";
    context.font = (italic ? "italic " : "") + weight + " " + size + "px " + font;

    context.clearRect (0, 0, canvas.width, canvas.height);
    fillTextWithSpacing (context, text, canvas.width / 2, (canvas.height + size) / 2, spacing);
}

document.getElementById ("font").addEventListener (
    "change",
    (event) => {
        update ();
    }
);
document.getElementById ("size").addEventListener (
    "change",
    (event) => {
        update ();
    }
);
document.getElementById ("weight").addEventListener (
    "change",
    (event) => {
        update ();
    }
);
document.getElementById ("italic").addEventListener (
    "change",
    (event) => {
        update ();
    }
);
document.getElementById ("spacing").addEventListener (
    "change",
    (event) => {
        update ();
    }
);
document.getElementById ("text").addEventListener (
    "input",
    (event) => {
        update ();
    }
);

update ();
select, input {
  display: inline-block;
}
input[type=text] {
  display: block;
  margin: 0.5rem 0;
}
canvas {
  border: 1px solid #b0b0b0;
  width: 600px;
  height: 150px;
}
<!DOCTYPE html>
<html lang="en-US">
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <select id="font">
            <option value="serif">Serif</option>
            <option value="sans-serif">Sans Serif</option>
            <option value="fixed-width">Fixed Width</option>
        </select>
        <label>Size: <input type="number" id="size" value="60" min="1" max="200" size="3" /></label>
        <label>Weight: <input type="number" id="weight" value="100" min="100" max="1000" step="100" size="4" /></label>
        <label>Italic: <input type="checkbox" id="italic" checked /></label>
        <label>Spacing: <input type="number" id="spacing" value="0" min="-200" max="200" size="4" /></label>
        <input type="text" id="text" placeholder="Text" value="hello" size="40"/>
        <canvas id="canvas"></canvas>
    </body>
</html>

于 2021-12-30T04:21:14.267 回答
0

实际上字母间距概念画布不支持。

所以我用javascript来做到这一点。

var value = $('#sourceText1').val().split("").join("");

或者

var sample_text = "Praveen Chelumalla";
var text = sample_text.split("").join(" ");
于 2017-09-07T06:51:36.707 回答
0

这是基于 James Carlyle-Clarke 先前回答的另一种方法。它还可以让您将文本左、中和右对齐。

export function fillTextWithSpacing(context, text, x, y, spacing, textAlign) {
    const totalWidth = context.measureText(text).width + spacing * (text.length - 1);
    switch (textAlign) {
        case "right":
            x -= totalWidth;
            break;
        case "center":
            x -= totalWidth / 2;
            break;
    }
    for (let i = 0; i < text.length; i++) {
        let char = text.charAt(i);
        context.fillText(char, x, y);
        x += context.measureText(char).width + spacing;
    }
}
于 2021-03-23T20:21:08.550 回答
0

我不了解其他人,但我通过增加我正在编写的文本的 y 值来调整行距。我实际上是用空格分割一个字符串,并在循环内将每个单词踢下一行。我使用的数字基于默认字体。如果您使用不同的字体,这些数字可能需要调整。

// object holding x and y coordinates
var vectors = {'x':{1:100, 2:200}, 'y':{1:0, 2:100}
// replace the first letter of a word
var newtext = YOURSTRING.replace(/^\b[a-z]/g, function(oldtext) {
    // return it uppercase
    return oldtext.toUpperCase(); 
});
// split string by spaces
newtext = newtext.split(/\s+/);

// line height
var spacing = 10 ;
// initial adjustment to position
var spaceToAdd = 5;
// for each word in the string draw it based on the coordinates + spacing
for (var c = 0; c < newtext.length; c++) {
    ctx.fillText(newtext[c], vectors.x[i], vectors.y[i] - spaceToAdd);
    // increment the spacing 
    spaceToAdd += spacing;
}               
于 2019-05-02T02:02:18.490 回答
-1

支持画布中的字母间距,我使用了这个

canvas = document.getElementById('canvas');
canvas.style.letterSpacing = '2px';
于 2017-11-01T09:12:53.563 回答
-4

我用:

ctx.font = "32px Tahoma";//set font
ctx.scale(0.75,1);//important! the scale
ctx.fillText("LaFeteParFete test text", 2, 274);//draw
ctx.setTransform(1,0,0,1,0,0);//reset transform
于 2016-05-25T00:01:51.660 回答