vue3实现模拟地图上,站点名称按需显示的功能

代码写久了,戾气越来越重..... 2024-06-21 17:11:00 阅读 73

很久很久没有更新博客了,因为实在是太忙了,每天都有公司的事情忙不完.......

最近在做车辆模拟地图,在实现控制站点名称按需显示时,折腾了好一段时间,特此记录一下。最终界面如下图7路所示:

站点显示需求:首末站必须显示,从第一个站开始,如果站点名称能显示下,则显示,如果站点名称会重叠则隐藏,以此类推。当界面宽度变化时,车辆模拟地图自动变化,保证站点名称能够最大限度的显示。

最开始我用的比例换算法,算法复杂度是O,结果总是不准。尽管一开始我就觉得算法的复杂度应该是O2。我之前却一直想着只遍历一次就算出来,我也尝试过把需求描述得很详细去问chatgpt,可是它就像个傻子一样输出各种算法错误代码。

需要注意的地方:由于站点的名称内容是千奇百怪的,可以有空格,各种特殊图标,所以站点文字的长度计算是一个问题,这里是通过canvas来计算的。还有,这里我添加了一个限制,站点文字内容我最大显示120px,超出隐藏并显示省略号,站点名称上添加了title显示全称。

/**

* 获取站点名称

* @param item

* @param showFullName 是否总是显示站点全名

*/

/** */

export const getSiteName = (item: any,showFullName?:boolean=false) => {

const { siteSign } = simulatedMapConf.value;

let name = '';

if (siteSign == 'firstWord') {

name = getSubStrByPreNum(item.stationName);

} else if (siteSign == 'order') {

name = item.stationSeq + '';

} else {

if(showFullName){

name=item.stationName;

}else{

name =item.show? item.stationName:''; //show控制站点名称是否显示

}

}

return name || '';

}

/**

* 获取站点名称宽度

* @param item 站点对象

* @param showFullName 是否总是显示站点全名

* @returns

*/

export const getSiteNameWidth = (item: any,showFullName?:boolean=false) => {

const name =showFullName?item.stationName: getSiteName(item,showFullName);

const width= calculateStringWidth(name);

return width>siteMaxWidth?siteMaxWidth:width;

}

/**

* 根据字符串计算出界面渲染的宽度

* @param str

* @returns

*/

function calculateStringWidth(str:string) {

// 创建一个虚拟的 <canvas> 元素

const canvas = document.createElement('canvas');

const ctx = canvas.getContext('2d');

// 设置字体样式

ctx.font = '12px sans-serif';

// 使用 canvas 的 measureText 方法测量字符串的宽度

const width = ctx.measureText(str).width;

// 返回计算出的宽度

return width;

}

最核心的算法:

//计算上行站点,控制站点是否显示在模拟地图上

function calcSite(station: any, lineWidth: number) {

if (station.length < 1) return [];

station.forEach((f: any, index) => {

f.show=false;

});

let lastSiteLength = getSiteNameWidth(station[station.length - 1], true) / 2;//站点文字宽度

let lastLeft = getSiteCx(station[station.length - 1], station.length - 1);//最后一个站点left

lastLeft = toDecimal(lastLeft - lastSiteLength);

station.forEach((f: any, index) => {

let siteLength = getSiteNameWidth(f, true);//站点文字宽度

let bigHalf = siteLength / 2;//获取当前的半宽

f.left = getSiteCx(f, index);

if (index == 0 || index == station.length - 1) { //第一项和最后一项必须显示

f.show = true;

} else {

const preShowIndex = getLastTrueIndex(station); //获取前面最近一个显示站点的索引

const preEndLeft = toDecimal(station[preShowIndex].left + getSiteNameWidth(station[preShowIndex], true) / 2);//上一项显示的站点名称结束left位置

f.show = toDecimal(f.left - bigHalf) >=preEndLeft && preEndLeft < lastLeft; //如果上一个显示站点文字的结尾位置 小于等于 当前站点文字的开始位置 并且小于最后一个站点文字的开始位置

if (f.show && toDecimal(f.left + bigHalf) > lastLeft) {

f.show = false;

}

}

})

}

获取前面最近一个显示站点的索引:

//获取list集合中最后一项show的index位置

function getLastTrueIndex(dataList: any) {

// 从数组末尾第2项开始向前遍历

for (let i = dataList.length - 2; i >= 0; i--) {

if (dataList[i].show === true) {

return i; // 返回第一个找到的最后一个为true的索引

}

}

return -1; // 如果未找到符合条件的对象,返回-1

}

下行站点的计算有些差别,因为下行站点是从右至左,所以left基本上是反着的:

//计算下行站点,控制站点是否显示在模拟地图上 getDownSiteCx

function calcDownSite(station: any, lineWidth: number) {

if (station.length < 1) return [];

station.forEach((f: any, index) => {

f.show=false;

});

let lastSiteLength = getSiteNameWidth(station[station.length - 1], true) / 2;//站点文字宽度

let lastLeft = getDownSiteCx(station[station.length - 1], station.length - 1);//最后一个站点left

lastLeft = toDecimal(lastLeft + lastSiteLength);

station.forEach((f: any, index) => {

let siteLength = getSiteNameWidth(f, true);//站点文字宽度

let bigHalf = siteLength / 2;//获取当前的半宽

f.left = getDownSiteCx(f, index);

if (index == 0 || index == station.length - 1) { //第一项和最后一项必须显示

f.show = true;

} else {

const preShowIndex = getLastTrueIndex(station); //获取前面最近一个显示站点的索引

const preEndLeft = toDecimal(station[preShowIndex].left - getSiteNameWidth(station[preShowIndex], true) / 2);//上一项显示站的的结束left位置

f.show = toDecimal(f.left + bigHalf) <=preEndLeft && preEndLeft > lastLeft;

if (f.show && toDecimal(f.left - bigHalf) < lastLeft) {

f.show = false;

}

}

})

}

另外获取站点中心点位置的方法

//获取上行站点水平x位置

const getSiteCx = (item: any, index: number) => {

return startleft.value + dLayout.lineWidth * index;

}

//获取下行站点水平x位置

const getDownSiteCx = (item: any, index: number) => {

return downStartleft.value - layout.endLine - dLayout.downLineWidth * index;

}

说明:站点的布局采用css绝对定位。第一个版本这块我是采用的svg画的,后来发现扩展起来越来越麻烦,周末就在家花了半天时间全部改造为html实现了。

我最开始的有问题代码是上下行站点共用的,最大的问题是会出现跳站点显示的情况,代码如下的:

//计算站点,控制站点是否显示在模拟地图上

function calcSite(station: any, lineWidth: number) {

let availableWidth = (station.length - 1) * lineWidth; //总长度

//记录显示站点的长度

let totalLength = 0;

station.forEach((f: any, index) => {

let siteLength = getSiteNameWidth(f, true);

let bigHalf =siteLength / 2;//获取比较大的半宽

let bigHalfPre = 0;

//计算上一项的文字半宽

if (index >= 1) {

let siteLengthPre = getSiteNameWidth(station[index - 1], true);

bigHalfPre =siteLengthPre / 2;

}

f.left = toDecimal(lineWidth * index);

f.show =index==0?true: f.left >=toDecimal(totalLength);

if(index >= 1&&station[index-1].show&&bigHalf+bigHalfPre>lineWidth){

f.show=false;

}

if (f.show) {

let times = getDivisor(siteLength, lineWidth);

totalLength += times * lineWidth;

}

})

}

/**

* 两个数相除有余数时结果加1

* @param all 被除数 站点宽度

* @param num 除数 线宽

* @returns

*/

export const getDivisor=( all:number,item:number)=>{

if(all<=item) return 1;

let diff:number=0;

if(item<=20){

diff=2.5;

}

if(item<=30){

diff=2;

}

else if(item<=40){

diff=1.5;

}

else if(item<=46){

diff=1.05;

}

else if(item<=50){

diff=1;

}

return all%item==0?(all/item):(Math.ceil(all/item)+diff);

}

完!


上一篇: JavaScript状态数据

下一篇: Nuxt 3 路由系统详解:配置与实践指南

本文标签

显示    const    return    站点   


声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。