是否可以使用 HTML5 Canvas Text API 绘制文本装饰(下划线等)?

发布于 2024-10-11 07:58:54 字数 194 浏览 3 评论 0原文

我正在使用 HTML5 画布 API 来显示一些字符串 (canvas.fillText),我想知道画布 API 是否可以实现文本装饰(如下划线、删除线等)。不幸的是,我对此一无所获。

我发现的唯一解决方案是使用画布绘图 API 手动进行装饰(我的意思是,显式绘制一条水平线,例如,模仿“下划线”装饰)。

使用画布文本 API 可以实现这一点吗?

I am using the HTML5 canvas API to display some string (canvas.fillText), and I was wondering whether text-decoration (like underline, strikethrough, etc.) was something possible with the canvas API. Unfortunately, I found nothing about this.

The only solution I found was to manually do the decoration using the canvas drawing API (I mean, explicitly drawing a horizontal line, for example, to mimic the 'underline' decoration).

Is this possible using the canvas text API?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

メ斷腸人バ 2024-10-18 07:58:54

它不适用于内置方法,但这是我根据阅读成功使用的简化函数,“HTML5 Canvas:文本下划线解决方法"。

var underline = function(ctx, text, x, y, size, color, thickness ,offset){
  var width = ctx.measureText(text).width;

  switch(ctx.textAlign){
    case "center":
    x -= (width/2); break;
    case "right":
    x -= width; break;
  }

  y += size+offset;
  
  ctx.beginPath();
  ctx.strokeStyle = color;
  ctx.lineWidth = thickness;
  ctx.moveTo(x,y);
  ctx.lineTo(x+width,y);
  ctx.stroke();

}

It won't work with a built-in method, but here is a simplified function I used successfully based on reading, "HTML5 Canvas: Text underline workaround" on the ScriptStock website.

var underline = function(ctx, text, x, y, size, color, thickness ,offset){
  var width = ctx.measureText(text).width;

  switch(ctx.textAlign){
    case "center":
    x -= (width/2); break;
    case "right":
    x -= width; break;
  }

  y += size+offset;
  
  ctx.beginPath();
  ctx.strokeStyle = color;
  ctx.lineWidth = thickness;
  ctx.moveTo(x,y);
  ctx.lineTo(x+width,y);
  ctx.stroke();

}
尽揽少女心 2024-10-18 07:58:54

您可以通过使用 measureTextfillRect 来完成此操作,如下所示:

ctx.fillText(text, xPos, yPos);
let { width } = ctx.measureText(text);
ctx.fillRect(xPos, yPos, width, 2);

此方法唯一困难的部分是无法使用measureText 获取高度。否则,您可以在绘制 fillRect 时使用它作为 Y 坐标。

您的 Y 位置仅取决于文本的高度以及您希望下划线的距离。

堆栈片段中的演示

// get canvas / context
var can = document.getElementById('my-canvas');
var ctx = can.getContext('2d');

let xPos=10, yPos=15;
let text = "Hello World";

ctx.fillText(text, xPos, yPos);
let { width } = ctx.measureText(text);
ctx.fillRect(xPos, yPos, width, 2);
<canvas id="my-canvas" width="250" height="150"></canvas>

You can do this by using measureText and fillRect like so:

ctx.fillText(text, xPos, yPos);
let { width } = ctx.measureText(text);
ctx.fillRect(xPos, yPos, width, 2);

The only difficult part about this approach is there is no way to obtain the height use measureText. Otherwise, you could use that as your Y coordinate when drawing your fillRect.

Your Y position will only depend on the height of your text and how close you'd like the underline.

Demo in Stack Snippets

// get canvas / context
var can = document.getElementById('my-canvas');
var ctx = can.getContext('2d');

let xPos=10, yPos=15;
let text = "Hello World";

ctx.fillText(text, xPos, yPos);
let { width } = ctx.measureText(text);
ctx.fillRect(xPos, yPos, width, 2);
<canvas id="my-canvas" width="250" height="150"></canvas>

回忆躺在深渊里 2024-10-18 07:58:54

要向画布文本添加下划线,只需在与文本相同的 (x,y) 位置添加下划线字符即可。
例如,您想要在 abc 下划线

    context.fillText("abc",x,y);
    context.fillText ("___",x,y);

类似地,对于删除线,您可以使用“-”字符而不是下划线。

To add an underline to your canvas text, simply add underline characters at the same (x,y) position as your text.
e.g. you want to underline abc

    context.fillText("abc",x,y);
    context.fillText ("___",x,y);

Similarly for strike through, you would use the "-" character rather than underline.

逆流 2024-10-18 07:58:54

我创建了 Mulhoon 代码的替代版本。我还考虑了文本基线。

const underline = (ctx, text, x, y) => {
  let metrics = measureText(ctx, text)
  let fontSize = Math.floor(metrics.actualHeight * 1.4) // 140% the height 
  switch (ctx.textAlign) {
    case "center" : x -= (metrics.width / 2) ; break
    case "right"  : x -= metrics.width       ; break
  }
  switch (ctx.textBaseline) {
    case "top"    : y += (fontSize)     ; break
    case "middle" : y += (fontSize / 2) ; break
  }
  ctx.save()
  ctx.beginPath()
  ctx.strokeStyle = ctx.fillStyle
  ctx.lineWidth = Math.ceil(fontSize * 0.08)
  ctx.moveTo(x, y)
  ctx.lineTo(x + metrics.width, y)
  ctx.stroke()
  ctx.restore()
}

完整示例

const triggerEvent = (el, eventName) => {
  var event = document.createEvent('HTMLEvents')
  event.initEvent(eventName, true, false)
  el.dispatchEvent(event)
}

const measureText = (ctx, text) => {
  let metrics = ctx.measureText(text)
  return {
    width: Math.floor(metrics.width),
    height: Math.floor(metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent),
    actualHeight: Math.floor(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent)
  }
}

const underline = (ctx, text, x, y) => {
  let metrics = measureText(ctx, text)
  let fontSize = Math.floor(metrics.actualHeight * 1.4) // 140% the height 
  switch (ctx.textAlign) {
    case "center" : x -= (metrics.width / 2) ; break
    case "right"  : x -= metrics.width       ; break
  }
  switch (ctx.textBaseline) {
    case "top"    : y += (fontSize)     ; break
    case "middle" : y += (fontSize / 2) ; break
  }
  ctx.save()
  ctx.beginPath()
  ctx.strokeStyle = ctx.fillStyle
  ctx.lineWidth = Math.ceil(fontSize * 0.08)
  ctx.moveTo(x, y)
  ctx.lineTo(x + metrics.width, y)
  ctx.stroke()
  ctx.restore()
}

const getOrigin = (ctx) => ({
  x : Math.floor(ctx.canvas.width / 2),
  y : Math.floor(ctx.canvas.height / 2)
})

const redraw = (ctx, sampleText, fontSize) => {
  let origin = getOrigin(ctx)
  ctx.font = fontSize + 'px Arial'
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
  renderText(ctx, sampleText, origin.x, origin.y, 'Yellow', 'left', 'top')
  renderText(ctx, sampleText, origin.x, origin.y, 'SkyBlue ', 'right', 'bottom')
  renderText(ctx, sampleText, origin.x, origin.y, 'Tomato', 'left', 'bottom')
  renderText(ctx, sampleText, origin.x, origin.y, 'Chartreuse ', 'right', 'top')
  renderText(ctx, sampleText, origin.x, origin.y, 'Black', 'center', 'middle')
}

const renderText = (ctx, text, x, y, fillStyle, textAlign, textBaseLine) => {
  ctx.fillStyle = fillStyle
  ctx.textAlign = textAlign
  ctx.textBaseline = textBaseLine
  ctx.fillText(text, x, y)
  underline(ctx, text, x, y)
}

const sampleText = 'Hello World'
const fontSizes = [ 8, 12, 16, 24, 32 ]

document.addEventListener('DOMContentLoaded', () => {
  let ctx = document.querySelector('#demo').getContext('2d')
  let sel = document.querySelector('select[name="font-size"]')
  
  fontSizes.forEach(fontSize => sel.appendChild(new Option(fontSize, fontSize)))
  sel.addEventListener('change', (e) => redraw(ctx, sampleText, sel.value))
  sel.value = fontSizes[fontSizes.length - 1]
  triggerEvent(sel, 'change')
})
canvas { border: thin solid grey }
label { font-weight: bold }
label::after { content: ": " }
<canvas id="demo" width="360" height="120"></canvas>
<form>
  <label for="font-size-select">Font Size</label>
  <select id="font-size-select" name="font-size"></select>
</form>

I created an alternative version of Mulhoon's code. I also take into account the text baseline.

const underline = (ctx, text, x, y) => {
  let metrics = measureText(ctx, text)
  let fontSize = Math.floor(metrics.actualHeight * 1.4) // 140% the height 
  switch (ctx.textAlign) {
    case "center" : x -= (metrics.width / 2) ; break
    case "right"  : x -= metrics.width       ; break
  }
  switch (ctx.textBaseline) {
    case "top"    : y += (fontSize)     ; break
    case "middle" : y += (fontSize / 2) ; break
  }
  ctx.save()
  ctx.beginPath()
  ctx.strokeStyle = ctx.fillStyle
  ctx.lineWidth = Math.ceil(fontSize * 0.08)
  ctx.moveTo(x, y)
  ctx.lineTo(x + metrics.width, y)
  ctx.stroke()
  ctx.restore()
}

Full Example

const triggerEvent = (el, eventName) => {
  var event = document.createEvent('HTMLEvents')
  event.initEvent(eventName, true, false)
  el.dispatchEvent(event)
}

const measureText = (ctx, text) => {
  let metrics = ctx.measureText(text)
  return {
    width: Math.floor(metrics.width),
    height: Math.floor(metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent),
    actualHeight: Math.floor(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent)
  }
}

const underline = (ctx, text, x, y) => {
  let metrics = measureText(ctx, text)
  let fontSize = Math.floor(metrics.actualHeight * 1.4) // 140% the height 
  switch (ctx.textAlign) {
    case "center" : x -= (metrics.width / 2) ; break
    case "right"  : x -= metrics.width       ; break
  }
  switch (ctx.textBaseline) {
    case "top"    : y += (fontSize)     ; break
    case "middle" : y += (fontSize / 2) ; break
  }
  ctx.save()
  ctx.beginPath()
  ctx.strokeStyle = ctx.fillStyle
  ctx.lineWidth = Math.ceil(fontSize * 0.08)
  ctx.moveTo(x, y)
  ctx.lineTo(x + metrics.width, y)
  ctx.stroke()
  ctx.restore()
}

const getOrigin = (ctx) => ({
  x : Math.floor(ctx.canvas.width / 2),
  y : Math.floor(ctx.canvas.height / 2)
})

const redraw = (ctx, sampleText, fontSize) => {
  let origin = getOrigin(ctx)
  ctx.font = fontSize + 'px Arial'
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
  renderText(ctx, sampleText, origin.x, origin.y, 'Yellow', 'left', 'top')
  renderText(ctx, sampleText, origin.x, origin.y, 'SkyBlue ', 'right', 'bottom')
  renderText(ctx, sampleText, origin.x, origin.y, 'Tomato', 'left', 'bottom')
  renderText(ctx, sampleText, origin.x, origin.y, 'Chartreuse ', 'right', 'top')
  renderText(ctx, sampleText, origin.x, origin.y, 'Black', 'center', 'middle')
}

const renderText = (ctx, text, x, y, fillStyle, textAlign, textBaseLine) => {
  ctx.fillStyle = fillStyle
  ctx.textAlign = textAlign
  ctx.textBaseline = textBaseLine
  ctx.fillText(text, x, y)
  underline(ctx, text, x, y)
}

const sampleText = 'Hello World'
const fontSizes = [ 8, 12, 16, 24, 32 ]

document.addEventListener('DOMContentLoaded', () => {
  let ctx = document.querySelector('#demo').getContext('2d')
  let sel = document.querySelector('select[name="font-size"]')
  
  fontSizes.forEach(fontSize => sel.appendChild(new Option(fontSize, fontSize)))
  sel.addEventListener('change', (e) => redraw(ctx, sampleText, sel.value))
  sel.value = fontSizes[fontSizes.length - 1]
  triggerEvent(sel, 'change')
})
canvas { border: thin solid grey }
label { font-weight: bold }
label::after { content: ": " }
<canvas id="demo" width="360" height="120"></canvas>
<form>
  <label for="font-size-select">Font Size</label>
  <select id="font-size-select" name="font-size"></select>
</form>

巡山小妖精 2024-10-18 07:58:54

很遗憾,答案是“不”。 HTML Canvas 上下文的文本方法

I'm sorry to say that the answer is 'no'. There are no 'text-decoration' or similar styles available in the text methods of the HTML Canvas Context.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文