{T}

Canvas 文本绘制

本文档详细介绍 Canvas 的文本绘制功能,包括文本渲染、样式设置、文本测量等内容。

基础文本绘制

Canvas 提供了两个基本的文本绘制方法和一个测量方法。

方法概览

方法说明
fillText(text, x, y, maxWidth)绘制填充文本
strokeText(text, x, y, maxWidth)绘制描边文本
measureText(text)测量文本宽度

参数说明

参数类型说明
textString要绘制的文本
xNumber文本起始 X 坐标
yNumber文本起始 Y 坐标(基线位置)
maxWidthNumber可选,文本最大宽度

基本用法

javascript
const canvas = document.getElementById('myCanvas')
const ctx = canvas.getContext('2d')

// 绘制填充文本
ctx.font = '30px Arial'
ctx.fillStyle = '#333'
ctx.fillText('Hello Canvas', 50, 50)

// 绘制描边文本
ctx.strokeStyle = '#e74c3c'
ctx.lineWidth = 2
ctx.strokeText('描边文本', 50, 100)

限制文本宽度

javascript
// 设置最大宽度,文本会自动缩放
ctx.fillText('这是一段很长的文本会被限制宽度', 50, 150, 200)

文本样式

font 属性

font 属性设置文本的字体样式,语法与 CSS font 属性相同。

javascript
ctx.font = 'italic bold 24px Arial, sans-serif'

语法格式: font-style font-weight font-size font-family

font-style(字体样式)

javascript
ctx.font = 'normal 20px Arial'  // 正常(默认)
ctx.font = 'italic 20px Arial'  // 斜体
ctx.font = 'oblique 20px Arial' // 倾斜

font-weight(字体粗细)

javascript
ctx.font = 'normal 20px Arial'  // 正常(默认)
ctx.font = 'bold 20px Arial'    // 粗体
ctx.font = 'lighter 20px Arial' // 更细
ctx.font = 'bolder 20px Arial'  // 更粗
ctx.font = '100 20px Arial'     // 数字值(100-900)
ctx.font = '400 20px Arial'     // 正常
ctx.font = '700 20px Arial'     // 粗体

font-size(字体大小)

javascript
ctx.font = '16px Arial'   // 像素
ctx.font = '1em Arial'    // em 单位
ctx.font = '1rem Arial'   // rem 单位
ctx.font = '12pt Arial'   // 点
ctx.font = '100% Arial'   // 百分比

font-family(字体族)

javascript
ctx.font = '20px Arial'                    // 单一字体系列
ctx.font = '20px "Courier New", monospace' // 多个备选字体
ctx.font = '20px Georgia, serif'
ctx.font = '20px "Microsoft YaHei", sans-serif'

颜色与样式

javascript
// 纯色文本
ctx.fillStyle = '#3498db'
ctx.fillText('蓝色文本', 50, 50)

// 渐变文本
const gradient = ctx.createLinearGradient(50, 0, 300, 0)
gradient.addColorStop(0, '#e74c3c')
gradient.addColorStop(1, '#f39c12')
ctx.fillStyle = gradient
ctx.fillText('渐变文本', 50, 100)

// 图案文本
const pattern = ctx.createPattern(img, 'repeat')
ctx.fillStyle = pattern
ctx.fillText('图案文本', 50, 150)

文本对齐

水平对齐 (textAlign)

textAlign 属性设置文本的水平对齐方式。

javascript
ctx.textAlign = 'start' | 'end' | 'left' | 'right' | 'center'

对齐方式说明

code
left:    文本左对齐
center:  文本居中对齐
right:   文本右对齐
start:   根据文本方向对齐(LTR 时等同于 left)
end:     根据文本方向对齐(LTR 时等同于 right)

可视化示例

javascript
const canvas = document.getElementById('myCanvas')
const ctx = canvas.getContext('2d')

// 绘制中心线
ctx.strokeStyle = '#ccc'
ctx.moveTo(200, 0)
ctx.lineTo(200, 250)
ctx.stroke()

// 不同对齐方式
const alignments = ['left', 'center', 'right']
ctx.font = '20px Arial'
ctx.fillStyle = '#333'

alignments.forEach((align, index) => {
  ctx.textAlign = align
  ctx.fillText(`textAlign: ${align}`, 200, 50 + index * 50)
})

垂直对齐 (textBaseline)

textBaseline 属性设置文本的垂直对齐方式。

javascript
ctx.textBaseline = 'top' | 'hanging' | 'middle' | 'alphabetic' | 'ideographic' | 'bottom'

基线说明

code
top:         文本顶端对齐 Y 坐标
hanging:     悬挂基线(主要用于印度文字)
middle:      文本垂直居中对齐 Y 坐标
alphabetic:  字母基线(默认)
ideographic: 表意文字基线
bottom:      文本底端对齐 Y 坐标

可视化示例

javascript
// 绘制基线
ctx.strokeStyle = '#ccc'
ctx.moveTo(0, 100)
ctx.lineTo(400, 100)
ctx.stroke()

const baselines = ['top', 'middle', 'alphabetic', 'bottom']
ctx.font = '20px Arial'
ctx.fillStyle = '#333'
ctx.textAlign = 'left'

baselines.forEach((baseline, index) => {
  ctx.textBaseline = baseline
  ctx.fillText(`baseline: ${baseline}`, 20 + index * 100, 100)
})

组合使用

javascript
// 文本居中(水平 + 垂直)
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText('居中文本', canvas.width / 2, canvas.height / 2)

文本测量

measureText() 方法返回一个 TextMetrics 对象,包含文本的测量信息。

基本用法

javascript
const text = 'Hello Canvas'
const metrics = ctx.measureText(text)

console.log('文本宽度:', metrics.width)

TextMetrics 属性

属性说明
width文本的宽度
actualBoundingBoxLeft从 textAlign 确定的对齐点到文本矩形左边的距离
actualBoundingBoxRight从 textAlign 确定的对齐点到文本矩形右边的距离
actualBoundingBoxAscent从 textBaseline 确定的基线到文本矩形顶部的距离
actualBoundingBoxDescent从 textBaseline 确定的基线到文本矩形底部的距离

计算文本高度

javascript
function getTextHeight(text, font) {
  ctx.font = font
  const metrics = ctx.measureText(text)
  return metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent
}

const height = getTextHeight('Hello', '30px Arial')
console.log('文本高度:', height)

获取文本边界框

javascript
function getTextBoundingBox(text, x, y) {
  const metrics = ctx.measureText(text)
  
  return {
    x: x - metrics.actualBoundingBoxLeft,
    y: y - metrics.actualBoundingBoxAscent,
    width: metrics.actualBoundingBoxLeft + metrics.actualBoundingBoxRight,
    height: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent
  }
}

// 使用示例
const text = 'Hello Canvas'
ctx.font = '30px Arial'
const box = getTextBoundingBox(text, 100, 100)

// 绘制文本和边界框
ctx.fillText(text, 100, 100)
ctx.strokeStyle = 'red'
ctx.strokeRect(box.x, box.y, box.width, box.height)

文本自适应宽度

javascript
function fitTextToWidth(text, maxWidth, fontSize, fontFamily) {
  ctx.font = `${fontSize}px ${fontFamily}`
  let width = ctx.measureText(text).width
  
  // 如果宽度超过最大宽度,缩小字体
  while (width > maxWidth && fontSize > 10) {
    fontSize--
    ctx.font = `${fontSize}px ${fontFamily}`
    width = ctx.measureText(text).width
  }
  
  return fontSize
}

const text = '这是一段需要自适应的文本内容'
const fontSize = fitTextToWidth(text, 300, 30, 'Arial')
ctx.font = `${fontSize}px Arial`
ctx.fillText(text, 50, 100)

文本效果

文本阴影

javascript
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'
ctx.shadowBlur = 5
ctx.shadowOffsetX = 2
ctx.shadowOffsetY = 2

ctx.font = '40px Arial'
ctx.fillStyle = '#333'
ctx.fillText('阴影文本', 50, 100)

发光文本

javascript
ctx.shadowColor = '#f39c12'
ctx.shadowBlur = 20
ctx.shadowOffsetX = 0
ctx.shadowOffsetY = 0

ctx.font = 'bold 40px Arial'
ctx.fillStyle = '#f39c12'
ctx.fillText('发光文本', 50, 100)

描边文本

javascript
ctx.font = 'bold 40px Arial'
ctx.strokeStyle = '#3498db'
ctx.lineWidth = 3
ctx.strokeText('描边文本', 50, 100)

// 填充 + 描边组合
ctx.fillStyle = '#fff'
ctx.fillText('组合文本', 50, 150)
ctx.strokeText('组合文本', 50, 150)

渐变文本

javascript
const gradient = ctx.createLinearGradient(50, 0, 350, 0)
gradient.addColorStop(0, '#e74c3c')
gradient.addColorStop(0.5, '#f39c12')
gradient.addColorStop(1, '#9b59b6')

ctx.font = 'bold 40px Arial'
ctx.fillStyle = gradient
ctx.fillText('渐变文本', 50, 100)

3D 文本效果

javascript
function draw3DText(text, x, y, depth, color) {
  ctx.font = 'bold 40px Arial'
  
  // 绘制阴影层
  for (let i = depth; i > 0; i--) {
    ctx.fillStyle = `rgba(0, 0, 0, ${0.1 * (depth - i + 1)})`
    ctx.fillText(text, x + i, y + i)
  }
  
  // 绘制主文本
  ctx.fillStyle = color
  ctx.fillText(text, x, y)
}

draw3DText('3D 文本', 50, 100, 5, '#3498db')

文本沿路径排列

javascript
function drawTextOnPath(text, pathPoints) {
  ctx.font = '20px Arial'
  
  let charIndex = 0
  let totalLength = 0
  
  // 计算路径总长度
  for (let i = 1; i < pathPoints.length; i++) {
    const dx = pathPoints[i].x - pathPoints[i-1].x
    const dy = pathPoints[i].y - pathPoints[i-1].y
    totalLength += Math.sqrt(dx * dx + dy * dy)
  }
  
  const charSpacing = totalLength / text.length
  
  // 沿路径绘制每个字符
  let currentPoint = 0
  let traveledDistance = 0
  
  for (let i = 0; i < text.length; i++) {
    const charWidth = ctx.measureText(text[i]).width / 2
    const targetDistance = i * charSpacing + charWidth
    
    // 找到目标距离对应的点
    while (currentPoint < pathPoints.length - 1) {
      const dx = pathPoints[currentPoint + 1].x - pathPoints[currentPoint].x
      const dy = pathPoints[currentPoint + 1].y - pathPoints[currentPoint].y
      const segmentLength = Math.sqrt(dx * dx + dy * dy)
      
      if (traveledDistance + segmentLength >= targetDistance) {
        const t = (targetDistance - traveledDistance) / segmentLength
        const x = pathPoints[currentPoint].x + dx * t
        const y = pathPoints[currentPoint].y + dy * t
        const angle = Math.atan2(dy, dx)
        
        ctx.save()
        ctx.translate(x, y)
        ctx.rotate(angle)
        ctx.fillText(text[i], -charWidth, 0)
        ctx.restore()
        
        break
      }
      
      traveledDistance += segmentLength
      currentPoint++
    }
  }
}

// 使用示例
const path = [
  { x: 50, y: 100 },
  { x: 150, y: 50 },
  { x: 250, y: 100 },
  { x: 350, y: 50 }
]
drawTextOnPath('沿路径排列的文本', path)

综合示例

文本按钮

javascript
function drawTextButton(text, x, y, width, height) {
  // 按钮背景
  const gradient = ctx.createLinearGradient(x, y, x, y + height)
  gradient.addColorStop(0, '#667eea')
  gradient.addColorStop(1, '#764ba2')
  
  ctx.fillStyle = gradient
  ctx.beginPath()
  ctx.roundRect(x, y, width, height, 8)
  ctx.fill()
  
  // 按钮阴影
  ctx.shadowColor = 'rgba(0, 0, 0, 0.3)'
  ctx.shadowBlur = 10
  ctx.shadowOffsetY = 4
  
  // 文本
  ctx.font = 'bold 16px Arial'
  ctx.textAlign = 'center'
  ctx.textBaseline = 'middle'
  ctx.fillStyle = 'white'
  ctx.fillText(text, x + width / 2, y + height / 2)
}

drawTextButton('点击按钮', 100, 100, 150, 50)

文本动画(打字机效果)

javascript
let text = '这是打字机效果示例文本'
let currentIndex = 0

function typeWriter() {
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  
  ctx.font = '30px Arial'
  ctx.fillStyle = '#333'
  
  const displayText = text.substring(0, currentIndex)
  ctx.fillText(displayText, 50, 100)
  
  // 光标
  const metrics = ctx.measureText(displayText)
  ctx.fillRect(50 + metrics.width, 70, 2, 30)
  
  if (currentIndex < text.length) {
    currentIndex++
    setTimeout(typeWriter, 100)
  }
}

typeWriter()

多行文本

javascript
function drawMultiLineText(text, x, y, maxWidth, lineHeight) {
  const words = text.split('')
  let line = ''
  let lineCount = 0
  
  ctx.font = '20px Arial'
  ctx.textBaseline = 'top'
  
  for (let i = 0; i < words.length; i++) {
    const testLine = line + words[i]
    const metrics = ctx.measureText(testLine)
    
    if (metrics.width > maxWidth && line !== '') {
      ctx.fillText(line, x, y + lineCount * lineHeight)
      line = words[i]
      lineCount++
    } else {
      line = testLine
    }
  }
  
  // 绘制最后一行
  ctx.fillText(line, x, y + lineCount * lineHeight)
}

const text = '这是一段很长的文本,需要自动换行显示。Canvas 不支持自动换行,需要手动实现换行逻辑。'
drawMultiLineText(text, 50, 50, 300, 30)

常见问题

1. 中文字体问题

问题: 中文字体显示不正常。

解决方案: 确保指定了支持中文的字体。

javascript
ctx.font = '20px "Microsoft YaHei", "SimHei", sans-serif'

2. 文本模糊

问题: 文本在高 DPI 屏幕上模糊。

解决方案: 根据 devicePixelRatio 调整 Canvas 尺寸。

javascript
const dpr = window.devicePixelRatio || 1
canvas.width = width * dpr
canvas.height = height * dpr
canvas.style.width = width + 'px'
canvas.style.height = height + 'px'
ctx.scale(dpr, dpr)

3. 文本换行

问题: Canvas 不支持自动换行。

解决方案: 手动实现换行逻辑(参见多行文本示例)。


下一步学习


返回Canvas 教程目录 | 上一篇样式与效果