Add pulse animation for POI search

This commit is contained in:
Râu Cao 2026-01-18 19:02:30 +07:00
parent a82cdbf7e0
commit 452ea8e674
Signed by: raucao
GPG Key ID: 37036C356E56CC51
2 changed files with 65 additions and 3 deletions

View File

@ -5,7 +5,8 @@ import 'ol/ol.css';
import Map from 'ol/Map.js';
import { defaults as defaultControls } from 'ol/control.js';
import View from 'ol/View.js';
import { fromLonLat, toLonLat } from 'ol/proj.js';
import { fromLonLat, toLonLat, getPointResolution } from 'ol/proj.js';
import Overlay from 'ol/Overlay.js';
import LayerGroup from 'ol/layer/Group.js';
import VectorLayer from 'ol/layer/Vector.js';
import VectorSource from 'ol/source/Vector.js';
@ -21,6 +22,8 @@ export default class MapComponent extends Component {
mapInstance;
bookmarkSource;
searchOverlay;
searchOverlayElement;
setupMap = modifier((element) => {
if (this.mapInstance) return;
@ -57,6 +60,16 @@ export default class MapComponent extends Component {
apply(openfreemap, 'https://tiles.openfreemap.org/styles/liberty');
// Create Overlay for search pulse
this.searchOverlayElement = document.createElement('div');
this.searchOverlayElement.className = 'search-pulse';
this.searchOverlay = new Overlay({
element: this.searchOverlayElement,
positioning: 'center-center',
stopEvent: false, // Allow clicks to pass through
});
this.mapInstance.addOverlay(this.searchOverlay);
this.mapInstance.on('singleclick', this.handleMapClick);
// Load places when map moves
@ -196,11 +209,28 @@ export default class MapComponent extends Component {
const coords = toLonLat(event.coordinate);
const [lon, lat] = coords;
// ... continue with normal OSM fetch logic ...
// Determine search radius based on whether we clicked a named feature
const searchRadius = selectedFeatureName ? 30 : 50;
// Show visual feedback (pulse)
if (this.searchOverlayElement) {
const view = this.mapInstance.getView();
const resolutionAtPoint = getPointResolution(
view.getProjection(),
view.getResolution(),
event.coordinate
);
const diameterInMeters = searchRadius * 2;
const diameterInPixels = diameterInMeters / resolutionAtPoint;
this.searchOverlayElement.style.width = `${diameterInPixels}px`;
this.searchOverlayElement.style.height = `${diameterInPixels}px`;
this.searchOverlay.setPosition(event.coordinate);
this.searchOverlayElement.classList.add('active');
}
// 2. Fetch authoritative data via Overpass
try {
const searchRadius = selectedFeatureName ? 30 : 50;
let pois = await this.osm.getNearbyPois(lat, lon, searchRadius);
// Sort by distance from click
@ -263,6 +293,10 @@ export default class MapComponent extends Component {
}
} catch (error) {
console.error('Failed to fetch POIs:', error);
} finally {
if (this.searchOverlayElement) {
this.searchOverlayElement.classList.remove('active');
}
}
};

View File

@ -141,3 +141,31 @@ body {
color: #666;
margin-top: 2rem;
}
/* Map Search Pulse Animation */
.search-pulse {
border-radius: 50%;
border: 2px solid rgba(255, 204, 51, 0.8); /* Gold/Yellow to match markers */
background: rgba(255, 204, 51, 0.2);
position: absolute;
transform: translate(-50%, -50%);
pointer-events: none;
animation: pulse 1.5s infinite ease-out;
box-sizing: border-box; /* Ensure border is included in width/height */
display: none; /* Hidden by default */
}
.search-pulse.active {
display: block;
}
@keyframes pulse {
0% {
transform: translate(-50%, -50%) scale(0.8);
opacity: 0.8;
}
100% {
transform: translate(-50%, -50%) scale(1.4);
opacity: 0;
}
}