Created
May 23, 2020 03:09
-
-
Save wxzen/9deb922f29ba464fb64fd23efdabf45d to your computer and use it in GitHub Desktop.
用高德地图自定义canvas图层绘制带渐变效果的点标记,支持鼠标交互事件/弹跳动画,适合地图渲染大量点标记
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Created: 2020-04-25 15:14:01 | |
* Author : xuwei | |
* Email : [email protected] | |
* ----- | |
* Description: 渐变点标记插件 | |
*/ | |
/* eslint-disable guard-for-in */ | |
/* eslint-disable prefer-destructuring */ | |
import TWEEN from '@tweenjs/tween.js' | |
const { | |
AMap | |
} = window; | |
class GradientMarkerManager { | |
constructor(map, colorsMapping, radius) { | |
this.map = map; | |
this.canvas = document.createElement('canvas'); | |
this.canvas.id = 'stations-canvas'; | |
this.colorsMapping = colorsMapping; | |
this.radius = AMap.Browser.retina ? radius * 2 : radius; | |
} | |
setMousemoveEventCallback(cbFunc) { | |
this.mousemoveCbFunc = cbFunc; | |
} | |
setMouseOverEventCallback(cbFunc) { | |
this.mouseOverCbFunc = cbFunc; | |
} | |
setMouseOutEventCallback(cbFunc) { | |
this.mouseOutCbFunc = cbFunc; | |
} | |
setMouseClickEventCallback(cbFunc) { | |
this.clickCbFunc = cbFunc; | |
} | |
setData(data) { | |
this.data = data; | |
} | |
addLayer() { | |
const { | |
map, | |
canvas, | |
data | |
} = this; | |
AMap.plugin('AMap.CustomLayer', () => { | |
canvas.id = 'stations-canvas'; | |
this.layer = new AMap.CustomLayer(canvas, { | |
zooms: [3, 12], | |
alwaysRender: true, //缩放过程中是否重绘,复杂绘制建议设为false | |
zIndex: 110 | |
}); | |
const ctx = canvas.getContext("2d"); | |
const onRender = () => { | |
const size = map.getSize(); //resize | |
let { | |
width, | |
height | |
} = size; | |
canvas.style.width = width + 'px'; | |
canvas.style.height = height + 'px'; | |
if (AMap.Browser.retina) { //高清适配 | |
width *= 2; | |
height *= 2; | |
} | |
canvas.width = width; | |
canvas.height = height; //清除画布 | |
this.drawCanvas(data); | |
}; | |
//events | |
map.on('click', e => { | |
const { | |
pixel | |
} = e; | |
for (const d in data) { | |
if (Object.prototype.hasOwnProperty.call(data, d)) { | |
for (let i = 0, len = data[d].length; i < len; i++) { | |
this.buildPath(map, data[d][i], ctx); | |
if (ctx.isPointInPath(pixel.x, pixel.y)) { | |
if (this.clickCbFunc) { | |
this.clickCbFunc(data[d][i]); | |
} | |
break; | |
} | |
} | |
} | |
} | |
}); | |
map.on('mousemove', e => { | |
const { | |
pixel | |
} = e; | |
let isHover = false; | |
let point = null; | |
for (const k in data) { | |
if (Object.prototype.hasOwnProperty.call(data, k)) { | |
for (let i = 0, len = data[k].length; i < len; i++) { | |
this.buildPath(map, data[k][i], ctx); | |
if (ctx.isPointInPath(pixel.x, pixel.y)) { | |
isHover = true; | |
point = data[k][i]; | |
break; | |
} | |
} | |
} | |
canvas.style.cursor = isHover ? 'pointer' : ''; | |
if (isHover) { | |
if (this.mouseOverCbFunc) { | |
this.mouseOverCbFunc(point, pixel); | |
} | |
} else if (this.mouseOutCbFunc) { | |
this.mouseOutCbFunc(); | |
} | |
} | |
}); | |
this.layer.render = onRender; | |
this.layer.setMap(map); | |
}); | |
} | |
buildPath(map, d, ctx) { | |
const pos = new AMap.LngLat(d.longitude, d.latitude); | |
const pixel = map.lngLatToContainer(pos); | |
ctx.beginPath(); | |
ctx.moveTo(pixel.x + this.radius, pixel.y); | |
ctx.arc(pixel.x, pixel.y, this.radius, 0, Math.PI * 2); | |
ctx.closePath(); | |
} | |
drawCanvas(data) { | |
const ctx = this.canvas.getContext('2d'); | |
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); | |
for (const d in data) { | |
if (Object.prototype.hasOwnProperty.call(data, d)) { | |
data[d].forEach(e => { | |
this.drawPoint(e); | |
}); | |
} | |
} | |
return ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); | |
} | |
drawPoint(p, offsetY) { | |
const ctx = this.canvas.getContext('2d'); | |
const { | |
longitude, | |
latitude, | |
color = 'green' | |
} = p; | |
const pos = new AMap.LngLat(longitude, offsetY !== undefined ? latitude + offsetY : | |
latitude); | |
let pixel = this.map.lngLatToContainer(pos); | |
if (AMap.Browser.retina) { | |
pixel = pixel.multiplyBy(2); | |
} | |
ctx.beginPath(); | |
ctx.moveTo(pixel.x + this.radius, pixel.y); | |
ctx.arc(pixel.x, pixel.y, this.radius, 0, Math.PI * 2); | |
ctx.closePath(); | |
const gradient = ctx.createRadialGradient(pixel.x, pixel.y, this.radius / 10, pixel.x, | |
pixel | |
.y, this.radius); | |
const colorList = this.colorsMapping[color]; | |
gradient.addColorStop(0, colorList[0]); | |
gradient.addColorStop(0.8, colorList[1]); | |
gradient.addColorStop(1, colorList[2]); | |
ctx.lineWidth = 0.5; | |
ctx.fillStyle = gradient; | |
ctx.fill(); | |
} | |
draw() { | |
const data = this.data; | |
if (!this.layer) { | |
this.addLayer(); | |
} else { | |
this.drawCanvas(data); | |
} | |
} | |
clear() { | |
// const ctx = this.canvas.getContext('2d'); | |
// ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); | |
if (this.layer) { | |
this.map.remove(this.layer); | |
} | |
this.layer = null; | |
this.data = null; | |
} | |
clearByType(type) { | |
const ctx = this.canvas.getContext('2d'); | |
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); | |
const data = this.data; | |
for (const k in data) { | |
if (Object.prototype.hasOwnProperty.call(data, k)) { | |
if (k === type) { | |
delete data[k]; | |
break; | |
} | |
} | |
} | |
if (JSON.stringify(data) !== '{}') { | |
this.drawCanvas(data); | |
} | |
} | |
animatePoint(stationInfo) { | |
let animatingFlag = true; | |
const data = this.data; | |
let point; | |
let index; | |
let stationType; | |
for (const k in data) { | |
if (Object.prototype.hasOwnProperty.call(data, k)) { | |
point = data[k].filter(e => e.stationCode === stationInfo.stationCode).pop(); | |
if (point !== undefined) { | |
stationType = k; | |
index = data[k].indexOf(point); | |
break; | |
} | |
} | |
} | |
const canvas = this.canvas; | |
const ctx = canvas.getContext('2d'); | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
const _data = (function () { | |
const list = {}; | |
for (const k in data) { | |
if (Object.prototype.hasOwnProperty.call(data, k)) { | |
if (k !== stationType) { | |
list[k] = data[k].slice(); | |
} else { | |
list[k] = data[k].slice(0, index).concat(data.slice(index + 1)); | |
} | |
} | |
}; | |
return list; | |
})(); | |
// console.log('size', _data.length); | |
const backgroundImgdata = this.drawCanvas(_data); | |
//start an animation | |
const params = { | |
offset: 0 | |
}; | |
const tween = new TWEEN.Tween(params) | |
.to({ | |
offset: 1 / 2 ** (this.map.getZoom() - 5) | |
}) | |
.repeat(1) | |
.easing(TWEEN.Easing.Quadratic.Out) | |
.onUpdate(() => { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
ctx.putImageData(backgroundImgdata, 0, 0); | |
this.drawPoint(point, params.offset); | |
}) | |
.onComplete(() => { | |
animatingFlag = false; | |
}) | |
.yoyo(true); | |
function animate(time) { | |
requestAnimationFrame(animate); | |
TWEEN.update(time); | |
} | |
tween.start(); | |
requestAnimationFrame(animate); | |
} | |
} | |
export default GradientMarkerManager; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment