1.效果
在项目开发过程中,有一个需求是有若干个需要展示的点,每个点icon不一样、对应的广告牌文字不一样、并且文字还需要有图片背景、每个文字背景也不同(抓狂)。这种需求只能编写canvas来绘制“icon+文字背景+文字”的image,并返回给billboard。
实测100个点渲染,我这小破电脑花费140ms,能够满足项目需求;作为一个新手切图崽,所编写的代码还有很多改进的地方,有请各位大佬在评论区批评指正。
2.代码
调用renderBillboard(options)方法,将返回一个billboard对象,返回值可以直接使用。也可以使用回调,renderBillboard(options,(result)=>{}),result包含billboard所需的image和pixelOffset。
需要注意的是,vue3+vite加载图片资源使用的是new URL("地址", import.meta.url).href
而vue2加载图片资源使用的是require,在代码中均有,可以根据需要使用。
import * as Cesium from "cesium";
let canvas, context;
/*** @author: linmaoxin* @date: 2024/12/14* @description 渲染billboard,可选择 带背景的文字+图标icon、仅背景的文字 两种类型。1、返回Promise对象,异步获取billboard;2、也可以使用回调分别获取image和pixelOffset。* @param {*} options* @callback function* @returns Promise<object>* @example* let options = {type: 'text-icon', // 默认 'text-icon'类型(图标+文字)、 'text' 类型(仅文字)paddingTo: 20, // 图标距离文字背景textPadding: 20, // 文字左右间距textBcgWidth: 200, // 文字背景宽度textBcgHeight: 50, // 文字背景高度iconWidth: 60, // 图标宽度iconHeight: 70, // 图标高度textBcgUrl: '../assets/label-bcg.png', // 文字背景图片地址 iconUrl: '../assets/positionIcon.png', // 图标地址textUrl:'', // 字体地址text: '测试文字测试文字', // 文字内容textColor: '#DCD085', // 字体颜色textFontSize: 16, // 字体大小textFontWeight: 800, // 字体粗细} */
export function renderBillboard(options, callback) {let settings = {type: options.type || 'text-icon', // 默认 'text-icon'类型(图标+文字)、 'text' 类型(仅文字)paddingTo: options.paddingTo || 5, // 默认 图标距离文字背景textPadding: options.textPadding || 20, // 默认 文字左右间距textBcgWidth: options.textBcgWidth || 150, // 默认 文字背景宽度textBcgHeight: options.textBcgHeight || 40, // 默认 文字背景高度iconWidth: options.iconWidth || 40, // 默认 图标宽度iconHeight: options.iconHeight || 50, // 默认 图标高度textBcgUrl: options.textBcgUrl || console.error('请传入文字背景图片地址 textBcgUrl '), // 文字背景图片地址 iconUrl: options.iconUrl || console.error('请传入图标图片地址 iconUrl '), // 图标地址textUrl: options.textUrl, // 文字地址text: options.text || '', // 文字内容textColor: options.textColor || '#ffffff', // 默认 文字颜色textFontSize: options.textFontSize || 16, // 默认 文字大小textFontWeight: options.textFontWeight || 500, //默认 文字粗细}if (!canvas) {canvas = document.createElement('canvas');}if (!context) {context = canvas.getContext('2d', { willReadFrequently: true });}// 字体初始化context.font = `${Number(settings.textFontWeight)} ${Number(settings.textFontSize)}px Microsoft Yahei`;context.fillStyle = settings.textColor;context.textAlign = 'center';context.textBaseline = 'middle';context.canvas.willReadFrequently = true;// 根据字数计算宽度 并加上左右间距const textMetrics = context.measureText(settings.text);settings.textBcgWidth = textMetrics.width + settings.textPadding;// 画布大小if (settings.type === 'text-icon') {settings.textBcgWidth = settings.textBcgWidth > settings.iconWidth ? settings.textBcgWidth : settings.iconWidth;canvas.width = settings.textBcgWidth;canvas.height = settings.textBcgHeight + settings.paddingTo + settings.iconHeight;} else if (settings.type === 'text') {canvas.width = settings.textBcgWidth;canvas.height = settings.textBcgHeight;}return new Promise((result) => {// 加载图片资源if (settings.type === 'text-icon') {// 字体二次设置context.font = `${Number(settings.textFontWeight)} ${Number(settings.textFontSize)}px Microsoft Yahei`;context.fillStyle = settings.textColor;context.textAlign = 'center';context.textBaseline = 'middle';const image1 = new Image();const image2 = new Image();//image1.src = require(settings.iconUrl); //图标地址 vue2// image2.src = require(settings.textBcgUrl); //文字背景图片地址 vue2image1.src = new URL(settings.iconUrl, import.meta.url).href // viteimage2.src = new URL(settings.textBcgUrl, import.meta.url).href // viteimage1.onload = () => {context.drawImage(image1, 0, 0, canvas.width, canvas.height);}image2.onload = () => {context.drawImage(image2, 0, 0, canvas.width, canvas.height);}Promise.all([new Promise(resolve1 => { image1.onload = resolve1; }),new Promise(resolve2 => { image2.onload = resolve2; })]).then(() => {// 清除画布context.clearRect(0, 0, canvas.width, canvas.height);context.beginPath();// 渲染context.drawImage(image2, 0, 0, settings.textBcgWidth, settings.textBcgHeight);context.drawImage(image1, (canvas.width / 2) - (settings.iconWidth / 2), (settings.textBcgHeight + settings.paddingTo), settings.iconWidth, settings.iconHeight);context.fillText(settings.text, settings.textBcgWidth / 2, settings.textBcgHeight / 2);// 判断callback是不是函数if (typeof callback === 'function') {callback({ 'image': canvas.toDataURL(), 'pixelOffsetY': Math.round(-canvas.height / 2) })}result({image: canvas.toDataURL(),pixelOffset: new Cesium.Cartesian2(0, Math.round(-canvas.height / 2))})// console.log('canvas', canvas.height)});} else if (settings.type === 'text') {const image2 = new Image();//image2.src = require(settings.textBcgUrl); //文字背景图片地址 vue2image2.src = new URL(settings.textBcgUrl, import.meta.url).href // viteimage2.onload = () => {// 清除画布context.clearRect(0, 0, canvas.width, canvas.height);context.beginPath();// 渲染context.drawImage(image2, 0, 0, canvas.width, canvas.height);context.fillText(settings.text, settings.textBcgWidth / 2, settings.textBcgHeight / 2);// 判断callback是不是函数if (typeof callback === 'function') {callback({ 'image': canvas.toDataURL(), 'pixelOffsetY': Math.round(-canvas.height / 2), })}result({image: canvas.toDataURL(),pixelOffset: new Cesium.Cartesian2(0, Math.round(-canvas.height / 2))})}}})}
3.结尾
(1)加载图片资源其实可以用 new Cesium.Resource.fetchImage(url).then(image => {}这个方法,这是Cesium的一个异步加载图片资源的方法,并不一定要使用 new Image。
(2)实现过程本文章不过多赘述,有不懂的地方可以评论区讨论、或者问问AI。
(PS:创造不易,如果文章对你有帮助,可以点个赞鼓励下博主哦!)