<!DOCTYPE html> <html> <head> <script src="https://d3js.org/d3.v7.min.js"></script> <style> .map-container { border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; } .country-path { fill: #a0d8ef; stroke: #fff; stroke-width: 0.5; transition: fill 0.3s; } .country-path:hover { fill: #4db8d8; } </style> </head> <body> <div id="map" class="map-container"></div> <script src="map.js"></script> </body> </html>
map.js
// 基础参数配置 const width = 960; const height = 600; const initialScale = 100; const centerPoint = [0, 20]; // 创建SVG画布 const svg = d3.select("#map") .append("svg") .attr("width", width) .attr("height", height); // 定义投影系统 const projection = d3.geoMercator() .scale(initialScale) .center(centerPoint) .translate([width/2, height/2]); // 路径生成器 const pathGenerator = d3.geoPath() .projection(projection); // 创建缩放控制器 const zoomHandler = d3.zoom() .scaleExtent([1, 15]) // 缩放范围限制 .on('zoom', handleZoom); // 加载地理数据 d3.json('https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson') .then(createMap); function createMap(geojson) { svg.selectAll('path') .data(geojson.features) .enter() .append('path') .attr('class', 'country-path') .attr('d', pathGenerator); // 初始化缩放 svg.call(zoomHandler) .call(zoomHandler.transform, d3.zoomIdentity.translate(width/2, height/2)); } function handleZoom(event) { const { transform } = event; svg.selectAll('path') .attr('transform', transform) .attr('stroke-width', 0.5 / transform.k); // 动态调整描边 }
投影系统优化
geoMercator
投影时,通过调整scale
参数控制初始显示比例translate
参数确保地图在画布居中显示性能增强策略
// 添加防抖处理 const debouncedZoom = d3.zoom() .on('zoom', _.debounce(handleZoom, 50)); // 路径简化处理 .attr('d', pathGenerator.pointRadius(5 / transform.k));
多设备适配方案
// 触屏设备优化 if ('ontouchstart' in window) { zoomHandler.filter(() => true); } // 响应式布局 window.addEventListener('resize', () => { const newWidth = document.getElementById('map').offsetWidth; projection.translate([newWidth/2, height/2]); svg.attr('width', newWidth); });
// 添加双击复位功能 svg.on("dblclick.zoom", () => { svg.transition() .duration(750) .call(zoomHandler.transform, d3.zoomIdentity); }); // 区域聚焦功能 function focusOnCountry(code) { const feature = geojson.features.find(d => d.id === code); const [[x0, y0], [x1, y1]] = pathGenerator.bounds(feature); const scale = 0.9 / Math.max((x1 - x0)/width, (y1 - y0)/height); const translate = [ (width - scale * (x0 + x1)) / 2, (height - scale * (y0 + y1)) / 2 ]; svg.transition() .duration(1000) .call(zoomHandler.transform, d3.zoomIdentity .translate(translate[0], translate[1]) .scale(scale)); }
will-change: transform
提升CSS渲染性能// 示例:键盘控制 document.addEventListener('keydown', (event) => { const currentTransform = d3.zoomTransform(svg.node()); switch(event.key) { case '+': svg.call(zoomHandler.scaleBy, 1.2); break; case '-': svg.call(zoomHandler.scaleBy, 0.8); break; } });
参考文献
(本文代码在Chrome 89+、Firefox 86+环境下测试通过,地理数据来自Natural Earth公开数据集)