Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] - Add smooth move animation in Marker when coordinates are updated with bearing #2305

Open
howdyhyber opened this issue Oct 25, 2023 · 1 comment
Labels

Comments

@howdyhyber
Copy link

Target Use Case

It would be nice if the marker's location is updated, it will move smoothly to the location.

Proposal

Apply a smooth transition on markers movements based on location changes

@BjoernRave
Copy link

BjoernRave commented Feb 27, 2024

I just came up with this, not sure if it's the best/most performant approach you can do though

import "mapbox-gl/dist/mapbox-gl.css"
import React, { useEffect, useState } from "react"
import ReactMapGL, { Marker } from "react-map-gl"
import { usePrevious } from "react-use"

interface UserMarkerProps {
  longitude: number
  latitude: number
}

const UserMarker: React.FC<UserMarkerProps> = ({ longitude, latitude }) => {
  const prevLocation = usePrevious({ longitude, latitude })
  const [interpolatedLocation, setInterpolatedLocation] = useState({
    longitude,
    latitude,
  })

  const interpolateLocation = (
    startLocation,
    endLocation,
    duration: number,
  ) => {
    let startTime: number

    const step = (timestamp) => {
      if (!startTime) startTime = timestamp
      const progress = (timestamp - startTime) / duration

      if (progress < 1) {
        const latitude =
          startLocation.latitude +
          (endLocation.latitude - startLocation.latitude) * progress
        const longitude =
          startLocation.longitude +
          (endLocation.longitude - startLocation.longitude) * progress
        setInterpolatedLocation({ latitude, longitude })
        requestAnimationFrame(step)
      } else {
        setInterpolatedLocation(endLocation)
      }
    }

    requestAnimationFrame(step)
  }

  useEffect(() => {
    if (prevLocation) {
      interpolateLocation(prevLocation, { longitude, latitude }, 1000)
    }
  }, [longitude, latitude])

  return (
    <Marker
      longitude={interpolatedLocation.longitude}
      latitude={interpolatedLocation.latitude}
    >
      <div className="marker">test</div>
    </Marker>
  )
}

const MapComponent: React.FC = () => {
  const [userPosition, setUserPosition] = useState({
    latitude: 37.78,
    longitude: -122.41,
  })

  // Simulate position change
  useEffect(() => {
    const interval = setInterval(() => {
      setUserPosition({
        latitude: userPosition.latitude + 0.001,
        longitude: userPosition.longitude + 0.001,
      })
    }, 1000)

    return () => clearInterval(interval)
  }, [userPosition])

  return (
    <ReactMapGL
      style={{
        height: "100vh",
        width: "100%",
      }}
      mapStyle="mapbox://styles/mapbox/streets-v11"
      mapboxAccessToken={process.env.NEXT_PUBLIC_MAPBOX_KEY}
    >
      <UserMarker
        longitude={userPosition.longitude}
        latitude={userPosition.latitude}
      />
    </ReactMapGL>
  )
}

export default MapComponent

or as its own component:

//AnimatedMarker.tsx

import { FC, useEffect, useState, type PropsWithChildren } from "react"
import { Marker, type MarkerProps } from "react-map-gl"
import { usePrevious } from "react-use"

const AnimatedMarker: FC<PropsWithChildren<Props>> = ({
  longitude,
  latitude,
  children,
  ...props
}) => {
  const prevLocation = usePrevious({ longitude, latitude })
  const [interpolatedLocation, setInterpolatedLocation] = useState({
    longitude,
    latitude,
  })

  const interpolateLocation = (
    startLocation: { longitude: number; latitude: number },
    endLocation: { longitude: number; latitude: number },
    duration: number,
  ) => {
    let startTime: number

    const step = (timestamp: number) => {
      if (!startTime) startTime = timestamp
      const progress = (timestamp - startTime) / duration

      if (progress < 1) {
        const latitude =
          startLocation.latitude +
          (endLocation.latitude - startLocation.latitude) * progress

        const longitude =
          startLocation.longitude +
          (endLocation.longitude - startLocation.longitude) * progress

        setInterpolatedLocation({ latitude, longitude })
        requestAnimationFrame(step)
      } else {
        setInterpolatedLocation(endLocation)
      }
    }

    requestAnimationFrame(step)
  }

  useEffect(() => {
    if (prevLocation) {
      interpolateLocation(prevLocation, { longitude, latitude }, 1000)
    }
  }, [longitude, latitude])

  return (
    <Marker
      longitude={interpolatedLocation.longitude}
      latitude={interpolatedLocation.latitude}
      {...props}
    >
      {children}
    </Marker>
  )
}

export default AnimatedMarker

interface Props extends MarkerProps {}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants