You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现Google Map平滑360度方位角旋转(多端适配)

Google Maps 360° Camera Rotation: Smooth, Stutter-Free Animation Across Platforms

I've tackled the problem of achieving a seamless 360° camera rotation around a central point in Google Maps, fixing the stutter caused by default easing animations. Below are platform-specific solutions for Swift, Android, Web (JS/React), and even PHP for server-side setup.


Swift: Linear Animation Fix (Already Implemented)

The initial iOS implementation had stuttering because of the default ease-in/ease-out timing function. Switching to a linear timing function via CAMediaTimingFunctionLinear eliminated the start/end jank. Here's the polished code with clear comments:

// Perform 3x 120° rotations to complete a full 360° loop
func start360MapRotation() {
    let rotationDuration: CFTimeInterval = 3.0
    let linearTiming = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
    
    // First 120° rotation
    CATransaction.begin()
    CATransaction.setValue(rotationDuration, forKey: kCATransactionAnimationDuration)
    CATransaction.setAnimationTimingFunction(linearTiming)
    CATransaction.setCompletionBlock { [weak self] in
        guard let self = self else { return }
        
        // Second 120° rotation
        CATransaction.begin()
        CATransaction.setValue(rotationDuration, forKey: kCATransactionAnimationDuration)
        CATransaction.setAnimationTimingFunction(linearTiming)
        CATransaction.setCompletionBlock { [weak self] in
            guard let self = self else { return }
            
            // Third 120° rotation
            CATransaction.begin()
            CATransaction.setValue(rotationDuration, forKey: kCATransactionAnimationDuration)
            CATransaction.setAnimationTimingFunction(linearTiming)
            CATransaction.setCompletionBlock { [weak self] in
                guard let self = self else { return }
                
                // Post-rotation cleanup & focus on nearest branch
                UIView.animate(withDuration: 0.5) {
                    self.findingYourLocation.alpha = 0.0
                }
                
                // Zoom in and center on target location with easing
                let nearestBranch = CLLocationCoordinate2D(latitude: 24.806849, longitude: 67.038734)
                let targetCam = GMSCameraUpdate.setTarget(nearestBranch)
                
                CATransaction.begin()
                CATransaction.setValue(3.0, forKey: kCATransactionAnimationDuration)
                CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut))
                CATransaction.setCompletionBlock { [weak self] in
                    self?.nextButton.alpha = 1.0
                }
                self.mapView.animate(with: targetCam)
                self.mapView.animate(toZoom: 15)
                self.mapView.animate(toBearing: 0)
                self.mapView.animate(toViewingAngle: 0)
                CATransaction.commit()
            }
            self.mapView.animate(toBearing: self.mapView.camera.bearing + 120)
            CATransaction.commit()
        }
        self.mapView.animate(toBearing: self.mapView.camera.bearing + 120)
        CATransaction.commit()
    }
    self.mapView.animate(toBearing: self.mapView.camera.bearing + 120)
    CATransaction.commit()
}

Key fix: Using kCAMediaTimingFunctionLinear ensures consistent rotation speed throughout the animation, no slow start/end stutter.


Android: Fixing Stuttering Rotation

The default Google Maps Android animation uses an ease-in/ease-out interpolator, which causes stutter at the start and end of rotations. To fix this, use a ValueAnimator with a linear interpolator to manually drive the bearing value:

fun start360MapRotation(map: GoogleMap) {
    val initialBearing = map.cameraPosition.bearing
    val targetBearing = initialBearing + 360f // Full 360° rotation
    val rotationDuration = 9000L // 3 seconds per 120° x 3 loops = 9s total

    val animator = ValueAnimator.ofFloat(initialBearing, targetBearing).apply {
        duration = rotationDuration
        interpolator = LinearInterpolator() // Critical for linear motion
        addUpdateListener { animation ->
            val currentBearing = animation.animatedValue as Float
            val updatedCamera = CameraPosition.builder(map.cameraPosition)
                .bearing(currentBearing)
                .build()
            map.moveCamera(CameraUpdateFactory.newCameraPosition(updatedCamera))
        }
        // Optional: Add loop logic
        addListener(object : Animator.AnimatorListener {
            override fun onAnimationEnd(animation: Animator) {
                // Restart animation if needed
                // start360MapRotation(map)
            }
            override fun onAnimationStart(animation: Animator) {}
            override fun onAnimationCancel(animation: Animator) {}
            override fun onAnimationRepeat(animation: Animator) {}
        })
    }
    animator.start()
}

Why this works: By controlling the bearing value directly with a linear interpolator, we avoid the default easing that causes stutter. The animation maintains a constant speed from start to finish.


Web: Smooth Rotation for Vanilla JS & React

Vanilla JavaScript

Google Maps JS API's default animations use easing, so we'll use requestAnimationFrame to create a linear animation by calculating progress over time:

function start360MapRotation(map) {
    let currentHeading = map.getHeading();
    const targetHeading = currentHeading + 360;
    const duration = 3000; // 3 seconds for full rotation
    const startTime = performance.now();

    const animate = (timestamp) => {
        const elapsed = timestamp - startTime;
        if (elapsed < duration) {
            const progress = elapsed / duration;
            const newHeading = currentHeading + (360 * progress);
            map.setHeading(newHeading);
            requestAnimationFrame(animate);
        } else {
            map.setHeading(targetHeading); // Ensure final value is exact
            // Optional: Restart loop
            // start360MapRotation(map);
        }
    };

    requestAnimationFrame(animate);
}

React (with @react-google-maps/api)

Use React hooks to manage the animation lifecycle and avoid memory leaks:

import { useGoogleMap } from '@react-google-maps/api';
import { useEffect, useRef } from 'react';

const Map360Rotation = () => {
    const map = useGoogleMap();
    const animationIdRef = useRef(null);
    const startTimeRef = useRef(null);

    useEffect(() => {
        if (!map) return;

        const startRotation = () => {
            const currentHeading = map.getHeading();
            const targetHeading = currentHeading + 360;
            const duration = 3000;

            startTimeRef.current = performance.now();

            const animate = (timestamp) => {
                const elapsed = timestamp - startTimeRef.current;
                if (elapsed < duration) {
                    const progress = elapsed / duration;
                    const newHeading = currentHeading + (360 * progress);
                    map.setHeading(newHeading);
                    animationIdRef.current = requestAnimationFrame(animate);
                } else {
                    map.setHeading(targetHeading);
                    // Optional: Restart rotation
                    // startRotation();
                }
            };

            animationIdRef.current = requestAnimationFrame(animate);
        };

        startRotation();

        // Cleanup on unmount
        return () => {
            if (animationIdRef.current) {
                cancelAnimationFrame(animationIdRef.current);
            }
        };
    }, [map]);

    return null;
};

export default Map360Rotation;

Both solutions use time-based progress calculation to ensure a smooth, linear rotation without easing-induced stutter.


PHP: Server-Side Bearing Calculation

For server-side setup (e.g., precomputing camera positions for frontend use), calculate the final bearing after a full rotation:

<?php
// Initial bearing (0° = north)
$initialBearing = 0;
// Total rotation (360° for full loop)
$rotationAngle = 360;
// Final bearing (mod 360 to keep value within 0-360 range)
$finalBearing = fmod($initialBearing + $rotationAngle, 360);

// Camera parameters to pass to frontend
$cameraConfig = [
    'latitude' => 24.806849,
    'longitude' => 67.038734,
    'bearing' => $finalBearing,
    'zoom' => 15
];

header('Content-Type: application/json');
echo json_encode($cameraConfig);
?>

This provides the frontend with the exact final bearing, which can be used to anchor the end of your rotation animation.


内容的提问来源于stack exchange,提问作者Nah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:30:46