【问题标题】:is it possible to draw text decoration (underline, etc.) with HTML5 Canvas Text API?是否可以使用 HTML5 Canvas Text API 绘制文本装饰(下划线等)?
【发布时间】:2011-06-05 08:33:06
【问题描述】:

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

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

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

【问题讨论】:

    标签: html canvas


    【解决方案1】:

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

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

    与删除线类似,您将使用“-”字符而不是下划线。

    【讨论】:

    • 喜欢这个 hack 并且它有效,但是如果你仔细检查你会发现这条线有一些边缘。在这里实现 -> codepen.io/stormraider2495/pen/gOgNJEL
    • 你是绝对正确的,这只是一个基于旧方法的快速简单的黑客攻击,在打字机上划线和删除,该打字机将具有固定宽度的字体,因此线条将具有相同的宽度作为文本。
    【解决方案2】:

    它不适用于内置方法,但这是我根据在 ScriptStock 网站上阅读“HTML5 Canvas: Text underline workaround”成功使用的简化函数。

    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();
    
    }
    

    【讨论】:

      【解决方案3】:

      您可以像这样使用measureTextfillRect 来做到这一点:

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

      【讨论】:

        【解决方案4】:

        我创建了 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>

        【讨论】:

          【解决方案5】:

          很抱歉,答案是'否'。 text methods of the HTML Canvas Context 中没有“文本装饰”或类似样式。

          【讨论】:

          • Patrick,您仍然可以使用带有文本的普通 div 并将其放在您的画布上,这并不能真正回答您的问题,但它易于操作且易于管理。
          • @Tim 好点。不仅可以获得额外的文字装饰,还可以获得subpixel antialiasing,这与画布上的文字不同。
          • @蒂姆。但是假设我的文本有一个旋转变换,这意味着使用 CSS3 变换,我现在必须坚持使用 canvas api。
          猜你喜欢
          • 1970-01-01
          • 2012-03-24
          • 2016-01-23
          • 1970-01-01
          • 2012-02-10
          • 2013-09-26
          • 2017-12-21
          • 2011-12-23
          • 1970-01-01
          相关资源
          最近更新 更多