在知识的海洋狗刨,使用canvas绘制标签

2024-03-17 15:30

本文主要是介绍在知识的海洋狗刨,使用canvas绘制标签,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

下面总结了一些canvas绘制标签的心得体会,希望能够对你有所帮助。

绘制圆弧

<canvas id="canvas" width="300" height="300" ref="canvas"></canvas>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(150,150,50,0,Math.PI*1/2);
ctx.stroke();

运行结果:

绘制直线

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(100,100);
ctx.lineTo(200,150);
ctx.stroke();

运行结果:

绘制圆角矩形

通过圆弧与直线相结合,绘制出矩形路径。

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');var width = 100;
var height = 50;
var radius = 5;ctx.translate(100, 100);
ctx.beginPath(0);
ctx.arc(width - radius, height - radius, radius, 0, Math.PI / 2);
ctx.lineTo(radius, height);
ctx.arc(radius, height - radius, radius, Math.PI / 2, Math.PI);
ctx.lineTo(0, radius);
ctx.arc(radius, radius, radius, Math.PI, Math.PI * 3 / 2);
ctx.lineTo(width - radius, 0);
ctx.arc(width - radius, radius, radius, Math.PI * 3 / 2, Math.PI * 2);
ctx.lineTo(width, height - radius);
ctx.closePath();
// 填充背景色
ctx.fillStyle = "#ff6a61";
ctx.fill();
ctx.restore();

运行结果:

填充文本

...
ctx.font = '16px PingFangSC-Regular';
ctx.textAlign = "center";
ctx.textBaseline = 'middle';
ctx.fillStyle = '#fff';
ctx.fillText('快狗打车', 50, 25);

textBaseline 属性设置或返回在绘制文本时的当前文本基线。

运行结果:

宽度自适应

标签宽度设置定值的做法难免有些耍流氓,怎样才能实现标签宽度由文本内容撑起?canvas为我们提供了ctx.measureText(text).width接口获得文本内容宽度;标签宽度 = 文本内容宽度 + 左右内边距。

先把绘制圆角矩形背景的部分单独抽出来:

function drawRoundRect(ctx, x, y, width, height, radius, bgc) {ctx.save();ctx.translate(x, y);drawRoundRectPath(ctx, width, height, radius);ctx.fillStyle = bgc;ctx.fill();ctx.restore();
}
function drawRoundRectPath(ctx, width, height, radius) {ctx.beginPath(0);ctx.arc(width - radius, height - radius, radius, 0, Math.PI / 2);ctx.lineTo(radius, height);ctx.arc(radius, height - radius, radius, Math.PI / 2, Math.PI);ctx.lineTo(0, radius);ctx.arc(radius, radius, radius, Math.PI, (Math.PI * 3) / 2);ctx.lineTo(width - radius, 0);ctx.arc(width - radius, radius, radius, (Math.PI * 3) / 2, Math.PI * 2);ctx.lineTo(width, height - radius);ctx.closePath();
}
};
复制代码
接下来只需要设定参数调用即可  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()属性:复制画布上指定矩形的像素数据

这种方法简单粗暴,但是局限性也非常明显,先绘制后获取数据等于耍流氓。小伙伴们还有除此之外的方法不妨在评论区探讨一下。

推荐阅读

  • 前端获取微信头像 base64 数据的踩坑实践

  • 使用vue实现HTML页面生成图片

  • 如何实现微信小程序图像剪切?代码拿去用,不谢!

好文我在看????

这篇关于在知识的海洋狗刨,使用canvas绘制标签的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/819353

相关文章

Python通用唯一标识符模块uuid使用案例详解

《Python通用唯一标识符模块uuid使用案例详解》Pythonuuid模块用于生成128位全局唯一标识符,支持UUID1-5版本,适用于分布式系统、数据库主键等场景,需注意隐私、碰撞概率及存储优... 目录简介核心功能1. UUID版本2. UUID属性3. 命名空间使用场景1. 生成唯一标识符2. 数

SpringBoot中如何使用Assert进行断言校验

《SpringBoot中如何使用Assert进行断言校验》Java提供了内置的assert机制,而Spring框架也提供了更强大的Assert工具类来帮助开发者进行参数校验和状态检查,下... 目录前言一、Java 原生assert简介1.1 使用方式1.2 示例代码1.3 优缺点分析二、Spring Fr

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

java使用protobuf-maven-plugin的插件编译proto文件详解

《java使用protobuf-maven-plugin的插件编译proto文件详解》:本文主要介绍java使用protobuf-maven-plugin的插件编译proto文件,具有很好的参考价... 目录protobuf文件作为数据传输和存储的协议主要介绍在Java使用maven编译proto文件的插件

SpringBoot线程池配置使用示例详解

《SpringBoot线程池配置使用示例详解》SpringBoot集成@Async注解,支持线程池参数配置(核心数、队列容量、拒绝策略等)及生命周期管理,结合监控与任务装饰器,提升异步处理效率与系统... 目录一、核心特性二、添加依赖三、参数详解四、配置线程池五、应用实践代码说明拒绝策略(Rejected

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

Ubuntu如何分配​​未使用的空间

《Ubuntu如何分配​​未使用的空间》Ubuntu磁盘空间不足,实际未分配空间8.2G因LVM卷组名称格式差异(双破折号误写)导致无法扩展,确认正确卷组名后,使用lvextend和resize2fs... 目录1:原因2:操作3:报错5:解决问题:确认卷组名称​6:再次操作7:验证扩展是否成功8:问题已解

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

使用Docker构建Python Flask程序的详细教程

《使用Docker构建PythonFlask程序的详细教程》在当今的软件开发领域,容器化技术正变得越来越流行,而Docker无疑是其中的佼佼者,本文我们就来聊聊如何使用Docker构建一个简单的Py... 目录引言一、准备工作二、创建 Flask 应用程序三、创建 dockerfile四、构建 Docker

Python使用vllm处理多模态数据的预处理技巧

《Python使用vllm处理多模态数据的预处理技巧》本文深入探讨了在Python环境下使用vLLM处理多模态数据的预处理技巧,我们将从基础概念出发,详细讲解文本、图像、音频等多模态数据的预处理方法,... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核