vue3实现模拟地图站点名称按需显示的功能(车辆模拟地图)

 更新时间:2024年06月22日 09:43:32   作者:邹琼俊  
最近在做车辆模拟地图,在实现控制站点名称按需显示,下面通过本文给大家分享vue3实现模拟地图站点名称按需显示的功能,感兴趣的朋友跟随小编一起看看吧

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

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

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

最开始我用的比例换算法,算法复杂度是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站点名称按需显示内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Vue实现父子组件的事件传递的示例代码

    Vue实现父子组件的事件传递的示例代码

    在Vue.js这个现代化的前端框架中,组件化是其核心理念之一,正确的组件间通信是实现高效和可维护代码的关键,在许多应用程序中,父子组件之间的事件传递是一个常见的需求,,本文将深入探讨在Vue中如何实现父子组件的事件传递,并辅以示例代码来展示这些概念
    2025-01-01
  • Vue面试中observable原理详解

    Vue面试中observable原理详解

    这篇文章主要为大家介绍了vue面试observable原理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • Vue项目报错:parseComponent问题及解决

    Vue项目报错:parseComponent问题及解决

    这篇文章主要介绍了Vue项目报错:parseComponent问题及解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-05-05
  • vue axios form-data格式传输数据和文件方式

    vue axios form-data格式传输数据和文件方式

    这篇文章主要介绍了vue axios form-data格式传输数据和文件方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05
  • vue3的setup语法糖简单封装ckediter的过程

    vue3的setup语法糖简单封装ckediter的过程

    Vue3官方提供了 script setup 语法糖,只需在script标签中添加setup,组件只需引入不用注册,属性和方法也不用返回,今天通过本文给大家分享vue3的setup语法糖简单封装ckediter的过程,感兴趣的朋友一起看看吧
    2023-10-10
  • axios取消请求与避免重复请求

    axios取消请求与避免重复请求

    在项目中经常有一些场景会连续发送多个请求,而异步会导致最后得到的结果不是我们想要的,并且对性能也有非常大的影响,这篇文章主要给大家介绍了关于axios取消请求与避免重复请求的相关资料,需要的朋友可以参考下
    2021-06-06
  • Vue无法对iframe进行缓存的解决方案

    Vue无法对iframe进行缓存的解决方案

    项目采用的若依框架,但系统中会嵌入大屏、报表页面,是使用iframe来实现的,若依框架的菜单管理中提供了缓存功能,是使用keep-alive实现的,但对于iframe页面并不生效,所以本文介绍了关于Vue无法对iframe进行缓存的解决方案记录,需要的朋友可以参考下
    2024-12-12
  • vue中下载文件的方法示例详解

    vue中下载文件的方法示例详解

    这篇文章主要介绍了vue中下载文件的方法,通过实例代码讲解了下载后端返回文件和下载本地文件,本文结合实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-11-11
  • vue使用动画实现滚动表格效果

    vue使用动画实现滚动表格效果

    这篇文章主要为大家详细介绍了vue使用动画实现滚动表格效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • vue watch如何深度监听数组每一项的变化

    vue watch如何深度监听数组每一项的变化

    这篇文章主要介绍了vue watch如何深度监听数组每一项的变化问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07

最新评论