水平与垂直定位
在canvas中使用 strokeText() 或 fillText() 绘制文本时,需要指定所绘文本的 X 和 Y 的坐标,然而,浏览器具体将文本绘制在何处,则要看 textAlign 和 textBaseline 这两个属性。
属性 | 描述 |
---|---|
textAlign | 决定文本在水平方向上的对齐方式。start、left、center、right、end,默认值start |
textBaseline | 决定文本在垂直方向上的对齐方式。top、bottom、middle、alphabetic、ideographic、hanging,默认值alphabetic |
示例
应用程序中的实心矩形表示传递给 fillText() 方法的 X 与 Y 坐标,显示的每个字符串都表示了一种 textAlign 与 textBaseline 属性值的组合。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!-- 设置文档字符编码为UTF-8 -->
<meta charset="UTF-8" />
<title>3-4-利用textAlign与textBaseline属性来定位文本</title>
<style>
/* 页面背景色 */
body {
background: #eeeeee;
}
/* 画布样式设置 */
#canvas {
background: #ffffff;
margin-top: 5px;
margin-left: 10px;
/* 兼容不同浏览器的阴影效果 */
-webkit-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);
-moz-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);
/* 边框样式 */
border: 1px solid rgba(0, 0, 0, 0.2);
}
</style>
</head>
<body>
<!-- 画布元素,不支持时显示提示 -->
<canvas id="canvas" width="780" height="440">canvas not supports</canvas>
<script>
// 使用严格模式
'use strict'
// 获取画布和绘图上下文
let canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
// 字体高度
fontHeight = 24,
// 文本水平对齐方式数组
alignValues = ['start', 'center', 'end'],
// 文本垂直对齐基线数组
baseLineValue = ['top', 'middle', 'bottom', 'alphabetic', 'ideographic', 'hanging'],
// 文本坐标
x,
y
// 函数定义...
/**
* 绘制网格线
* @param {string} color - 网格线颜色
* @param {number} stepx - 水平间距
* @param {number} stepy - 垂直间距
*/
let drawGrid = (color, stepx, stepy) => {
context.save()
// 设置网格样式
context.strokeStyle = color
context.fillStyle = '#ffffff'
context.lineWidth = 0.5
// 填充白色背景
context.fillRect(0, 0, context.canvas.width, context.canvas.height)
// 绘制垂直网格线
for (let i = stepx + 0.5; i < context.canvas.width; i += stepx) {
context.beginPath()
context.moveTo(i, 0)
context.lineTo(i, context.canvas.height)
context.stroke()
}
// 绘制水平网格线
for (let i = stepy + 0.5; i < context.canvas.height; i += stepy) {
context.beginPath()
context.moveTo(0, i)
context.lineTo(context.canvas.width, i)
context.stroke()
}
context.restore()
}
/**
* 在(x,y)位置绘制标记点
*/
let drawTextMarker = () => {
context.fillStyle = 'yellow'
// 绘制黄色小方块
context.fillRect(x, y, 7, 7)
// 绘制小方块边框
context.strokeRect(x, y, 7, 7)
}
/**
* 绘制文本
* @param {string} text - 要绘制的文本
* @param {string} textAlign - 水平对齐方式
* @param {string} textBaseLine - 垂直基线对齐方式
*/
let drawText = (text, textAlign, textBaseLine) => {
// 设置文本对齐方式
if (textAlign) context.textAlign = textAlign
if (textBaseLine) context.textBaseline = textBaseLine
// 绘制蓝色文本
context.fillStyle = 'cornflowerblue'
context.fillText(text, x, y)
}
/**
* 绘制文本参考线
*/
let drawTextLine = () => {
context.strokeStyle = 'gray'
// 绘制水平参考线
context.beginPath()
context.moveTo(x, y)
context.lineTo(x + 738, y)
context.stroke()
}
// 初始化...
// 设置字体样式
context.font = 'oblique normal bold 24px palatino'
// 绘制网格
drawGrid('lightgray', 10, 10)
// 遍历所有对齐组合
for (let align = 0; align < alignValues.length; ++align) {
for (let baseline = 0; baseline < baseLineValue.length; baseline++) {
// 计算文本位置
x = 20 + align * fontHeight * 15
y = 20 + baseline * fontHeight * 3
// 绘制文本和标记
drawText(alignValues[align] + '/' + baseLineValue[baseline], alignValues[align], baseLineValue[baseline])
drawTextMarker()
drawTextLine()
}
}
</script>
</body>
</html>
textAlign
textAlign 属性默认值是 start,它对应了基于 X 的位置,类似于“向初始看齐”、“向中间看齐”、“向结尾看齐”。这里如果当 canvas 元素的 dir 属性值是ltr时,也就是说浏览器是按照从左至右的方向来显示文本时,left效果与start相同,right 效果与 end 相同。同理,如果 dir 属性值是 rtl,浏览器从右至左来显示文本,那么 right 效果与 start 相同,left 效果与 end 一致。图示应用程序是从左至右来显示文本的。
textBaseline
textBaseline 的属性默认值是 alphabetic,改值用于绘制由基于拉丁字母的语言所组成的字符串。ideographic 值则用于绘制中文字符串或日文。hanging 绘制各种印度语字符串。top、bottom、middle与特定语言不相关,代表文本周围的边界框之内的某个位置,这个边界框也叫做“字符方框”(em square)
将文本居中
下面应用程序分别将 textAlign 与 textBaseline 属性的值设置为 center 与 middle,然后对文本进行描边与填充。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>3-5-将文本居中与某个点</title>
<style>
#canvas {
background: #eee;
}
</style>
</head>
<body>
<canvas id="canvas" width="600" height="400"> canvas not supported </canvas>
<script>
const canvas = document.getElementById('canvas'),
context = canvas.getContext('2d')
context.textAlign = 'center'
context.textBaseline = 'middle' // 文本基线,默认是alphabetic,即文本的基线是字母的基线
context.font = '64px Arial' // 字体大小和字体,必须在设置textAlig
context.fillStyle = 'blue'
context.strokeStyle = 'yellow'
context.fillText('Centered', canvas.width / 2, canvas.height / 2)
context.strokeText('Centered', canvas.width / 2, canvas.height / 2)
</script>
</body>
</html>
文本的度量
只要你做的事情与文本有关,你就得设法获取某个字符串的宽度和高度,Canvas 绘图环境对象提供了一个名为 measureText() 的方法,用以度量某个字符串的宽度。该方法所返回的 TextMetrics 对象中包含了一个名为 width 的属性,这个属性就是字符串的宽度。
警告:在调用 measureText() 方法之前先设置好字型
在使用measureText()方法时,常见的错误就是在调用完该方法之后,才去设置字型。因为measureText()方法是基于当前字型来计算字符串宽度的,因此调用方法后采取改变字型,所返回的宽度不能反映出那种字型来度量的是实际文本宽度。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>3-6-计算文本行的宽度</title>
<style>
body {
background: #eeeeee;
}
#canvas {
background: #ffffff;
margin-top: 5px;
margin-left: 10px;
-webkit-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);
-moz-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);
border: 1px solid rgba(0, 0, 0, 0.2);
}
</style>
</head>
<body>
<canvas id="canvas">canvas not supports</canvas>
<p>width展示的是在Canvas画布上的字符串'Hello'的宽度</p>
<script>
'use strict'
let canvas = document.getElementById('canvas'),
context = canvas.getContext('2d')
context.font = '24px Arial'
let word = 'Hello'
context.fillText(word, 10, 50)
context.fillText('width:' + context.measureText(word).width, 10, 100)
</script>
</body>
</html>
绘制坐标轴旁边的文本标签
绘制垂直于水平坐标轴的文本标签是非常简单的。应用程序根据坐标轴的位置、坐标轴上每条刻度线的长度,以及坐标轴于文本标签之间的距离,来决定每个文本标签的绘制坐标。
请注意,应用程序会设置textAlign和textBaseline的值,在绘制水平坐标轴上文本标签分别设置”center“和”top“,在绘制垂直坐标轴时,则设置“right”和”middle“。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>3-7-为坐标轴加上文本标签</title>
</head>
<body>
<!-- 创建画布元素 -->
<canvas id="canvas" width="1200" height="600"></canvas>
</body>
<script>
// 获取画布和绘图上下文
let canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
// 坐标轴外边距
AXIS_MARGIN = 40.5,
// 坐标原点位置(左下角)
AXIS_ORIGIN = { x: AXIS_MARGIN, y: canvas.height - AXIS_MARGIN },
// y轴顶点位置
AXIS_TOP = AXIS_MARGIN,
// x轴顶点位置
AXIS_RIGHT = canvas.width - AXIS_MARGIN,
// 横向刻度线间距
HORIZONTAL_TICK_SPACING = 10,
// 垂直刻度线间距
VERTICAL_TICK_SPACING = 10,
// X轴长度
AXIS_WIDTH = AXIS_RIGHT - AXIS_ORIGIN.x,
// y轴长度
AXIS_HEIGHT = AXIS_ORIGIN.y - AXIS_TOP,
// y轴刻度数量
NUM_VERTICAL_TICKS = AXIS_HEIGHT / VERTICAL_TICK_SPACING,
// x轴刻度数量
NUM_HORIZONTAL_TICKS = AXIS_WIDTH / HORIZONTAL_TICK_SPACING,
// 刻度线宽度
TICK_WIDTH = 10,
// 刻度线线宽
TICKS_LINEWIDTH = 0.5,
// 刻度线颜色
TICK_COLOR = 'navy',
// 坐标轴线宽
AXIS_LINEWIDTH = 1.0,
// 坐标轴颜色
AXIS_COLOR = 'blue'
// 标签与坐标轴的间距
SPACE_BETWEEN_LABELS_AND_AXIS = 15
/**
* 绘制背景网格线
* @param {string} color - 网格线颜色
* @param {number} stepX - 水平间距
* @param {number} stepY - 垂直间距
*/
let drawGrid = (color, stepX, stepY) => {
context.strokeStyle = color
context.lineWidth = 0.5
// 绘制垂直网格线
for (let i = stepX + 0.5; i < context.canvas.width; i += stepX) {
context.beginPath()
context.moveTo(i, 0)
context.lineTo(i, context.canvas.height)
context.stroke()
}
// 绘制水平网格线
for (let i = stepY + 0.5; i < context.canvas.height; i += stepY) {
context.beginPath()
context.moveTo(0, i)
context.lineTo(context.canvas.width, i)
context.stroke()
}
}
/**
* 绘制坐标轴(包含x轴和y轴)
*/
let drawAxis = () => {
context.save()
context.strokeStyle = AXIS_COLOR
context.lineWidth = AXIS_LINEWIDTH
// 绘制x轴和y轴
drawHorizontalAxis()
drawVerticalAxis()
// 设置刻度线样式
context.lineWidth = TICKS_LINEWIDTH
context.strokeStyle = TICK_COLOR
// 绘制刻度线
drawVerticalAxisTicks()
drawHorizontalAxisTicks()
context.restore()
}
/**
* 绘制x轴
*/
let drawHorizontalAxis = () => {
context.beginPath()
context.moveTo(AXIS_ORIGIN.x, AXIS_ORIGIN.y)
context.lineTo(AXIS_RIGHT, AXIS_ORIGIN.y)
context.stroke()
}
/**
* 绘制y轴
*/
let drawVerticalAxis = () => {
context.beginPath()
context.moveTo(AXIS_ORIGIN.x, AXIS_ORIGIN.y)
context.lineTo(AXIS_ORIGIN.x, AXIS_TOP)
context.stroke()
}
/**
* 绘制y轴刻度线
*/
let drawVerticalAxisTicks = () => {
let deltaY
for (let i = 1; i < NUM_VERTICAL_TICKS; i++) {
context.beginPath()
// 每5个刻度画一个长刻度线
deltaY = i % 5 === 0 ? TICK_WIDTH : TICK_WIDTH / 2
context.moveTo(AXIS_ORIGIN.x - deltaY, AXIS_ORIGIN.y - i * VERTICAL_TICK_SPACING)
context.lineTo(AXIS_ORIGIN.x + deltaY, AXIS_ORIGIN.y - i * VERTICAL_TICK_SPACING)
context.stroke()
}
}
/**
* 绘制x轴刻度线
*/
let drawHorizontalAxisTicks = () => {
let deltaY
for (let i = 1; i < NUM_HORIZONTAL_TICKS; i++) {
context.beginPath()
// 每5个刻度画一个长刻度线
deltaY = i % 5 === 0 ? TICK_WIDTH : TICK_WIDTH / 2
context.moveTo(AXIS_ORIGIN.x + i * HORIZONTAL_TICK_SPACING, AXIS_ORIGIN.y - deltaY)
context.lineTo(AXIS_ORIGIN.x + i * HORIZONTAL_TICK_SPACING, AXIS_ORIGIN.y + deltaY)
context.stroke()
}
}
/**
* 绘制坐标轴标签
*/
let drawAxisLabels = () => {
context.fillStyle = 'blue'
drawHorizontalAxisLabels()
drawVerticalAxisLabels()
}
/**
* 绘制x轴标签
*/
let drawHorizontalAxisLabels = () => {
context.textAlign = 'center'
context.textBaseline = 'top'
// 每5个刻度添加一个标签
for (let i = 0; i <= NUM_HORIZONTAL_TICKS; ++i) {
if (i % 5 === 0) {
context.fillText(
i,
AXIS_ORIGIN.x + i * HORIZONTAL_TICK_SPACING,
AXIS_ORIGIN.y + SPACE_BETWEEN_LABELS_AND_AXIS
)
}
}
}
/**
* 绘制y轴标签
*/
let drawVerticalAxisLabels = () => {
context.textAlign = 'right'
context.textBaseline = 'middle'
// 每5个刻度添加一个标签
for (let i = 0; i <= NUM_VERTICAL_TICKS; ++i) {
if (i % 5 === 0) {
context.fillText(i, AXIS_ORIGIN.x - SPACE_BETWEEN_LABELS_AND_AXIS, AXIS_ORIGIN.y - i * VERTICAL_TICK_SPACING)
}
}
}
// 初始化绘制
drawGrid('lightgray', 10, 10) // 绘制背景网格
drawAxis() // 绘制坐标轴
drawAxisLabels() // 绘制坐标轴标签
</script>
</html>
在圆弧周围绘制文本
应用程序通过如下步骤来绘制文本:
- 计算圆弧周围每个字符的绘制坐标
- 将坐标系平移至绘制字符的地点
- 将坐标系旋转 π/2 - angle 度
- 对字符进行描边或填充操作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>3-9-沿着圆弧绘制文本</title>
</head>
<body>
<canvas id="canvas" width="800" height="600"> canvas not supports </canvas>
<script>
const canvas = document.getElementById('canvas'),
context = canvas.getContext('2d')
const TEXT_FILL_STYLE = 'rgba(100, 130, 240, 0.5)',
TEXT_STROKE_STYLE = 'rgba(255, 255, 255, 0.8)',
TEXT_SIZE = 64
const circle = {
x: canvas.width / 2,
y: canvas.height / 2,
radius: 200,
}
// Functions ......
const drawCircleCenterPoint = () => {
context.save()
context.beginPath()
context.arc(circle.x, circle.y, 4, 0, Math.PI * 2, false)
context.fillStyle = 'red'
context.fill()
context.restore()
}
/**
* 1. 计算圆弧周围每个字符的绘制坐标
* 2. 将坐标轴平移至绘制字符的地点
* 3. 旋转坐标轴,使字符绘制方向与圆弧方向一致 π/2 - angle
* 4. 对字符进行描边或填充操作
* */
const drawCircleText = (text, startAngle, endAngle) => {
const radius = circle.radius,
angleDecrement = (startAngle - endAngle) / (text.length - 1)
let character,
angle = parseFloat(startAngle),
index = 0
context.save()
context.fillStyle = TEXT_FILL_STYLE
context.strokeStyle = TEXT_STROKE_STYLE
context.font = `${TEXT_SIZE}px Arial`
while (index < text.length) {
character = text[index]
context.save()
context.beginPath()
context.translate(circle.x + Math.cos(angle) * radius, circle.y - Math.sin(angle) * radius)
context.rotate(Math.PI / 2 - angle)
context.fillText(character, 0, 0)
context.strokeText(character, 0, 0)
angle -= angleDecrement
index++
context.restore()
}
context.restore()
}
// Initialization......
drawCircleCenterPoint()
context.textAlign = 'center'
context.textBaseline = 'middle'
drawCircleText('Clockwise around the circle', Math.PI * 2, Math.PI / 8)
</script>
</body>
</html>