vue3實現(xiàn)模擬地圖站點名稱按需顯示的功能(車輛模擬地圖)
很久很久沒有更新博客了,因為實在是太忙了,每天都有公司的事情忙不完.......
最近在做車輛模擬地圖,在實現(xiàn)控制站點名稱按需顯示時,折騰了好一段時間,特此記錄一下。最終界面如下圖所示:


站點顯示需求:首末站必須顯示,從第一個站開始,如果站點名稱能顯示下,則顯示,如果站點名稱會重疊則隱藏,以此類推。當(dāng)界面寬度變化時,車輛模擬地圖自動變化,保證站點名稱能夠最大限度的顯示。
最開始我用的比例換算法,算法復(fù)雜度是O,結(jié)果總是不準(zhǔn)。盡管一開始我就覺得算法的復(fù)雜度應(yīng)該是O2。我之前卻一直想著只遍歷一次就算出來,我也嘗試過把需求描述得很詳細(xì)去問chatgpt,可是它就像個傻子一樣輸出各種算法錯誤代碼。
需要注意的地方:由于站點的名稱內(nèi)容是千奇百怪的,可以有空格,各種特殊圖標(biāo),所以站點文字的長度計算是一個問題,這里是通過canvas來計算的。還有,這里我添加了一個限制,站點文字內(nèi)容我最大顯示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;
}
/**
* 根據(jù)字符串計算出界面渲染的寬度
* @param str
* @returns
*/
function calculateStringWidth(str:string) {
// 創(chuàng)建一個虛擬的 <canvas> 元素
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 設(shè)置字體樣式
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;//獲取當(dāng)前的半寬
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);//上一項顯示的站點名稱結(jié)束left位置
f.show = toDecimal(f.left - bigHalf) >=preEndLeft && preEndLeft < lastLeft; //如果上一個顯示站點文字的結(jié)尾位置 小于等于 當(dāng)前站點文字的開始位置 并且小于最后一個站點文字的開始位置if (f.show && toDecimal(f.left + bigHalf) > lastLeft) {
f.show = false;
}
}
})
}獲取前面最近一個顯示站點的索引:
//獲取list集合中最后一項show的index位置
function getLastTrueIndex(dataList: any) {
// 從數(shù)組末尾第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;//獲取當(dāng)前的半寬
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);//上一項顯示站的的結(jié)束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畫的,后來發(fā)現(xiàn)擴展起來越來越麻煩,周末就在家花了半天時間全部改造為html實現(xiàn)了。
我最開始的有問題代碼是上下行站點共用的,最大的問題是會出現(xiàn)跳站點顯示的情況,代碼如下的:
//計算站點,控制站點是否顯示在模擬地圖上
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;
}
})
}/** * 兩個數(shù)相除有余數(shù)時結(jié)果加1 * @param all 被除數(shù) 站點寬度 * @param num 除數(shù) 線寬 * @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);
}到此這篇關(guān)于vue3實現(xiàn)模擬地圖上,站點名稱按需顯示的功能的文章就介紹到這了,更多相關(guān)vue3站點名稱按需顯示內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue關(guān)于element穿梭框進行的修改成table表格穿梭框方式
這篇文章主要介紹了Vue關(guān)于element穿梭框進行的修改成table表格穿梭框方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04
vue 全局封裝loading加載教程(全局監(jiān)聽)
這篇文章主要介紹了vue 全局封裝loading加載教程(全局監(jiān)聽),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11

