H5页面手绘轨迹路径-过程中允许拖动+缩放地图

本文章可以参考解决的问题:H5端的多指、单指操作混乱的问题; mapbox-gl手绘轨迹线。

希望本文能帮助到其他人!

对于"在H5页面支持在地图上手绘轨迹"这个需求,从需求层面看比较简单。

作为开发,你会怎么做?

第一映像是 锁定地图,绘制过程中地图不跟随移动,否则手指在移动过程中手指的坐标不会变化,这样就完成了这一需求。

可以的!完全没问题。

但优秀吗?

从用户体验上,一旦锁定地图,在绘制过程中用户要操作的区域到可视区之外的话,则不好完了,怎么办?增加锁定、释放,移动等一系列辅助按钮?

功能上可以了,满足。

但完美吗?

从UI层面,增加了按钮,增加用户操作,体验不太好。

怎么让用户操作起来更丝滑?尽量保留地图的原生操作:1. 双指放大缩小地图。 2. 单指移动地图。直接上代码片段:

javascript 复制代码
// 几种特殊情况: 多指开始->中间一个一个单指结束 ,单指开始 -> 中间加入多指。 处理方式:只要存在多指情况则不处理,且注意延迟。
let lnglats = [], moveTempLnglat = [], lastLnglat, locusChanging = false, touchCount = 0,handleTimer, singleTouch = true, handling = false // 1-单指, 2-双指。从开始start 到结束 end 一次操作才算完成
const tolerance = {10: 1000, 11: 1000, 12: 800, 13: 400, 14: 200, 15: 100, 16: 50, 17: 20, 18: 10, 19: 5, 20: 2, 21: 1}
const setLocus = () => {
    map.getSource("mylocus").setData({
        type: "FeatureCollection",
        features: [
            {
                type: "Feature",
                properties: {},
                geometry: {
                    type: "LineString",
                    coordinates: lnglats
                }
            }
        ]
    })
}


const startHandler = e => {
    const touchLen = e.originalEvent.changedTouches.length
    touchCount += touchLen
    // 任一超一个触点,则视为多指,必须在所有触点释放后才能做后续操作
    if (touchCount > 1) {
        singleTouch = false
    }
    // 检查是否在附近,只有在附近才视为连续绘制
    if (singleTouch && touchLen === 1 && lnglats.length > 1) {
        const dis = turf.rhumbDistance(turf.point(lnglats[lnglats.length - 1]), turf.point([e.lngLat.lng, e.lngLat.lat]), {units: "meters"})
        if (dis > tolerance[Math.round(map.getZoom())]){
            map.dragPan.enable()
            return
        }
    }
    clearTimeout(handleTimer)
    // 清空移动预存
    moveTempLnglat = []
    // 避免双指延迟
    handleTimer = setTimeout(() => {
        if (singleTouch && touchLen === 1) {
            handling = true
            lnglats.push([e.lngLat.lng, e.lngLat.lat], ...moveTempLnglat.splice(0, moveTempLnglat.length))
            setLocus()
        }
    }, 200)
}

const moveHandler = e => {
    const touchLen = e.originalEvent.targetTouches.length
    if (singleTouch && touchLen === 1) {
        if (handling) {
            lnglats.push([e.lngLat.lng, e.lngLat.lat])
            setLocus()
        } else {
            moveTempLnglat.push([e.lngLat.lng, e.lngLat.lat])
        }
    }
}

const endHandler = e => {
    const touchLen = e.originalEvent.changedTouches.length
    touchCount -= touchLen
    // 之前是单指模式,则开始动作
    if (singleTouch && touchLen === 1 && handling) {
        lnglats.push([e.lngLat.lng, e.lngLat.lat])
        setLocus()
    }

    if (touchCount === 0) {
        singleTouch = true
        handling = false
    }
    if (locusChanging) {
        map.dragPan.disable()
    }
}

const startChangeLocus = () => {
    const map = mapObj.value
    lnglats = []
    moveTempLnglat = []
    handling = false

    map.dragPan.disable()
    // 测试:锁定地图,不允许拖动
    if (!map.getLayer("mylocus")) {
        map.addLayer({
            id: "mylocus",
            type: "line",
            source: {
                type: "geojson",
                data: {type: "FeatureCollection", features: []}
            }
        })
    } else {
        setLocus()
    }
    if (!locusChanging) {
        map.on("touchstart", startHandler)
        map.on("touchmove", moveHandler)
        map.on("touchend", endHandler)
    }
    locusChanging = true
}
const stopChangeLocus = () => {
    const map = mapObj.value
    map.dragPan.enable()
    map.off("touchstart", startHandler)
    map.off("touchmove", moveHandler)
    map.off("touchend", endHandler)
    locusChanging = false
}

主要思路: 只要触发了多触点,则视为当前多指操作,不做绘制;同时,预留了双指两个指头不同步的情况。

总结:

在使用触摸事件时,注意 changeTouches,touches,targetTouches 属性的在touchstart,touchmove,touchend 事件中分别的含义。

TouchEvent.changedTouches - Web API 接口参考 | MDN

TouchEvent.targetTouches - Web API 接口参考 | MDN

TouchEvent.touches - Web API 接口参考 | MDN

相关推荐
Seraphina361 小时前
DVWA(SQL注入-low,medium,XSS反射-low)
前端·数据库·笔记·sql·网络安全·web·xss
Access开发易登软件1 小时前
Access 导入 Markdown 表格:把文档里的数据写进表
前端·html·vba·markdown·access开发
扶风ff1 小时前
求职笔试资料太散?用练题簿在线刷题,按岗位整理练习清单
前端·学习·小程序
浪浪山_大橙子1 小时前
公司里的 AI,终于不只会聊天:我用 GPT‑6 把企业工作伙伴开源了
前端·后端·面试
琹箐1 小时前
命令行调用接口
前端
可乐鸡翅yeah_2 小时前
AES‑128 加密 M3U8,IV 初始化向量新手容易踩坑
前端·网络·数据库·ffmpeg·音视频·m3u8在线
MetWeave2 小时前
机场天气的"广播系统":三种报文一次讲明白
前端
xieter2 小时前
【技术精选】深入浅出现代前端渲染与性能优化实践指南 (2026-10-04)
前端·javascript
计算机魔术师3 小时前
Anthropic 密会宗教领袖谈 Claude 意识,OpenAI 为什么急眼
前端
xieter3 小时前
【硬核实战】TypeScript Compiler API: Preserving Child Node Narrowing in Reusable Type Guards 🔧 (2026-10-03)
前端·javascript