324 lines
10 KiB
XML
324 lines
10 KiB
XML
// import mapboxgl from 'mapbox-gl';
|
|
|
|
// interface AnimationOptions {
|
|
// duration: number;
|
|
// easing: string;
|
|
// transform: string;
|
|
// }
|
|
|
|
// interface CustomPopupOptions extends mapboxgl.PopupOptions {
|
|
// openingAnimation?: AnimationOptions;
|
|
// closingAnimation?: AnimationOptions;
|
|
// }
|
|
|
|
// // Extend the native Mapbox Popup
|
|
// export class CustomAnimatedPopup extends mapboxgl.Popup {
|
|
// private openingAnimation: AnimationOptions;
|
|
// private closingAnimation: AnimationOptions;
|
|
// private animating = false;
|
|
|
|
// constructor(options: CustomPopupOptions = {}) {
|
|
// // Extract animation options and pass the rest to the parent class
|
|
// const {
|
|
// openingAnimation,
|
|
// closingAnimation,
|
|
// className,
|
|
// ...mapboxOptions
|
|
// } = options;
|
|
|
|
// // Add our custom class to the className
|
|
// const customClassName = `custom-animated-popup ${className || ''}`.trim();
|
|
|
|
// // Call the parent constructor
|
|
// super({
|
|
// ...mapboxOptions,
|
|
// className: customClassName,
|
|
// });
|
|
|
|
// // Store animation options
|
|
// this.openingAnimation = openingAnimation || {
|
|
// duration: 300,
|
|
// easing: 'ease-out',
|
|
// transform: 'scale'
|
|
// };
|
|
|
|
// this.closingAnimation = closingAnimation || {
|
|
// duration: 200,
|
|
// easing: 'ease-in-out',
|
|
// transform: 'scale'
|
|
// };
|
|
|
|
// // Override the parent's add method
|
|
// const parentAdd = this.addTo;
|
|
// this.addTo = (map: mapboxgl.Map) => {
|
|
// // Call the parent method first
|
|
// parentAdd.call(this, map);
|
|
|
|
// // Apply animation after a short delay to ensure the element is in the DOM
|
|
// setTimeout(() => this.animateOpen(), 10);
|
|
|
|
// return this;
|
|
// };
|
|
// }
|
|
|
|
// // Override the remove method to add animation
|
|
// remove(): this {
|
|
// if (this.animating) {
|
|
// return this;
|
|
// }
|
|
|
|
// this.animateClose(() => {
|
|
// super.remove();
|
|
// });
|
|
|
|
// return this;
|
|
// }
|
|
|
|
// // Animation methods
|
|
// private animateOpen(): void {
|
|
// const container = this._container;
|
|
// if (!container) return;
|
|
|
|
// // Apply initial state
|
|
// container.style.opacity = '0';
|
|
// container.style.transform = 'scale(0.8)';
|
|
// container.style.transition = `
|
|
// opacity ${this.openingAnimation.duration}ms ${this.openingAnimation.easing},
|
|
// transform ${this.openingAnimation.duration}ms ${this.openingAnimation.easing}
|
|
// `;
|
|
|
|
// // Force reflow
|
|
// void container.offsetHeight;
|
|
|
|
// // Apply final state to trigger animation
|
|
// container.style.opacity = '1';
|
|
// container.style.transform = 'scale(1)';
|
|
// }
|
|
|
|
// private animateClose(callback: () => void): void {
|
|
// const container = this._container;
|
|
// if (!container) {
|
|
// callback();
|
|
// return;
|
|
// }
|
|
|
|
// this.animating = true;
|
|
|
|
// // Setup transition
|
|
// container.style.transition = `
|
|
// opacity ${this.closingAnimation.duration}ms ${this.closingAnimation.easing},
|
|
// transform ${this.closingAnimation.duration}ms ${this.closingAnimation.easing}
|
|
// `;
|
|
|
|
// // Apply closing animation
|
|
// container.style.opacity = '0';
|
|
// container.style.transform = 'scale(0.8)';
|
|
|
|
// // Execute callback after animation completes
|
|
// setTimeout(() => {
|
|
// this.animating = false;
|
|
// callback();
|
|
// }, this.closingAnimation.duration);
|
|
// }
|
|
|
|
// // Add method to create expanding wave circles
|
|
// addWaveCircles(map: mapboxgl.Map, lngLat: mapboxgl.LngLat, options: {
|
|
// color?: string,
|
|
// maxRadius?: number,
|
|
// duration?: number,
|
|
// count?: number,
|
|
// showCenter?: boolean
|
|
// } = {}): void {
|
|
// const {
|
|
// color = 'red',
|
|
// maxRadius = 80, // Reduce max radius for less "over" effect
|
|
// duration = 2000, // Faster animation
|
|
// count = 2, // Fewer circles
|
|
// showCenter = true
|
|
// } = options;
|
|
|
|
// const sourceId = `wave-circles-${Math.random().toString(36).substring(2, 9)}`;
|
|
|
|
// if (!map.getSource(sourceId)) {
|
|
// map.addSource(sourceId, {
|
|
// type: 'geojson',
|
|
// data: {
|
|
// type: 'FeatureCollection',
|
|
// features: [{
|
|
// type: 'Feature',
|
|
// geometry: {
|
|
// type: 'Point',
|
|
// coordinates: [lngLat.lng, lngLat.lat]
|
|
// },
|
|
// properties: {
|
|
// radius: 0
|
|
// }
|
|
// }]
|
|
// }
|
|
// });
|
|
|
|
// for (let i = 0; i < count; i++) {
|
|
// const layerId = `${sourceId}-layer-${i}`;
|
|
// const delay = i * (duration / count);
|
|
|
|
// map.addLayer({
|
|
// id: layerId,
|
|
// type: 'circle',
|
|
// source: sourceId,
|
|
// paint: {
|
|
// 'circle-radius': ['interpolate', ['linear'], ['get', 'radius'], 0, 0, 100, maxRadius],
|
|
// 'circle-color': 'transparent',
|
|
// 'circle-opacity': ['interpolate', ['linear'], ['get', 'radius'],
|
|
// 0, showCenter ? 0.15 : 0, // Lower opacity
|
|
// 100, 0
|
|
// ],
|
|
// 'circle-stroke-width': 1.5, // Thinner stroke
|
|
// 'circle-stroke-color': color
|
|
// }
|
|
// });
|
|
|
|
// this.animateWaveCircle(map, sourceId, layerId, duration, delay);
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// private animateWaveCircle(
|
|
// map: mapboxgl.Map,
|
|
// sourceId: string,
|
|
// layerId: string,
|
|
// duration: number,
|
|
// delay: number
|
|
// ): void {
|
|
// let start: number | null = null;
|
|
// let animationId: number;
|
|
|
|
// const animate = (timestamp: number) => {
|
|
// if (!start) {
|
|
// start = timestamp + delay;
|
|
// }
|
|
|
|
// const progress = Math.max(0, timestamp - start);
|
|
// const progressPercent = Math.min(progress / duration, 1);
|
|
|
|
// if (map.getSource(sourceId)) {
|
|
// (map.getSource(sourceId) as mapboxgl.GeoJSONSource).setData({
|
|
// type: 'FeatureCollection',
|
|
// features: [{
|
|
// type: 'Feature',
|
|
// geometry: {
|
|
// type: 'Point',
|
|
// coordinates: (map.getSource(sourceId) as any)._data.features[0].geometry.coordinates
|
|
// },
|
|
// properties: {
|
|
// radius: progressPercent * 100
|
|
// }
|
|
// }]
|
|
// });
|
|
// }
|
|
|
|
// if (progressPercent < 1) {
|
|
// animationId = requestAnimationFrame(animate);
|
|
// } else if (map.getLayer(layerId)) {
|
|
// // Restart the animation for continuous effect
|
|
// start = null;
|
|
// animationId = requestAnimationFrame(animate);
|
|
// }
|
|
// };
|
|
|
|
// // Start the animation after delay
|
|
// setTimeout(() => {
|
|
// animationId = requestAnimationFrame(animate);
|
|
// }, delay);
|
|
|
|
// // Clean up on popup close
|
|
// this.once('close', () => {
|
|
// cancelAnimationFrame(animationId);
|
|
// if (map.getLayer(layerId)) {
|
|
// map.removeLayer(layerId);
|
|
// }
|
|
// if (map.getSource(sourceId) && !map.getLayer(layerId)) {
|
|
// map.removeSource(sourceId);
|
|
// }
|
|
// });
|
|
// }
|
|
// }
|
|
|
|
// // Add styles to document when in browser environment
|
|
// if (typeof document !== 'undefined') {
|
|
// // Add styles only if they don't exist yet
|
|
// if (!document.getElementById('custom-animated-popup-styles')) {
|
|
// const style = document.createElement('style');
|
|
// style.id = 'custom-animated-popup-styles';
|
|
// style.textContent = `
|
|
// .custom-animated-popup {
|
|
// will-change: transform, opacity;
|
|
// }
|
|
// .custom-animated-popup .mapboxgl-popup-content {
|
|
// overflow: hidden;
|
|
// }
|
|
|
|
// /* Marker styles with wave circles */
|
|
// .marker-gempa {
|
|
// position: relative;
|
|
// width: 30px;
|
|
// height: 30px;
|
|
// cursor: pointer;
|
|
// }
|
|
|
|
// .circles {
|
|
// position: relative;
|
|
// width: 100%;
|
|
// height: 100%;
|
|
// display: flex;
|
|
// align-items: center;
|
|
// justify-content: center;
|
|
// }
|
|
|
|
// .circle1, .circle2, .circle3 {
|
|
// position: absolute;
|
|
// border-radius: 50%;
|
|
// border: 2px solid red;
|
|
// width: 100%;
|
|
// height: 100%;
|
|
// opacity: 0;
|
|
// animation: pulse 2s infinite;
|
|
// }
|
|
|
|
// .circle2 {
|
|
// animation-delay: 0.5s;
|
|
// }
|
|
|
|
// .circle3 {
|
|
// animation-delay: 1s;
|
|
// }
|
|
|
|
// @keyframes pulse {
|
|
// 0% {
|
|
// transform: scale(0.5);
|
|
// opacity: 0;
|
|
// }
|
|
// 50% {
|
|
// opacity: 0.8;
|
|
// }
|
|
// 100% {
|
|
// transform: scale(1.5);
|
|
// opacity: 0;
|
|
// }
|
|
// }
|
|
|
|
// .blink {
|
|
// animation: blink 1s infinite;
|
|
// color: red;
|
|
// font-size: 20px;
|
|
// }
|
|
|
|
// @keyframes blink {
|
|
// 0% { opacity: 1; }
|
|
// 50% { opacity: 0.3; }
|
|
// 100% { opacity: 1; }
|
|
// }
|
|
// `;
|
|
// document.head.appendChild(style);
|
|
// }
|
|
// }
|