MapLibre GL JS第70课:创建悬停效果

📌 学习目标

  • 掌握创建悬停效果的实现方法
  • 理解相关API的使用
  • 能够独立完成类似功能开发

MapLibre GL JS 从入门到精通 - 130+实战案例

🎯 核心概念

创建悬停效果以突出显示要素。

💻 完 整 代 码

代码示例

const map = new maplibregl.Map({
    container: 'map',
    style: 'https://demotiles.maplibre.org/style.json',
    center: [-100.486052, 37.830348],
    zoom: 2
});
let hoveredStateId = null;

map.on('load', () => {
    map.addSource('states', {
        'type': 'geojson',
        'data':
            'https://maplibre.org/maplibre-gl-js/docs/assets/us_states.geojson'
    });

    // 依赖要素状态的填充透明度表达式将在悬停状态设置为true时渲染悬停效果。
    map.addLayer({
        'id': 'state-fills',
        'type': 'fill',
        'source': 'states',
        'layout': {},
        'paint': {
            'fill-color': '#627BC1',
            'fill-opacity': [
                'case',
                ['boolean', ['feature-state', 'hover'], false],
                1,
                0.5
            ]
        }
    });

    map.addLayer({
        'id': 'state-borders',
        'type': 'line',
        'source': 'states',
        'layout': {},
        'paint': {
            'line-color': '#627BC1',
            'line-width': 2
        }
    });

    // 当用户将鼠标移到state-fill图层上方时,我们将更新
    // 鼠标下方要素的要素状态。
    map.on('mousemove', 'state-fills', (e) => {
        if (e.features.length > 0) {
            if (hoveredStateId) {
                map.setFeatureState(
                    {source: 'states', id: hoveredStateId},
                    {hover: false}
                );
            }
            hoveredStateId = e.features[0].id;
            map.setFeatureState(
                {source: 'states', id: hoveredStateId},
                {hover: true}
            );
        }
    });

    // 当鼠标离开state-fill图层时,更新之前悬停要素的要素状态。
    map.on('mouseleave', 'state-fills', () => {
        if (hoveredStateId) {
            map.setFeatureState(
                {source: 'states', id: hoveredStateId},
                {hover: false}
            );
        }
        hoveredStateId = null;
    });
});

代码示例

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Create a hover effect</title>
    <meta property="og:description" content="使用事件和要素状态创建每个要素的悬停效果。" />
    <meta property="og:created" content="2006-06-25" />
    <meta charset='utf-8'>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel='stylesheet' href='https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css' />
    <script src='https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js'></script>
    <style>
        body { margin: 0; padding: 0; }
        html, body, #map { height: 100%; }
    </style>
</head>
<body>
<div id="map"></div>
<script>
    const map = new maplibregl.Map({
        container: 'map',
        style: 'https://demotiles.maplibre.org/style.json',
        center: [-100.486052, 37.830348],
        zoom: 2
    });
    let hoveredStateId = null;

    map.on('load', () => {
        map.addSource('states', {
            'type': 'geojson',
            'data':
                'https://maplibre.org/maplibre-gl-js/docs/assets/us_states.geojson'
        });

        // 依赖要素状态的fill-opacity表达式将在要素的
        // 悬停状态设置为true时呈现悬停效果。
        map.addLayer({
            'id': 'state-fills',
            'type': 'fill',
            'source': 'states',
            'layout': {},
            'paint': {
                'fill-color': '#627BC1',
                'fill-opacity': [
                    'case',
                    ['boolean', ['feature-state', 'hover'], false],
                    1,
                    0.5
                ]
            }
        });

        map.addLayer({
            'id': 'state-borders',
            'type': 'line',
            'source': 'states',
            'layout': {},
            'paint': {
                'line-color': '#627BC1',
                'line-width': 2
            }
        });

        // 当用户将鼠标移动到state-fill图层上时,我们将更新
        // 鼠标下要素的要素状态。
        map.on('mousemove', 'state-fills', (e) => {
            if (e.features.length > 0) {
                if (hoveredStateId) {
                    map.setFeatureState(
                        {source: 'states', id: hoveredStateId},
                        {hover: false}
                    );
                }
                hoveredStateId = e.features[0].id;
                map.setFeatureState(
                    {source: 'states', id: hoveredStateId},
                    {hover: true}
                );
            }
        });

        // 当鼠标离开state-fill图层时,更新之前悬停要素的要素状态。
        map.on('mouseleave', 'state-fills', () => {
            if (hoveredStateId) {
                map.setFeatureState(
                    {source: 'states', id: hoveredStateId},
                    {hover: false}
                );
            }
            hoveredStateId = null;
        });
    });
</script>
</body>
</html>

🔍 代码解析

1. 初始化地图

使用 new maplibregl.Map() 创建地图实例,配置美国区域作为初始视图。

2. 关键配置项

  • map.addSource(): 添加GeoJSON数据源
  • feature-state: 使用要素状态控制样式
  • map.setFeatureState(): 设置要素状态
  • hover状态: 通过feature-state实现悬停效果

3. 核心机制

使用要素状态(feature-state)实现悬停效果:

  • 通过paint属性中的表达式根据状态设置透明度
  • 鼠标进入时设置hover:true,透明度变为1
  • 鼠标离开时设置hover:false,透明度变为0.5

⚙️ 参数说明

参数类型必填说明
sourcestring数据源ID
idnumber/string要素ID
stateobject要设置的键值对状态

🎨 效果说明

在这里插入图片描述

运行代码后,地图显示美国各州区域。鼠标悬停在某个州上时,该州会高亮显示(透明度从0.5变为1),移开后恢复原状。通过这种方式可以清晰区分当前悬停的州。

💡 常 见 问 题

Q1: 悬停效果不生效?
A: 检查以下几点:

  1. 确认数据源包含id字段(要素需要唯一ID)
  2. 检查paint表达式中是否正确使用了feature-state
  3. 确认hoveredStateId变量正确更新

Q2: 为什么使用feature-state而不是直接修改样式?
A: feature-state是MapLibre优化的状态管理机制,可以高效更新单个要素样式而不触发整个图层的重绘。

Q3: 如何添加更丰富的悬停效果?
A: 可以同时修改多个paint属性:

'fill-color': [
    'case',
    ['boolean', ['feature-state', 'hover'], false],
    '#ff0000',  // 高亮色
    '#627BC1'   // 默认色
],
'fill-opacity': [
    'case',
    ['boolean', ['feature-state', 'hover'], false],
    1,
    0.5
]

📝 练习任务

  1. 基础练习:修改悬停时的颜色和高亮效果
  2. 进阶挑战:添加边框高亮效果
  3. 拓展思考:如何实现点击选中效果?
  4. 综合实践:创建一个可交互的州信息展示系统

🌟 最佳实践

  1. 状态追踪: 使用变量追踪当前悬停的要素ID
  2. 性能优化: 使用feature-state避免频繁重绘
  3. 清理状态: 鼠标离开时重置状态,避免状态残留
  4. 视觉效果: 透明度变化是最简单的悬停反馈
  5. 组合效果: 结合颜色、边框、阴影等多重效果

🔗 延伸阅读


本文是MapLibre GL JS实践课程系列的一部分,欢迎关注收藏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丷丩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值