第13章 - 地图事件与动画
第13章 - 地图事件与动画
13.1 事件系统
13.1.1 地图事件
// 点击事件
map.on('click', (event) => {
console.log('坐标:', event.coordinate);
console.log('像素:', event.pixel);
});
// 单击(等待确认不是双击)
map.on('singleclick', (event) => {
console.log('单击');
});
// 双击
map.on('dblclick', (event) => {
event.preventDefault(); // 阻止默认缩放
});
// 指针移动
map.on('pointermove', (event) => {
if (!event.dragging) {
// 悬停检测
}
});
// 地图移动
map.on('movestart', () => console.log('移动开始'));
map.on('moveend', () => console.log('移动结束'));
// 渲染事件
map.on('postrender', () => { /* 每帧渲染后 */ });
map.on('rendercomplete', () => { /* 渲染完成 */ });
13.1.2 视图事件
const view = map.getView();
// 属性变化
view.on('change:center', () => {
console.log('中心点:', view.getCenter());
});
view.on('change:resolution', () => {
console.log('缩放级别:', view.getZoom());
});
view.on('change:rotation', () => {
console.log('旋转角度:', view.getRotation());
});
13.1.3 图层事件
// 图层可见性变化
layer.on('change:visible', () => {
console.log('可见性:', layer.getVisible());
});
// 透明度变化
layer.on('change:opacity', () => {
console.log('透明度:', layer.getOpacity());
});
// 矢量源事件
vectorSource.on('addfeature', (event) => {
console.log('添加要素:', event.feature);
});
vectorSource.on('removefeature', (event) => {
console.log('移除要素:', event.feature);
});
13.2 视图动画
13.2.1 基本动画
const view = map.getView();
// 平移动画
view.animate({
center: fromLonLat([121.4737, 31.2304]),
duration: 1000
});
// 缩放动画
view.animate({
zoom: 15,
duration: 500
});
// 旋转动画
view.animate({
rotation: Math.PI / 2,
duration: 500
});
// 组合动画
view.animate({
center: fromLonLat([121.4737, 31.2304]),
zoom: 15,
rotation: 0,
duration: 1500
});
13.2.2 缓动函数
import { easeIn, easeOut, inAndOut, linear } from 'ol/easing';
// 使用缓动函数
view.animate({
center: destination,
duration: 1000,
easing: easeOut // 先快后慢
});
// 自定义缓动
function bounce(t) {
const s = 7.5625, p = 2.75;
if (t < 1/p) return s*t*t;
if (t < 2/p) { t -= 1.5/p; return s*t*t + 0.75; }
if (t < 2.5/p) { t -= 2.25/p; return s*t*t + 0.9375; }
t -= 2.625/p;
return s*t*t + 0.984375;
}
view.animate({
center: destination,
duration: 1500,
easing: bounce
});
13.2.3 连续动画
// 按顺序执行
view.animate(
{ center: point1, duration: 500 },
{ center: point2, duration: 500 },
{ center: point3, duration: 500 }
);
// 飞行效果
function flyTo(destination, zoom) {
const startZoom = view.getZoom();
const minZoom = Math.min(startZoom, zoom, 5);
view.animate(
{ center: destination, duration: 1000 },
callback
);
view.animate(
{ zoom: minZoom, duration: 500 },
{ zoom: zoom, duration: 500 },
callback
);
let parts = 2;
function callback(complete) {
if (--parts === 0) {
console.log('飞行完成');
}
}
}
13.3 要素动画
13.3.1 位置动画
// 要素移动动画
function animateFeature(feature, destination, duration = 1000) {
const geometry = feature.getGeometry();
const start = geometry.getCoordinates();
const startTime = Date.now();
function animate() {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
// 使用缓动
const eased = easeOut(progress);
const x = start[0] + (destination[0] - start[0]) * eased;
const y = start[1] + (destination[1] - start[1]) * eased;
geometry.setCoordinates([x, y]);
if (progress < 1) {
requestAnimationFrame(animate);
}
}
animate();
}
// 沿路径移动
function animateAlongPath(feature, path, duration = 5000) {
const startTime = Date.now();
function animate() {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
// 计算当前位置
const point = path.getCoordinateAt(progress);
feature.getGeometry().setCoordinates(point);
if (progress < 1) {
requestAnimationFrame(animate);
map.render();
}
}
animate();
}
13.3.2 样式动画
// 脉冲效果
function createPulseStyle() {
let radius = 5;
let increasing = true;
return function(feature) {
if (increasing) {
radius += 0.5;
if (radius >= 15) increasing = false;
} else {
radius -= 0.5;
if (radius <= 5) increasing = true;
}
return new Style({
image: new Circle({
radius: radius,
fill: new Fill({ color: 'rgba(255, 0, 0, 0.5)' }),
stroke: new Stroke({ color: 'red', width: 2 })
})
});
};
}
// 闪烁效果
function createBlinkStyle() {
return function(feature) {
const visible = Math.floor(Date.now() / 500) % 2 === 0;
return visible ? new Style({
image: new Circle({
radius: 8,
fill: new Fill({ color: 'red' })
})
}) : null;
};
}
// 需要持续渲染
function startAnimation() {
map.on('postrender', () => map.render());
}
13.4 轨迹回放
class TrackPlayer {
constructor(map, layer, coordinates) {
this.map = map;
this.layer = layer;
this.coordinates = coordinates;
this.currentIndex = 0;
this.playing = false;
this.speed = 1;
// 创建移动的点要素
this.marker = new Feature({
geometry: new Point(coordinates[0])
});
this.marker.setStyle(new Style({
image: new Icon({
src: '/images/car.png',
rotation: 0
})
}));
layer.getSource().addFeature(this.marker);
}
play() {
this.playing = true;
this.animate();
}
pause() {
this.playing = false;
}
stop() {
this.playing = false;
this.currentIndex = 0;
this.updatePosition();
}
setSpeed(speed) {
this.speed = speed;
}
animate() {
if (!this.playing) return;
if (this.currentIndex >= this.coordinates.length - 1) {
this.playing = false;
return;
}
const start = this.coordinates[this.currentIndex];
const end = this.coordinates[this.currentIndex + 1];
// 计算方向
const dx = end[0] - start[0];
const dy = end[1] - start[1];
const rotation = -Math.atan2(dy, dx);
// 更新图标旋转
this.marker.getStyle().getImage().setRotation(rotation);
// 移动到下一点
this.currentIndex++;
this.updatePosition();
// 延迟后继续
setTimeout(() => this.animate(), 100 / this.speed);
}
updatePosition() {
const coord = this.coordinates[this.currentIndex];
this.marker.getGeometry().setCoordinates(coord);
// 地图跟随
this.map.getView().setCenter(coord);
}
}
// 使用示例
const coordinates = [/* 轨迹坐标数组 */];
const player = new TrackPlayer(map, vectorLayer, coordinates);
document.getElementById('play-btn').onclick = () => player.play();
document.getElementById('pause-btn').onclick = () => player.pause();
document.getElementById('stop-btn').onclick = () => player.stop();
13.5 地图截图与导出
// 导出为图片
function exportMapAsImage(map, filename = 'map.png') {
map.once('rendercomplete', () => {
const canvas = document.createElement('canvas');
const size = map.getSize();
canvas.width = size[0];
canvas.height = size[1];
const context = canvas.getContext('2d');
document.querySelectorAll('.ol-layer canvas').forEach(layerCanvas => {
if (layerCanvas.width > 0) {
const opacity = layerCanvas.parentNode.style.opacity || 1;
context.globalAlpha = opacity;
context.drawImage(layerCanvas, 0, 0);
}
});
// 下载
const link = document.createElement('a');
link.download = filename;
link.href = canvas.toDataURL();
link.click();
});
map.renderSync();
}
// 复制到剪贴板
async function copyMapToClipboard(map) {
map.once('rendercomplete', async () => {
const canvas = document.createElement('canvas');
const size = map.getSize();
canvas.width = size[0];
canvas.height = size[1];
const context = canvas.getContext('2d');
document.querySelectorAll('.ol-layer canvas').forEach(layerCanvas => {
if (layerCanvas.width > 0) {
context.drawImage(layerCanvas, 0, 0);
}
});
canvas.toBlob(async (blob) => {
await navigator.clipboard.write([
new ClipboardItem({ 'image/png': blob })
]);
console.log('已复制到剪贴板');
});
});
map.renderSync();
}
13.6 本章小结
本章介绍了地图事件与动画:
- 事件系统:地图、视图、图层事件
- 视图动画:平移、缩放、旋转、缓动函数
- 要素动画:位置动画、样式动画
- 轨迹回放:沿路径移动、播放控制
- 地图导出:截图、剪贴板
关键要点
- 使用 view.animate() 实现平滑过渡
- 缓动函数控制动画节奏
- requestAnimationFrame 实现自定义动画

浙公网安备 33010602011771号