接下来只需要设定参数调用即可
var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); ctx.font = "16px PingFangSC-Regular"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillStyle = "#fff"; var config = { paddingLeft: 20, // 文本左内边距 paddingRight: 20, // 文本右内边距 labelHeight: 50, // 标签高度 labelRadius: 5, // 圆角 labelBackgroundColor: "#ff6a61" // 标签背景色 }; var x = 100; var y = 100; var str = "快狗打车"; var textWidth = ctx.measureText(str).width; drawRoundRect( ctx, x, y, textWidth + config.paddingLeft + config.paddingRight, config.labelHeight, config.labelRadius, config.labelBackgroundColor); ctx.fillText( str, x + config.paddingLeft + textWidth / 2, y + config.labelHeight / 2 ); var x = 100; var y = 200; var str = "快狗打车-前端团队"; var textWidth = ctx.measureText(str).width; drawRoundRect( ctx, x, y, textWidth + config.paddingLeft + config.paddingRight, config.labelHeight, config.labelRadius, config.labelBackgroundColor); ctx.fillText( str, x + config.paddingLeft + textWidth / 2, y + config.labelHeight / 2 );
运行结果:
measureText() 方法返回包含一个对象,该对象包含以像素计的指定字体宽度。
多标签自动换行
如何实现绘制多标签自动换行?
标签的实际占用空间宽度 = 文本宽度 + 左右内边距 + 左右外边距
遍历所有标签文本,根据限定空间宽度与标签的实际占用空间宽度计算出每一个标签的具体坐标值。
配置参数:
var labelList = [ { "id": 1, "name": "小型面包" }, { "id": 2, "name": "金杯" }, { "id": 3, "name": "依维柯" }, { "id": 4, "name": "商务车" }, { "id": 5, "name": "皮卡" }, { "id": 6, "name": "冷藏车" }, { "id": 7, "name": "平板货车" }, { "id": 8, "name": "高栏货车" }, { "id": 9, "name": "宽体尾板" }, { "id": 10, "name": "厢式货车" }, { "id": 11, "name": "其它" } ] var config = { // 标签范围参数 spaceX: 0, // x坐标 spaceY: 0, // y坐标 spaceWidth: 300, // 宽度 spaceHeight: 300, // 高度 // 标签参数 paddingRight: 10, // 文本至左边框距离 paddingLeft: 10, // 文本至右边框距离 marginTop: 0, // 上外边界 marginRight: 10, // 右外边界 marginBottom: 10, // 下外边界 marginLeft: 0, // 左外边界 labelHeight: 30, // 高度 labelRadius: 5, // 圆角 labelBackgroundColor: '#ff6a61', // 背景色 // 字体参数 fontSize: 12, // 字体大小 fontColor: '#fff', // 字体颜色 fontFamily: 'PingFangSC-Regular', // 字体类型 }
遍历标签列表计算出每一个标签具体参数:
function formatLine(ctx, list, config) { let labelLine = []; let lineIndex = 0; let usedWidth = 0; list.forEach(item => { item.textWidth = ctx.measureText(item.name).width; // 文字占据空间 let labelSpace = item.textWidth + config.paddingLeft + config.paddingRight + config.marginLeft + config.marginRight; // 标签实际占据宽度 if(usedWidth + labelSpace > config.spaceWidth) { usedWidth = 0; lineIndex = lineIndex + 1; } item.x = config.spaceX + usedWidth + config.marginLeft; item.y = config.spaceY + lineIndex * (config.labelHeight + config.marginTop + config.marginBottom) + config.marginTop; labelLine.push(item); usedWidth = usedWidth + labelSpace; }); return labelLine }
接下来就是遍历标签进行绘制了:
let canvas = document.getElementById('canvas'); let ctx = canvas.getContext('2d'); // 填充背景色,以便于观察边界 ctx.fillStyle = "#ccc"; ctx.fillRect(config.spaceX, config.spaceY, config.spaceWidth, config.spaceHeight); let labelLine = formatLine(ctx, labelList, config); drawLabel(ctx, labelLine, config); function drawLabel(ctx, labelLine, config) { ctx.font = `${config.fontSize}px ${config.fontFamily}`; ctx.textAlign = "center"; ctx.textBaseline = 'middle'; ctx.fillStyle = config.fontColor; labelLine.map((item)=>{ drawRoundRect(ctx, item.x, item.y, item.textWidth + config.paddingLeft + config.paddingRight , config.labelHeight , config.labelRadius , config.labelBackgroundColor); ctx.fillText(item.name, item.x + config.paddingLeft + item.textWidth/2, item.y + config.labelHeight/2); }) }
运行结果:
延伸
那么到这里标签的绘制已经结束。鬼机灵的小伙伴可能会想到既然ctx.measureText(text).width
可以获得文本实际占据宽度,那ctx.measureText(text).height
肯定就是获取文本实际占据高度了呗(俺也一样),很遗憾并不是_(°:з」∠)_)
canvas并没有提供获取文本高度的接口,需要通过其他方式间接获取。所幸,我还是百度到了一种解决方案(百度打钱_(°:з」∠)_)——通过获取指定范围内的所有像素数据,计算非白色像素点之间的最大高度差值即为文本实际像素高度。说人话就是找出第一个与最后一个非白色像素点,两者所在像素行之间的差值即为文本实际高度。
核心代码如下
function measureTextHeight(ctx, x, y, width, height) { // 从画布获取像素数据 var data = ctx.getImageData(x, y, width, height).data, first = false, last = false, r = height, c = 0; // 找到最后一行非白色像素 while (!last && r) { r--; for (c = 0; c < width; c++) { if (data[r * width * 4 + c * 4 + 3]) { last = r; break; } } } // 找到第一行非白色像素 while (r) { r--; for (c = 0; c < width; c++) { if (data[r * width * 4 + c * 4 + 3]) { first = r; break; } } if (first != r) return last - first; } return 0; }
getImageData()属性:复制画布上指定矩形的像素数据
这种方法简单粗暴,但是局限性也非常明显,先绘制后获取数据等于耍流氓。小伙伴们还有除此之外的方法不妨在评论区探讨一下。