echarts地图上添加canvas动画

本文介绍了如何在Echarts地图上添加Canvas动画,以实现地图特定位置的光柱闪烁效果。通过在Echarts地图上方添加两个Canvas图层,并设置pointer-events: none,确保Canvas不影响地图交互。使用Echarts API将经纬度转换为像素坐标,在Canvas上绘制光柱。光柱的闪烁效果通过改变渐变填充颜色的透明度实现。同时,文章还分享了在绘制阴影部分遇到的挑战和解决方案。

最近做的一个项目需要做出地图上特定位置有光柱并且闪烁,点击地图选中区域变色若有光柱则切换光柱为坐标,坐标上下浮动,研究了一久最后决定使用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>

评论 9
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值