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

相关推荐
开心工作室_kaic10 分钟前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā10 分钟前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年2 小时前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder2 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_882727572 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架
SoaringHeart2 小时前
Flutter进阶:基于 MLKit 的 OCR 文字识别
前端·flutter
会发光的猪。2 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
天下代码客3 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js
猫爪笔记3 小时前
前端:HTML (学习笔记)【1】
前端·笔记·学习·html
前端李易安3 小时前
Webpack 热更新(HMR)详解:原理与实现
前端·webpack·node.js