本文章可以参考解决的问题: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