很久很久没有更新博客了,因为实在是太忙了,每天都有公司的事情忙不完.......
最近在做车辆模拟地图,在实现控制站点名称按需显示时,折腾了好一段时间,特此记录一下。最终界面如下图所示:
站点显示需求:首末站必须显示,从第一个站开始,如果站点名称能显示下,则显示,如果站点名称会重叠则隐藏,以此类推。当界面宽度变化时,车辆模拟地图自动变化,保证站点名称能够最大限度的显示。
最开始我用的比例换算法,算法复杂度是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 { name=showfullname?item.stationname:(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; }); const 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; }); const 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); }
到此这篇关于vue3实现模拟地图上,站点名称按需显示的功能的文章就介绍到这了,更多相关vue3站点名称按需显示内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论