前几天看到别的博主折腾足迹地图自己也尝试过,但由于时间关系,半途而废了。今天打开博客后,看着自己的关于页面,想着再写点啥,结果一个字儿也没蹦出来,倒是觉得足迹地图放这里应该挺合适的。正好印象中有一个使用这个主题的博主,他的足迹就是放在关于页面的,于是赶紧再跑过去看看他是如何实现的,自己也照葫芦画瓢搞一个。
足迹的实现,采用交互式地图 JavaScript 库 leaflet.js。目前只形成了基本的足迹功能,后台文章增加了自定义框,可以输入经纬度信息。地图上显示填写了经纬度信息的文章。对主题自带的灯箱功能和开启 PJAX 进行了适配。同时优化使用体验,当鼠标点击地图时才能缩放地图,当点击地图外面页面时再次禁止缩放地图。下面记录下实现的主要代码。
1. 在 functions.php 文件添加以下代码以加载 Leaflet、MarkerCluster 以及自定义的 custom-map.js 脚本。同时,确保将地图数据本地化传递给 JavaScript。
// ... 足迹 ... // 加载 Leaflet、MarkerCluster、FontAwesome 和自定义地图脚本 function enqueue_leaflet_markercluster_assets() { // Leaflet CSS wp_enqueue_style( 'leaflet-css', 'https://unpkg.com/leaflet@1.9.3/dist/leaflet.css', array(), '1.9.3' ); // MarkerCluster CSS wp_enqueue_style( 'leaflet-markercluster-css', 'https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css', array('leaflet-css'), '1.5.3' ); wp_enqueue_style( 'leaflet-markercluster-default-css', 'https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css', array('leaflet-markercluster-css'), '1.5.3' ); // FontAwesome CSS wp_enqueue_style( 'fontawesome-css', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css', array(), '6.4.0' ); // Leaflet JS wp_enqueue_script( 'leaflet-js', 'https://unpkg.com/leaflet@1.9.3/dist/leaflet.js', array(), '1.9.3', true ); // MarkerCluster JS wp_enqueue_script( 'leaflet-markercluster-js', 'https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js', array('leaflet-js'), '1.5.3', true ); // 自定义地图脚本 wp_enqueue_script( 'custom-map-js', get_template_directory_uri() . '/js/custom-map.js', array('leaflet-js', 'leaflet-markercluster-js'), '1.0', true ); // 本地化地图数据 $posts = get_posts(array( 'post_type' => 'post', 'meta_query' => array( array( 'key' => '_latitude', 'compare' => 'EXISTS', ), array( 'key' => '_longitude', 'compare' => 'EXISTS', ), ), 'numberposts' => -1, )); $locations = array(); foreach ($posts as $post) { $lat = get_post_meta($post->ID, '_latitude', true); $lng = get_post_meta($post->ID, '_longitude', true); if ($lat && $lng) { $key = $lat . ',' . $lng; if (!isset($locations[$key])) { $locations[$key] = array( 'lat' => floatval($lat), 'lng' => floatval($lng), 'posts' => array(), ); } $locations[$key]['posts'][] = array( 'title' => get_the_title($post->ID), 'permalink' => get_permalink($post->ID), ); } } // 转换为索引数组 $locations = array_values($locations); wp_localize_script( 'custom-map-js', 'mapData', $locations ); } add_action( 'wp_enqueue_scripts', 'enqueue_leaflet_markercluster_assets' ); // 添加自定义元框以输入纬度和经度 function add_location_meta_boxes() { add_meta_box( 'location_meta_box', // ID '地理位置信息', // 标题 'render_location_meta_box', // 回调函数 'post', // 文章类型 'side', // 上下文 'default' // 优先级 ); } add_action('add_meta_boxes', 'add_location_meta_boxes'); // 渲染元框内容 function render_location_meta_box($post) { // 使用 nonce 验证 wp_nonce_field('save_location_meta', 'location_meta_nonce'); // 获取现有的经度和纬度值 $latitude = get_post_meta($post->ID, '_latitude', true); $longitude = get_post_meta($post->ID, '_longitude', true); echo '<label for="latitude">纬度 (Latitude):</label>'; echo '<input type="text" id="latitude" name="latitude" value="' . esc_attr($latitude) . '" style="width:100%;" />'; echo '<br/><br/>'; echo '<label for="longitude">经度 (Longitude):</label>'; echo '<input type="text" id="longitude" name="longitude" value="' . esc_attr($longitude) . '" style="width:100%;" />'; } // 保存自定义字段数据 function save_location_meta_box($post_id) { // 检查 nonce 是否存在 if (!isset($_POST['location_meta_nonce'])) { return; } // 验证 nonce if (!wp_verify_nonce($_POST['location_meta_nonce'], 'save_location_meta')) { return; } // 防止自动保存 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } // 检查用户权限 if (isset($_POST['post_type']) && 'post' == $_POST['post_type']) { if (!current_user_can('edit_post', $post_id)) { return; } } // 保存纬度 if (isset($_POST['latitude'])) { $latitude = sanitize_text_field($_POST['latitude']); if (is_numeric($latitude) && $latitude >= -90 && $latitude <= 90) { update_post_meta($post_id, '_latitude', $latitude); } else { // 删除无效的纬度 delete_post_meta($post_id, '_latitude'); } } // 保存经度 if (isset($_POST['longitude'])) { $longitude = sanitize_text_field($_POST['longitude']); if (is_numeric($longitude) && $longitude >= -180 && $longitude <= 180) { update_post_meta($post_id, '_longitude', $longitude); } else { // 删除无效的经度 delete_post_meta($post_id, '_longitude'); } } // 清除缓存 delete_transient('map_locations'); } add_action('save_post', 'save_location_meta_box');
2. 在主题目录下的 js 文件夹中创建 custom-map.js 文件,并添加以下代码:
// 定义一个自定义图标类,扩展 L.Icon 并在创建图标时添加 'no-view' 属性 const CustomIcon = L.Icon.extend({ createIcon: function(oldIcon) { const icon = L.Icon.prototype.createIcon.call(this, oldIcon); if (icon.tagName === 'IMG') { icon.setAttribute('no-view', ''); } else { console.warn('Icon is not an IMG element:', icon); } return icon; } }); // 覆盖 L.Marker 的 onAdd 方法,以确保每次标记被添加到地图时都添加 'no-view' 属性 L.Marker.prototype.onAdd = (function(originalOnAdd) { return function(map) { originalOnAdd.call(this, map); const icon = this.getElement(); if (icon && icon.tagName === 'IMG') { icon.setAttribute('no-view', ''); } }; })(L.Marker.prototype.onAdd); // 初始化地图函数 function initializeMap() { const mapContainer = document.getElementById('map'); if (!mapContainer) { return; } if (mapContainer._leaflet_map) { mapContainer._leaflet_map.remove(); } // 初始化地图,禁用滚轮缩放 const map = L.map('map', { scrollWheelZoom: false, }).setView([35.86166, 104.195397], 4); // 设置为中国的地理中心 mapContainer._leaflet_map = map; // 使用天地图瓦片服务。 L.tileLayer('https://t{s}.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=在此填写自己申请的key', { subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'], attribution: '© 天地图', }).addTo(map); // 添加天地图的标注服务 L.tileLayer('https://t{s}.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=在此填写自己申请的key', { subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'], attribution: '', }).addTo(map); if (typeof mapData !== 'undefined' && Array.isArray(mapData) && mapData.length > 0) { const markers = L.markerClusterGroup(); const customIcon = new CustomIcon({ iconUrl: 'https://unpkg.com/leaflet@1.9.3/dist/images/marker-icon.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], }); mapData.forEach(function(location) { const marker = L.marker([location.lat, location.lng], { icon: customIcon }); let popupContent = '<ul class="map-menu">'; location.posts.forEach(function(post) { popupContent += `<li class="menu-item"><a href="${post.permalink}">${post.title}</a></li>`; }); popupContent += '</ul>'; marker.bindPopup(popupContent); markers.addLayer(marker); }); map.addLayer(markers); map.fitBounds(markers.getBounds().pad(0.2)); } else { map.setView([0, 0], 2); } // 添加事件监听器,仅在用户与地图交互时启用滚轮缩放 map.on('click', function() { map.scrollWheelZoom.enable(); }); // 监听整个文档的点击事件,点击地图外部时禁用滚轮缩放 document.addEventListener('click', function(e) { if (!map.getContainer().contains(e.target)) { map.scrollWheelZoom.disable(); } }); } // 将 initializeMap 函数暴露为全局函数 window.initializeMap = initializeMap; // 在 DOMContentLoaded 时初始化地图(仅在 #map 存在时) document.addEventListener('DOMContentLoaded', function () { if (document.getElementById('map')) { initializeMap(); } });
3. WordPress 后台编辑关于页面,插入自定义 HTML 如下:
<div id="map"></div>
4. 适配主题开启 PJAX,修改 script.js 的 after 回调,代码如下:
after() { $vm.sleep(100).then(() => { $vm.animation = 'animation-end'; $vm.overload(); // 调用地图初始化,仅在 #map 存在时 if (document.getElementById('map')) { initializeMap(); } });
5. 经纬度查询推荐网址:
https://lbs.amap.com/tools/picker