快过年了,我想爸爸妈妈了,我想制作一个中国地图,标出我与妈妈的距离,然后使用飞线链接两个点,地图使用Echarts的地图组件,然后再加上一段话,表达我对爸爸妈妈的思念,结果输出为一个html页面
可定制双城距离思念交互地图
用户想在过年时制作一个表达思念父母的HTML页面,需求包括:
- 使用中国地图(ECharts地图组件)
- 标记两个点(用户位置和妈妈位置)
- 使用飞线连接两个点
- 添加一段表达思念的文字
- 输出为单个HTML文件
这是一个情感化的数据可视化项目,需要:
- 使用ECharts(特别是map组件和lines系列用于飞线)
- 需要中国地图的GeoJSON数据或直接使用ECharts内置地图
- 需要模拟两个城市的位置(用户所在地和妈妈所在地)
- 需要设计温馨的UI风格(春节/思念主题)
注意:ECharts自5.0版本后不再内置地图数据,需要引入外部GeoJSON或使用注册好的地图。我可以使用CDN引入中国地图数据。
让我构建一个温馨的单页面应用:
- 深色背景(夜空感),配合金色/红色(春节氛围)
- 中央是ECharts中国地图
- 两个闪烁的标记点(家和我的位置)
- 流动的飞线连接
- 底部或侧边的思念文字
- 添加一些粒子效果或飘落效果(雪花或灯笼)增强氛围
由于用户没有提供具体城市,我需要:
- 让用户可以选择或输入两个城市
- 或者预设两个代表性城市(比如北京和广州,一北一南,距离感明显)
- 或者随机生成但可编辑
我觉得最好的方式是预设两个城市(比如"我"在上海,"妈妈"在成都),但提供输入框让用户修改,这样即开即用又能个性化。
技术实现:
- 使用ECharts 5.x
- 需要中国地图GeoJSON,可以用阿里DataV的地图服务
- 使用effectScatter做闪烁点
- 使用lines做飞线动画
- 添加CSS动画做氛围效果
情感文案需要温馨感人,突出距离和思念,以及春节团圆的期盼。
界面布局:
- 全屏地图
- 左上角标题
- 右下角或底部中央文案卡片
- 简单的控制面板让用户修改城市(可选,为了美观可以做成悬浮输入框)
让我开始编写代码...
我来为你制作一个充满温情的春节思念地图。这个页面将使用 ECharts 展示中国地图,用流动的飞线连接你和妈妈所在的城市,并配上温暖的思念文字。
HTML
Preview
Copy
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>思念的距离 - 献给爸爸妈妈</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #1e3c72 100%);
overflow: hidden;
position: relative;
height: 100vh;
}
/* 飘落的花瓣/雪花效果 */
.petal {
position: absolute;
background: rgba(255, 215, 0, 0.6);
border-radius: 50% 0 50% 50%;
animation: fall linear infinite;
pointer-events: none;
}
@keyframes fall {
to {
transform: translateY(100vh) rotate(360deg);
}
}
/* 地图容器 */
#map-container {
width: 100%;
height: 100vh;
position: relative;
z-index: 1;
}
/* 标题 */
.header {
position: absolute;
top: 30px;
left: 50%;
transform: translateX(-50%);
text-align: center;
z-index: 10;
color: #ffd700;
text-shadow: 0 2px 10px rgba(0,0,0,0.5);
}
.header h1 {
font-size: 2.5em;
font-weight: 300;
letter-spacing: 0.2em;
margin-bottom: 10px;
background: linear-gradient(to right, #ffd700, #ffed4e, #ffd700);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-size: 200% auto;
animation: shine 3s linear infinite;
}
@keyframes shine {
to {
background-position: 200% center;
}
}
.header .subtitle {
font-size: 1.1em;
color: rgba(255, 255, 255, 0.8);
font-weight: 300;
}
/* 思念文字卡片 */
.letter-card {
position: absolute;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
width: 90%;
max-width: 600px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
padding: 30px;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3),
inset 0 0 0 1px rgba(255,255,255,0.5);
z-index: 10;
border-left: 5px solid #e74c3c;
animation: float 6s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateX(-50%) translateY(0px); }
50% { transform: translateX(-50%) translateY(-10px); }
}
.letter-card::before {
content: '❝';
position: absolute;
top: -20px;
left: 20px;
font-size: 60px;
color: #e74c3c;
opacity: 0.3;
font-family: serif;
}
.letter-content {
color: #2c3e50;
line-height: 1.8;
font-size: 1.1em;
text-align: justify;
position: relative;
z-index: 1;
}
.letter-content .highlight {
color: #e74c3c;
font-weight: bold;
}
.signature {
text-align: right;
margin-top: 15px;
color: #7f8c8d;
font-style: italic;
}
/* 距离显示 */
.distance-badge {
position: absolute;
top: 120px;
right: 40px;
background: rgba(231, 76, 60, 0.9);
color: white;
padding: 15px 25px;
border-radius: 30px;
font-size: 1.2em;
font-weight: bold;
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.4);
z-index: 10;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
/* 城市选择器 */
.city-selector {
position: absolute;
top: 120px;
left: 40px;
background: rgba(255, 255, 255, 0.95);
padding: 20px;
border-radius: 15px;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
z-index: 10;
width: 200px;
}
.city-selector h3 {
color: #2c3e50;
margin-bottom: 15px;
font-size: 1em;
border-bottom: 2px solid #e74c3c;
padding-bottom: 5px;
}
.city-selector select {
width: 100%;
padding: 8px;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-family: inherit;
outline: none;
transition: border-color 0.3s;
}
.city-selector select:focus {
border-color: #e74c3c;
}
.city-selector label {
display: block;
margin-bottom: 5px;
color: #7f8c8d;
font-size: 0.9em;
}
/* 响应式 */
@media (max-width: 768px) {
.header h1 { font-size: 1.8em; }
.letter-card {
bottom: 20px;
padding: 20px;
font-size: 0.9em;
}
.city-selector {
top: auto;
bottom: 200px;
left: 20px;
width: calc(100% - 40px);
max-width: 200px;
}
.distance-badge {
top: 100px;
right: 20px;
font-size: 1em;
padding: 10px 15px;
}
}
</style>
</head>
<body>
<!-- 飘落效果容器 -->
<div id="petals-container"></div>
<!-- 标题 -->
<div class="header">
<h1>思念的距离</h1>
<div class="subtitle">无论多远,心永远在一起</div>
</div>
<!-- 距离显示 -->
<div class="distance-badge" id="distance">距离:约 <span>0</span> 公里</div>
<!-- 城市选择 -->
<div class="city-selector">
<h3>📍 设置位置</h3>
<label>我的位置:</label>
<select id="myCity">
<option value=" Beijing|116.4074|39.9042">北京</option>
<option value="Shanghai|121.4737|31.2304" selected>上海</option>
<option value="Guangzhou|113.2644|23.1291">广州</option>
<option value="Shenzhen|114.0579|22.5431">深圳</option>
<option value="Chengdu|104.0668|30.5728">成都</option>
<option value="Hangzhou|120.1551|30.2741">杭州</option>
<option value="Wuhan|114.3054|30.5931">武汉</option>
<option value="Xi'an|108.9398|34.3416">西安</option>
</select>
<label>妈妈的位置:</label>
<select id="momCity">
<option value="Beijing|116.4074|39.9042">北京</option>
<option value="Shanghai|121.4737|31.2304">上海</option>
<option value="Guangzhou|113.2644|23.1291">广州</option>
<option value="Shenzhen|114.0579|22.5431">深圳</option>
<option value="Chengdu|104.0668|30.5728" selected>成都</option>
<option value="Hangzhou|120.1551|30.2741">杭州</option>
<option value="Wuhan|114.3054|30.5931">武汉</option>
<option value="Xi'an|108.9398|34.3416">西安</option>
</select>
</div>
<!-- 地图 -->
<div id="map-container"></div>
<!-- 思念信件 -->
<div class="letter-card">
<div class="letter-content">
亲爱的爸爸妈妈,<br><br>
又是一年春节将至,地图上的这条飞线,连接着<span class="highlight">我所在的城市</span>与<span class="highlight">家乡的方向</span>。
虽然相隔千里,但每一缕思念都顺着这条线飞向你们。<br><br>
那些曾经的唠叨,如今成了最温暖的声音;那些日常的饭菜香,成了最怀念的味道。
距离丈量的是空间,却量不尽我对你们的牵挂。<br><br>
愿这条穿越山河的线,带去我的拥抱。等我回家,我们一起吃顿团圆饭。
</div>
<div class="signature">—— 想你们的孩子</div>
</div>
<script>
// 创建飘落效果
function createPetals() {
const container = document.getElementById('petals-container');
const petalCount = 30;
for (let i = 0; i < petalCount; i++) {
const petal = document.createElement('div');
petal.className = 'petal';
petal.style.left = Math.random() * 100 + '%';
petal.style.width = (Math.random() * 10 + 5) + 'px';
petal.style.height = (Math.random() * 10 + 5) + 'px';
petal.style.animationDuration = (Math.random() * 10 + 10) + 's';
petal.style.animationDelay = Math.random() * 10 + 's';
petal.style.opacity = Math.random() * 0.5 + 0.3;
container.appendChild(petal);
}
}
createPetals();
// 计算距离(简化的球面距离)
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371;
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return Math.round(R * c);
}
// 初始化地图
let myChart = null;
async function initMap() {
myChart = echarts.init(document.getElementById('map-container'));
// 加载中国地图
try {
const response = await fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json');
const chinaJson = await response.json();
echarts.registerMap('china', chinaJson);
updateMap();
} catch (error) {
console.error('地图加载失败:', error);
document.getElementById('map-container').innerHTML =
'<div style="color:white; text-align:center; padding-top:20%;">地图加载中,请稍候...</div>';
}
}
function updateMap() {
const mySelect = document.getElementById('myCity').value.split('|');
const momSelect = document.getElementById('momCity').value.split('|');
const myCity = {
name: mySelect[0],
value: [parseFloat(mySelect[1]), parseFloat(mySelect[2])]
};
const momCity = {
name: momSelect[0],
value: [parseFloat(momSelect[1]), parseFloat(momSelect[2])]
};
// 更新距离显示
const dist = calculateDistance(myCity.value[1], myCity.value[0], momCity.value[1], momCity.value[0]);
document.querySelector('#distance span').textContent = dist;
const option = {
backgroundColor: 'transparent',
geo: {
map: 'china',
roam: true,
zoom: 1.2,
center: [105, 36],
label: {
emphasis: {
show: true,
color: '#fff'
}
},
itemStyle: {
normal: {
areaColor: 'rgba(255,255,255,0.1)',
borderColor: '#ffd700',
borderWidth: 1,
shadowColor: 'rgba(0, 0, 0, 0.5)',
shadowBlur: 10
},
emphasis: {
areaColor: 'rgba(255,215,0,0.3)'
}
}
},
series: [
// 我的位置
{
name: '我的位置',
type: 'effectScatter',
coordinateSystem: 'geo',
data: [myCity],
symbolSize: 20,
showEffectOn: 'render',
rippleEffect: {
brushType: 'stroke',
scale: 4,
period: 4
},
label: {
show: true,
position: 'top',
formatter: '{b} (我)',
color: '#fff',
fontSize: 12,
fontWeight: 'bold',
backgroundColor: 'rgba(0,0,0,0.5)',
padding: [5, 10],
borderRadius: 4
},
itemStyle: {
color: '#3498db',
shadowBlur: 10,
shadowColor: '#3498db'
},
zlevel: 2
},
// 妈妈的位置
{
name: '妈妈的位置',
type: 'effectScatter',
coordinateSystem: 'geo',
data: [momCity],
symbolSize: 20,
showEffectOn: 'render',
rippleEffect: {
brushType: 'stroke',
scale: 4,
period: 4
},
label: {
show: true,
position: 'top',
formatter: '{b} (妈妈)',
color: '#fff',
fontSize: 12,
fontWeight: 'bold',
backgroundColor: 'rgba(231, 76, 60, 0.8)',
padding: [5, 10],
borderRadius: 4
},
itemStyle: {
color: '#e74c3c',
shadowBlur: 10,
shadowColor: '#e74c3c'
},
zlevel: 2
},
// 飞线
{
name: '思念的线',
type: 'lines',
zlevel: 1,
effect: {
show: true,
period: 6,
trailLength: 0.7,
color: '#ffd700',
symbolSize: 3,
constantSpeed: 100
},
lineStyle: {
normal: {
color: '#ffd700',
width: 2,
curveness: 0.3,
opacity: 0.8,
type: 'solid'
}
},
data: [{
coords: [myCity.value, momCity.value],
lineStyle: {
color: {
type: 'linear',
x: 0, y: 0, x2: 1, y2: 0,
colorStops: [
{offset: 0, color: '#3498db'},
{offset: 1, color: '#e74c3c'}
]
}
}
}]
}
]
};
myChart.setOption(option);
}
// 监听城市选择变化
document.getElementById('myCity').addEventListener('change', updateMap);
document.getElementById('momCity').addEventListener('change', updateMap);
// 窗口大小改变时重绘
window.addEventListener('resize', () => {
if (myChart) myChart.resize();
});
// 初始化
initMap();
</script>
</body>
</html>这个 HTML 页面包含以下温馨功能:
🗺️ 地图功能
- 使用 ECharts 渲染中国地图,采用深蓝色夜空背景配合金色边框
- 两个闪烁的动态标记点:蓝色代表"我",红色代表"妈妈"
- 金色流动飞线连接两点,象征着思念的传递
✨ 视觉效果
- 飘落的花瓣/灯笼动画,营造春节氛围
- 距离实时计算显示(右上角红色徽章)
- 悬浮的思念信笺卡片,带有微微的浮动动画
📝 情感表达
- 内置了一段温馨的思念文字,表达了游子在春节对父母的牵挂
- 文字卡片采用书信样式,带有引号和签名
🎛️ 交互功能
- 左上角提供城市选择器,可以修改"我"和"妈妈"所在的城市
- 选择后地图会自动更新飞线轨迹和距离数据
- 支持地图缩放和拖动查看
你可以直接保存这个代码为
.html 文件打开使用。如果需要修改文字内容,可以在代码中找到 letter-content 类的 div 进行编辑。 文字把地图挡住了,放到右侧吧!
将信件卡片移至右侧并调整响应式布局
用户说文字把地图挡住了,想要把文字放到右侧。我需要查看之前的代码,发现信件卡片(letter-card)是放在底部的,确实可能会挡住地图。
我需要做以下修改:
- 将信件卡片从底部移到右侧
- 调整响应式布局,确保在移动设备上也能正常显示
- 可能需要调整地图的中心位置或缩放,以确保主要地图区域不被遮挡
- 保持整体美观和春节氛围
修改策略:
- 将
.letter-card的bottom: 40px; left: 50%; transform: translateX(-50%);改为右侧定位,比如right: 40px; top: 50%; transform: translateY(-50%); - 调整宽度和高度,使其适合竖向放在右侧
- 调整
max-width和添加max-height防止超出屏幕 - 修改浮动动画,从上下浮动改为左右浮动(或者保持上下但调整位置)
- 调整响应式布局,在小屏幕上可能改为底部或左上角,避免遮挡
同时检查其他元素的位置:
- 标题在顶部中央
- 距离徽章在右上角(可能需要移到左上角避免与信件重叠,或者保持右上角但调整信件位置)
- 城市选择器在左上角
更好的布局方案:
- 右侧放信件卡片,竖向排列
- 左上角放城市选择器
- 右上角放距离显示
- 标题保持在顶部中央
让我修改代码。
我来调整布局,将思念文字移到右侧,让地图完整展现:
HTML
Preview
Copy
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>思念的距离 - 献给爸爸妈妈</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #1e3c72 100%);
overflow: hidden;
position: relative;
height: 100vh;
}
/* 飘落的花瓣/雪花效果 */
.petal {
position: absolute;
background: rgba(255, 215, 0, 0.6);
border-radius: 50% 0 50% 50%;
animation: fall linear infinite;
pointer-events: none;
}
@keyframes fall {
to {
transform: translateY(100vh) rotate(360deg);
}
}
/* 地图容器 */
#map-container {
width: 100%;
height: 100vh;
position: relative;
z-index: 1;
}
/* 标题 */
.header {
position: absolute;
top: 30px;
left: 50%;
transform: translateX(-50%);
text-align: center;
z-index: 10;
color: #ffd700;
text-shadow: 0 2px 10px rgba(0,0,0,0.5);
pointer-events: none;
}
.header h1 {
font-size: 2.5em;
font-weight: 300;
letter-spacing: 0.2em;
margin-bottom: 10px;
background: linear-gradient(to right, #ffd700, #ffed4e, #ffd700);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-size: 200% auto;
animation: shine 3s linear infinite;
}
@keyframes shine {
to {
background-position: 200% center;
}
}
.header .subtitle {
font-size: 1.1em;
color: rgba(255, 255, 255, 0.8);
font-weight: 300;
}
/* 思念文字卡片 - 移到右侧 */
.letter-card {
position: absolute;
right: 40px;
top: 50%;
transform: translateY(-50%);
width: 320px;
max-height: 70vh;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
padding: 30px;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3),
inset 0 0 0 1px rgba(255,255,255,0.5);
z-index: 10;
border-left: 5px solid #e74c3c;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: #e74c3c #f0f0f0;
}
/* 自定义滚动条 */
.letter-card::-webkit-scrollbar {
width: 6px;
}
.letter-card::-webkit-scrollbar-track {
background: #f0f0f0;
border-radius: 3px;
}
.letter-card::-webkit-scrollbar-thumb {
background: #e74c3c;
border-radius: 3px;
}
.letter-card::before {
content: '❝';
position: absolute;
top: 10px;
left: 20px;
font-size: 50px;
color: #e74c3c;
opacity: 0.2;
font-family: serif;
}
.letter-content {
color: #2c3e50;
line-height: 1.8;
font-size: 1em;
text-align: justify;
position: relative;
z-index: 1;
margin-top: 20px;
}
.letter-content .highlight {
color: #e74c3c;
font-weight: bold;
}
.signature {
text-align: right;
margin-top: 20px;
color: #7f8c8d;
font-style: italic;
font-size: 0.9em;
}
/* 距离显示 - 移到左上角 */
.distance-badge {
position: absolute;
top: 120px;
left: 40px;
background: linear-gradient(135deg, #e74c3c, #c0392b);
color: white;
padding: 15px 25px;
border-radius: 30px;
font-size: 1.2em;
font-weight: bold;
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.4);
z-index: 10;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
/* 城市选择器 - 保持左上但在距离下方 */
.city-selector {
position: absolute;
top: 200px;
left: 40px;
background: rgba(255, 255, 255, 0.95);
padding: 20px;
border-radius: 15px;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
z-index: 10;
width: 200px;
}
.city-selector h3 {
color: #2c3e50;
margin-bottom: 15px;
font-size: 1em;
border-bottom: 2px solid #e74c3c;
padding-bottom: 5px;
}
.city-selector select {
width: 100%;
padding: 10px;
margin-bottom: 12px;
border: 1px solid #ddd;
border-radius: 8px;
font-family: inherit;
outline: none;
transition: all 0.3s;
cursor: pointer;
}
.city-selector select:hover {
border-color: #e74c3c;
}
.city-selector select:focus {
border-color: #e74c3c;
box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.1);
}
.city-selector label {
display: block;
margin-bottom: 6px;
color: #555;
font-size: 0.9em;
font-weight: 500;
}
/* 响应式调整 */
@media (max-width: 968px) {
.letter-card {
right: 20px;
width: 280px;
padding: 20px;
}
}
@media (max-width: 768px) {
.header h1 { font-size: 1.8em; }
.header { top: 20px; }
/* 手机上改为底部横向滚动或折叠 */
.letter-card {
right: auto;
left: 50%;
top: auto;
bottom: 20px;
transform: translateX(-50%);
width: 90%;
max-height: 25vh;
max-width: 500px;
}
.city-selector {
top: 100px;
left: 20px;
width: calc(50% - 30px);
min-width: 150px;
padding: 15px;
}
.distance-badge {
top: 100px;
right: 20px;
left: auto;
font-size: 1em;
padding: 10px 15px;
}
}
@media (max-width: 480px) {
.city-selector {
width: calc(100% - 40px);
top: 100px;
}
.distance-badge {
top: 280px;
right: auto;
left: 20px;
}
}
</style>
</head>
<body>
<!-- 飘落的效果容器 -->
<div id="petals-container"></div>
<!-- 标题 -->
<div class="header">
<h1>思念的距离</h1>
<div class="subtitle">无论多远,心永远在一起</div>
</div>
<!-- 距离显示 - 左上 -->
<div class="distance-badge" id="distance">距离:约 <span>0</span> 公里</div>
<!-- 城市选择 - 左中 -->
<div class="city-selector">
<h3>📍 设置位置</h3>
<label>我的位置:</label>
<select id="myCity">
<option value="Beijing|116.4074|39.9042">北京</option>
<option value="Shanghai|121.4737|31.2304" selected>上海</option>
<option value="Guangzhou|113.2644|23.1291">广州</option>
<option value="Shenzhen|114.0579|22.5431">深圳</option>
<option value="Chengdu|104.0668|30.5728">成都</option>
<option value="Hangzhou|120.1551|30.2741">杭州</option>
<option value="Wuhan|114.3054|30.5931">武汉</option>
<option value="Xi'an|108.9398|34.3416">西安</option>
<option value="Chongqing|106.5516|29.5630">重庆</option>
<option value="Nanjing|118.7969|32.0603">南京</option>
</select>
<label>妈妈的位置:</label>
<select id="momCity">
<option value="Beijing|116.4074|39.9042">北京</option>
<option value="Shanghai|121.4737|31.2304">上海</option>
<option value="Guangzhou|113.2644|23.1291">广州</option>
<option value="Shenzhen|114.0579|22.5431">深圳</option>
<option value="Chengdu|104.0668|30.5728" selected>成都</option>
<option value="Hangzhou|120.1551|30.2741">杭州</option>
<option value="Wuhan|114.3054|30.5931">武汉</option>
<option value="Xi'an|108.9398|34.3416">西安</option>
<option value="Chongqing|106.5516|29.5630">重庆</option>
<option value="Harbin|126.5340|45.8038">哈尔滨</option>
</select>
</div>
<!-- 地图 -->
<div id="map-container"></div>
<!-- 思念信件 - 右侧垂直居中 -->
<div class="letter-card">
<div class="letter-content">
亲爱的爸爸妈妈:<br><br>
又是一年春节将至,地图上的这条金色飞线,连接着<span class="highlight">我所在的城市</span>与<span class="highlight">家乡的方向</span>。
虽然相隔千里,但每一缕思念都顺着这条线飞向你们。<br><br>
那些曾经的唠叨,如今成了最温暖的声音;那些日常的饭菜香,成了最怀念的味道。
距离丈量的是空间,却量不尽我对你们的牵挂。<br><br>
愿这条穿越山河的线,带去我的拥抱。等我回家,我们一起吃顿团圆饭。🧧
</div>
<div class="signature">—— 想你们的孩子<br>敬上</div>
</div>
<script>
// 创建飘落效果
function createPetals() {
const container = document.getElementById('petals-container');
const petalCount = 25;
for (let i = 0; i < petalCount; i++) {
const petal = document.createElement('div');
petal.className = 'petal';
petal.style.left = Math.random() * 100 + '%';
petal.style.width = (Math.random() * 8 + 4) + 'px';
petal.style.height = (Math.random() * 8 + 4) + 'px';
petal.style.animationDuration = (Math.random() * 8 + 8) + 's';
petal.style.animationDelay = Math.random() * 8 + 's';
petal.style.opacity = Math.random() * 0.5 + 0.2;
container.appendChild(petal);
}
}
createPetals();
// 计算距离
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371;
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return Math.round(R * c);
}
// 初始化地图
let myChart = null;
async function initMap() {
myChart = echarts.init(document.getElementById('map-container'));
try {
const response = await fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json');
const chinaJson = await response.json();
echarts.registerMap('china', chinaJson);
updateMap();
} catch (error) {
console.error('地图加载失败:', error);
document.getElementById('map-container').innerHTML =
'<div style="color:white; text-align:center; padding-top:20%; font-size:1.2em;">地图加载中,请检查网络连接...</div>';
}
}
function updateMap() {
const mySelect = document.getElementById('myCity').value.split('|');
const momSelect = document.getElementById('momCity').value.split('|');
const myCity = {
name: mySelect[0],
value: [parseFloat(mySelect[1]), parseFloat(mySelect[2])]
};
const momCity = {
name: momSelect[0],
value: [parseFloat(momSelect[1]), parseFloat(momSelect[2])]
};
const dist = calculateDistance(myCity.value[1], myCity.value[0], momCity.value[1], momCity.value[0]);
document.querySelector('#distance span').textContent = dist.toLocaleString();
// 根据距离调整地图中心点
const centerLng = (myCity.value[0] + momCity.value[0]) / 2;
const centerLat = (myCity.value[1] + momCity.value[1]) / 2;
const option = {
backgroundColor: 'transparent',
geo: {
map: 'china',
roam: true,
zoom: 1.1,
center: [centerLng, centerLat + 5], // 稍微偏移以平衡右侧文字
label: {
emphasis: {
show: true,
color: '#fff'
}
},
itemStyle: {
normal: {
areaColor: 'rgba(255,255,255,0.08)',
borderColor: '#ffd700',
borderWidth: 1.5,
shadowColor: 'rgba(0, 0, 0, 0.5)',
shadowBlur: 10
},
emphasis: {
areaColor: 'rgba(255,215,0,0.2)'
}
}
},
series: [
{
name: '我的位置',
type: 'effectScatter',
coordinateSystem: 'geo',
data: [myCity],
symbolSize: 25,
showEffectOn: 'render',
rippleEffect: {
brushType: 'stroke',
scale: 4,
period: 4
},
label: {
show: true,
position: 'top',
formatter: '{b}\n(我在这儿)',
color: '#fff',
fontSize: 12,
fontWeight: 'bold',
backgroundColor: 'rgba(52, 152, 219, 0.8)',
padding: [5, 10],
borderRadius: 4,
distance: 10
},
itemStyle: {
color: '#3498db',
shadowBlur: 20,
shadowColor: '#3498db'
},
zlevel: 2
},
{
name: '妈妈的位置',
type: 'effectScatter',
coordinateSystem: 'geo',
data: [momCity],
symbolSize: 25,
showEffectOn: 'render',
rippleEffect: {
brushType: 'stroke',
scale: 4,
period: 4
},
label: {
show: true,
position: 'top',
formatter: '{b}\n(妈妈在这儿)',
color: '#fff',
fontSize: 12,
fontWeight: 'bold',
backgroundColor: 'rgba(231, 76, 60, 0.9)',
padding: [5, 10],
borderRadius: 4,
distance: 10
},
itemStyle: {
color: '#e74c3c',
shadowBlur: 20,
shadowColor: '#e74c3c'
},
zlevel: 2
},
{
name: '思念的线',
type: 'lines',
zlevel: 1,
effect: {
show: true,
period: 5,
trailLength: 0.8,
color: '#ffd700',
symbolSize: 4,
constantSpeed: 120
},
lineStyle: {
normal: {
width: 3,
curveness: 0.2,
opacity: 0.9,
type: 'solid'
}
},
data: [{
coords: [myCity.value, momCity.value],
lineStyle: {
color: {
type: 'linear',
x: 0, y: 0, x2: 1, y2: 0,
colorStops: [
{offset: 0, color: '#3498db'},
{offset: 0.5, color: '#ffd700'},
{offset: 1, color: '#e74c3c'}
]
}
}
}]
}
]
};
myChart.setOption(option);
}
document.getElementById('myCity').addEventListener('change', updateMap);
document.getElementById('momCity').addEventListener('change', updateMap);
window.addEventListener('resize', () => {
if (myChart) myChart.resize();
});
initMap();
</script>
</body>
</html>主要调整:
- 📜 文字卡片移到右侧 - 垂直居中显示,不遮挡地图主体
- 📍 距离显示移到左上 - 与右侧文字形成视觉平衡
- 🗺️ 地图自适应 - 根据两座城市自动调整中心点,确保连线完整可见
- ✨ 优化了飞线效果 - 增加金色过渡,更醒目
- 📱 响应式改进 - 手机屏幕下文字自动回到底部,避免右侧空间不足
现在地图可以完整展示,右侧的书信卡片像一封竖着的情书,静静诉说着思念。