最近做的一个项目需要做出地图上特定位置有光柱并且闪烁,点击地图选中区域变色若有光柱则切换光柱为坐标,坐标上下浮动,研究了一久最后决定使用echarts来做地图,光柱和坐标动效则使用canvas添加在图层上方
div层为echarts地图图层
在下面两个canvas图层中添加动效,为使canvas图层不影响地图的是用在上面添加pointer-events: none
<body>
<div id="chart1" style="width: 100%;height: 37.5rem;position: absolute;pointer-events: auto;"></div>
// 'chart1'此图层为调用echarts绘制地图的图层
<canvas id="test" ; width="1680" height="1000" style="position: absolute; z-index: 1;pointer-events: none;"></canvas>
<canvas id="test2" ; width="1680" height="1000" style="position: absolute; z-index: 2;pointer-events: none;"></canvas>
// 这两个canvas是我项目使用的浮在地图上的光柱和其他内容图层
</body>
echart地图配置:
引入echarts的js和要使用地图的js
(js或json官网就有,在官网下载最新版本文件夹中map下载)
<script src="js/echarts.min.js" type="text/javascript" charset="utf-8"></script>
<script src="js/map/js/china.js"></script>
地图初始化,详细配置项就不细赘,直接看官网
echart 中国,世界,省份的初始配置
中国地图:
geo: {
show: true,
map: 'china',
label: {
normal: {
show: false
},
emphasis: {
show: false,
}
}
省份地图(直接使用名称):
geo: {
show: true,
map: "安徽",
label: {
normal: {
show: false
},
emphasis: {
show: false,
}
}
世界地图:
geo: {
show: true,
map: 'world',
label: {
normal: {
show: false
},
emphasis: {
show: false,
}
}
地图初始后就来解决需求:地图上特定位置有光柱并且闪烁
光柱闪烁的动效可以使用canvas来制作
(对canvas不熟的小伙伴可以看这个
https://www.runoob.com/html/html5-canvas.html
https://www.runoob.com/w3cnote/html5-canvas-intro.html),
传入经纬度转化为页面上的像素坐标就要使用echarts中的api
// [128.3324, 89.5344] 表示 [经度,纬度]。
// chart为需要操作echart
// geo要进行操作的geo坐标系
// 使用第一个 geo 坐标系进行转换:
chart.convertToPixel('geo', [128.3324, 89.5344]); // 参数 'geo' 等同于 {geoIndex: 0}
// 使用第二个 geo 坐标系进行转换:
chart.convertToPixel({geoIndex: 1}, [128.3324, 89.5344]);
// 使用 id 为 'bb' 的 geo 坐标系进行转换:
chart.convertToPixel({geoId: 'bb'}, [128.3324, 89.5344]);
转化后就可以在传入位置上进行绘图了
(这里的canvas我绘制的位置是在另一个canvas图层上,分层绘制减少图层间刷新时的干扰)
var coord=chart.convertToPixel('geo', [128.3324, 89.5344]);
ctx.beginPath();
//绘制矩形
ctx.fillRect(coord[0] - 8, coord[1] + 9, 16, -80);
//绘制完矩形后让其闪动则可以在一定时间内增加减少其透明度
ctx.closePath();
最终效果

绘制完成为矩形添加动态效果,项目需求是闪烁,我这里的思路是使用渐变填充颜色,在改变渐变白色部分的透明度即可.(若有更好做法,望不吝赐教,感激感激)
最后就是阴影部分,这个地方花了我很长时间去做,虽然到达了目的但感觉我使用的方法并不是最优解,特此记录
最近收到私信说看的不是太明白(自以为写得很好…惭愧…这就是所谓的写得差还不自知吧…捂脸),这里重新整理了部分源码发在下面,希望能帮助到你们
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>中国地图</title>
<!-- 引入 ECharts 文件 -->
<link rel="stylesheet" type="text/css" href="css/China.css" />
<script src="js/echarts.min.js" type="text/javascript" charset="utf-8"></script>
<script src="js/map/js/china.js"></script>
</head <body>
<div id="map-layer"></div>
<canvas id="light-piller-layer" width="1680" height="1000"></canvas>
<canvas id="icon-layer" ; width="1680" height="1000"></canvas>
</body>
</html>
<script type="text/javascript">
var showMap = {
"北京": {
name: "北京市",
rank: 1,
position: [116.405285, 39.904989],
city: [{
name: '顺义',
rank: 2
},
{
name: '大兴',
rank: 1
},
{
name: '海淀',
rank: 1
}
]
},
"天津": {
name: "天津市",
rank: 2,
position: [117.190182, 39.125596],
city: [{
name: '顺义',
rank: 2
},
{
name: '大兴',
rank: 1
},
{
name: '海淀',
rank: 1
}
]
},
"云南": {
name: "云南省",
rank: 2,
position: [102.702913, 25.050202],
city: [{
name: '昆明',
rank: 4
},
{
name: '曲靖',
rank: 2
}
]
},
"西藏": {
name: "西藏省",
rank: 3,
position: [88.112616, 31.668426],
city: [{
name: '拉萨',
rank: 1
}, ]
},
"黑龙江": {
name: "黑龙江省",
rank: 4,
position: [128.60352, 47.758573],
city: [{
name: '哈尔滨',
rank: 4
},
{
name: '大兴',
rank: 2
},
{
name: '齐齐哈尔',
rank: 1
},
{
name: '吼吼',
rank: 3
},
{
name: '吼吼',
rank: 3
}
]
},
}
var changeMap
changeMap = JSON.parse(JSON.stringify(showMap))
var mapLayer = echarts.init(document.getElementById("map-layer"));
var lightPillerLayer = document.getElementById("light-piller-layer")
var iconLayer = document.getElementById("icon-layer")
var lightPillerLayerConText = lightPillerLayer.getContext("2d");
var iconLayerContext = iconLayer.getContext("2d");
// 使用刚指定的配置项和数据显示图表
option = {
backgroundColor: "#00041e", //画布背景颜色
animation: false,
geo: [{
id: 'geo1',
zlevel: 2, //geo显示级别,默认是0
map: 'china', //地图名
roam: true, //设置为false,不启动roam就无所谓缩放拖曳同步了
zoom: 1.1, //缩放级别
itemStyle: { //顶层地图的样式,如地图区域颜色,边框颜色,边框大小等
normal: {
areaColor: '#08407D',
borderColor: '#00FFFF',
borderWidth: 0.5
}
},
},
{
id: 'geo2',
map: 'china', //地图名
roam: true, //设置为false,不启动roam就无所谓缩放拖曳同步了
zoom: 1.1, //缩放级别
showLegendSymbol: false,
itemStyle: { //顶层地图的样式,如地图区域颜色,边框颜色,边框大小等
areaColor: 'rgba(245,248,254,0.4)',
borderWidth: 1, //设置外层边框
shadowColor: 'rgba(150,202,236, 0.9)',
shadowOffsetX: -5,
shadowOffsetY: 10,
opacity: 1,
shadowBlur: 15
}
}
],
}
var geo2, geo3, mapLayerClick, mapLayerMousemove = false,
mapLayerGeoroam = false
mapLayer.on('georoam', function(params) {
// 控制台打印数据的名称
// console.log(params);
mapLayerGeoroam=true
var option = mapLayer.getOption(); //获得option对象
// console.log(option);
if (params.zoom != null && params.zoom != undefined) { //捕捉到缩放时
if (option.geo.length == 2) {
option.geo[1].zoom = option.geo[0].zoom; //下层geo的缩放等级跟着上层的geo一起改变
option.geo[1].center = option.geo[0].center; //下层的geo的中心位置随着上层geo一起改变
}
} else { //捕捉到拖曳时
// mapLayer.clear()
// if (option.geo[1].center == null || option.geo[1].center != option.geo[0].center) {
// geo3=option
if (mapLayerMousemove) {
if (option.geo.length == 2) {
geo2 = option.geo[1]
} else {
geo2.center = option.geo[0].center;
}
option.geo = option.geo[0]
}
}
mapLayer.setOption(option, true); //设置option
// mapLayer.setOption(option); //设置option
});
mapLayer.on("mousemove", function() {
if (mapLayerClick) {
mapLayerMousemove = true
} else {
mapLayerMousemove = false
}
});
mapLayer.on("mousedown", function() {
// if (mapLayerClick == false) {
// mapLayerClick = true
// }
mapLayerClick = true
// console.log("mousedown")
});
mapLayer.on("mouseup", function() {
var option = mapLayer.getOption();
console.log(option)
if(option.geo.length==1){
if(mapLayerGeoroam==true){
mapLayerGeoroam=false
option.geo.push(geo2)
mapLayer.setOption(option, true);
}
}
console.log(option)
mapLayerClick = false
console.log("mouseup")
});
mapLayer.setOption(option, true);
var img = new Image();
var timer = null,
disktimer = null
var name
var validateName;
var iconPosition
mapLayer.on('click', function(params) {
validateName = params.name;
if (showMap[validateName]) {
name = params.name
clearInterval(timer)
clearInterval(disktimer)
iconLayerContext.clearRect(0, 0, iconLayer.width, iconLayer.height)
if (changeMap[name]) {
changeMap = JSON.parse(JSON.stringify(showMap))
delete(changeMap[name])
timer = setInterval(function() {
//这儿放要执行的代码, 参数在外面准备好
iconAnimation(iconPosition)
}, 20)
sumY = 1
disktimer = setInterval(function() {
iconPosition = mapLayer.convertToPixel('geo', showMap[name].position)
//这儿放要执行的代码, 参数在外面准备好
}, 150)
// cityName.innerText = showMap[name].name;
} else {
changeMap = JSON.parse(JSON.stringify(showMap))
}
}
});
var disk = {
angle: 10
}
var iconChange = {
abeam: 22,
portrait: 9,
height: 26
}
function iconDraw(iconPosition) {
iconLayerContext.beginPath();
iconPosition = mapLayer.convertToPixel('geo', showMap[name].position)
var styleColor
if (showMap[name].rank == 1) {
styleColor = {
r: 50,
g: 181,
b: 115
}
} else if (showMap[name].rank == 2) {
styleColor = {
r: 241,
g: 188,
b: 26
}
} else if (showMap[name].rank == 3) {
styleColor = {
r: 255,
g: 152,
b: 0
}
} else {
styleColor = {
r: 233,
g: 63,
b: 69
}
}
iconLayerContext.strokeStyle = 'rgba(' + styleColor.r + ',' + styleColor.g + ',' + styleColor.b + ',0.2)';
// BezierEllipse1(iconLayerContext, iconPosition[0], iconPosition[1] + 12, 6, 2);
BezierEllipse1(iconLayerContext, iconPosition[0], iconPosition[1], 6, 2);
iconLayerContext.fillStyle = 'rgba(' + styleColor.r + ',' + styleColor.g + ',' + styleColor.b + ',0.4)';
iconLayerContext.fill();
iconLayerContext.closePath();
iconLayerContext.beginPath();
iconLayerContext.strokeStyle = 'rgba(' + styleColor.r + ',' + styleColor.g + ',' + styleColor.b + ',0.6)';
// BezierEllipse1(iconLayerContext, iconPosition[0], iconPosition[1] + 12, iconChange.abeam, iconChange.portrait);
BezierEllipse1(iconLayerContext, iconPosition[0], iconPosition[1], iconChange.abeam, iconChange.portrait);
if (showMap[name].rank == 1) {
img.src = "./img/icon1.png";
} else if (showMap[name].rank == 2) {
img.src = "./img/icon2.png";
} else if (showMap[name].rank == 3) {
img.src = "./img/icon3.png";
} else {
img.src = "./img/icon4.png";
}
// iconLayerContext.drawImage(img, iconPosition[0] - 21, iconPosition[1] - iconChange.height, 40, 40);
iconLayerContext.drawImage(img, iconPosition[0] - 21, iconPosition[1] - iconChange.height - 12, 40, 40);
iconLayerContext.closePath();
}
var iconAdd, iconHeight
function iconMove() {
if (Math.floor(iconChange.abeam) == 16) {
iconAdd = +0.1
} else if (Math.floor(iconChange.abeam) == 22) {
iconAdd = -0.1
}
if (Math.floor(iconChange.height) == 26) {
iconHeight = +0.1
} else if (Math.floor(iconChange.height) == 30) {
iconHeight = -0.1
}
iconChange.abeam = iconChange.abeam + iconAdd
iconChange.portrait = iconChange.portrait + iconAdd
iconChange.height = iconChange.height + iconHeight
}
function iconAnimation(iconPosition) {
iconMove(); //更新
iconLayerContext.clearRect(0, 0, iconLayer.width, iconLayer.height); //清除
iconDraw(iconPosition); //绘制
}
var change = {
light: 40,
abeam: 11,
portrait: 5
}
var lightAdd, abearAdd
function move() {
if (change.light == 40) {
lightAdd = +1
} else if (change.light == 100) {
lightAdd = -1
}
change.light = change.light + lightAdd
if (Math.floor(change.abeam) == 11) {
abearAdd = +0.1
} else if (Math.floor(change.abeam) == 18) {
abearAdd = -0.1
}
// console.log(change.abeam)
change.abeam = change.abeam + abearAdd
change.portrait = change.portrait + abearAdd
}
function draw() {
echarts.util.each(changeMap, function(dataItem, idx) {
var geoCoord = showMap[idx].position;
var coord = mapLayer.convertToPixel('geo', geoCoord);
var linkColor = {
r: 50,
g: 181,
b: 115
}
if (showMap[idx].rank == 1) {
linkColor = {
r: 50,
g: 181,
b: 115
}
} else if (showMap[idx].rank == 2) {
linkColor = {
r: 241,
g: 188,
b: 26
}
} else if (showMap[idx].rank == 3) {
linkColor = {
r: 255,
g: 152,
b: 0
}
} else {
linkColor = {
r: 233,
g: 63,
b: 69
}
}
var grd = lightPillerLayerConText.createLinearGradient(coord[0] - 10, coord[1] + 9, coord[0], coord[1] - 81);
grd.addColorStop(0, "rgba(" + linkColor.r + "," + linkColor.g + "," + linkColor.b + "," + change.light / 100 +
")");
grd.addColorStop(1, "rgba(255,255,255,0)");
lightPillerLayerConText.shadowColor = "rgba(" + linkColor.r + "," + linkColor.g + "," + linkColor.b + "," +
change
.light / 100 + ")";
lightPillerLayerConText.shadowOffsetX = 0;
lightPillerLayerConText.shadowOffsetY = 10;
lightPillerLayerConText.shadowBlur = 50;
lightPillerLayerConText.beginPath();
lightPillerLayerConText.fillStyle = grd;
//绘制矩形
// lightPillerLayerConText.fillRect(coord[0] - 8, coord[1] + 9, 16, -80);
lightPillerLayerConText.fillRect(coord[0] - 8, coord[1], 16, -80);
lightPillerLayerConText.closePath();
lightPillerLayerConText.beginPath();
lightPillerLayerConText.fillStyle = "rgba(" + linkColor.r + "," + linkColor.g + "," + linkColor.b + ",1)";
lightPillerLayerConText.strokeStyle = "rgba(" + linkColor.r + "," + linkColor.g + "," + linkColor.b + ",0.6)";
// BezierEllipse1(lightPillerLayerConText, coord[0], coord[1] + 9, 8, 2);
BezierEllipse1(lightPillerLayerConText, coord[0], coord[1], 8, 2);
lightPillerLayerConText.fill();
lightPillerLayerConText.closePath()
lightPillerLayerConText.beginPath();
lightPillerLayerConText.lineWidth = 1;
lightPillerLayerConText.strokeStyle = "rgba(" + linkColor.r + "," + linkColor.g + "," + linkColor.b + ",0.4)";
// BezierEllipse1(lightPillerLayerConText, coord[0], coord[1] + 9, change.abeam, change.portrait);
BezierEllipse1(lightPillerLayerConText, coord[0], coord[1], change.abeam, change.portrait);
lightPillerLayerConText.closePath();
})
}
function animation() {
move(); //更新
lightPillerLayerConText.clearRect(0, 0, lightPillerLayer.width, lightPillerLayer.height); //清除
draw(); //绘制
requestAnimationFrame(animation); //循环
}
animation()
// setInterval(animation, 200)
function BezierEllipse1(context, x, y, a, b) {
//关键是bezierCurveTo中两个控制点的设置
//0.5和0.6是两个关键系数(在本函数中为试验而得)
var ox = 0.5 * a,
oy = 0.6 * b;
context.save();
context.translate(x, y);
context.beginPath();
//从椭圆纵轴下端开始逆时针方向绘制
context.moveTo(0, b);
context.bezierCurveTo(ox, b, a, oy, a, 0);
context.bezierCurveTo(a, -oy, ox, -b, 0, -b);
context.bezierCurveTo(-ox, -b, -a, -oy, -a, 0);
context.bezierCurveTo(-a, oy, -ox, b, 0, b);
context.closePath();
context.stroke();
context.restore();
};
</script>
本文介绍了如何在Echarts地图上添加Canvas动画,以实现地图特定位置的光柱闪烁效果。通过在Echarts地图上方添加两个Canvas图层,并设置pointer-events: none,确保Canvas不影响地图交互。使用Echarts API将经纬度转换为像素坐标,在Canvas上绘制光柱。光柱的闪烁效果通过改变渐变填充颜色的透明度实现。同时,文章还分享了在绘制阴影部分遇到的挑战和解决方案。
263





