Map animations in Remotion
Create map animations in Remotion using Mapbox GL JS.
Prerequisites
Install the required packages:
- npm
- bun
- pnpm
- yarn
npm i --save-exact mapbox-gl @turf/turf @types/mapbox-gl
pnpm i mapbox-gl @turf/turf @types/mapbox-gl
bun i mapbox-gl @turf/turf @types/mapbox-gl
yarn --exact add mapbox-gl @turf/turf @types/mapbox-gl
Create a free Mapbox account and get an access token from the Mapbox Console.
Add the token to your .env file:
.envtxtREMOTION_MAPBOX_TOKEN=pk.your-mapbox-access-token
Adding a map
Use useDelayRender() to wait for the map to load. The container element must have explicit dimensions and position: "absolute".
tsximport {useEffect ,useMemo ,useRef ,useState } from 'react';import {AbsoluteFill ,useDelayRender ,useVideoConfig } from 'remotion';importmapboxgl , {Map } from 'mapbox-gl';mapboxgl .accessToken =process .env .REMOTION_MAPBOX_TOKEN as string;export constMapComposition = () => {constref =useRef <HTMLDivElement >(null);const {delayRender ,continueRender } =useDelayRender ();const {width ,height } =useVideoConfig ();const [handle ] =useState (() =>delayRender ('Loading map...'));const [map ,setMap ] =useState <Map | null>(null);useEffect (() => {const_map = newMap ({container :ref .current !,zoom : 11.53,center : [6.5615, 46.0598],pitch : 65,bearing : -180,style : 'mapbox://styles/mapbox/standard',interactive : false,fadeDuration : 0,});_map .on ('load', () => {continueRender (handle );setMap (_map );});}, [handle ,continueRender ]);conststyle :React .CSSProperties =useMemo (() => ({width ,height ,position : 'absolute'}), [width ,height ]);return <AbsoluteFill ref ={ref }style ={style } />;};
Set interactive: false and fadeDuration: 0, so you can drive all animations with useCurrentFrame() instead.
Styling the map
We recommend labels and features from the Mapbox Standard style for a cleaner look:
tsx_map .on ('style.load', () => {consthideFeatures = ['showRoadsAndTransit', 'showRoadLabels', 'showTransitLabels', 'showPlaceLabels', 'showPointOfInterestLabels', 'showAdminBoundaries', 'show3dObjects', 'show3dBuildings'];for (constfeature ofhideFeatures ) {_map .setConfigProperty ('basemap',feature , false);}_map .setConfigProperty ('basemap', 'colorMotorways', 'transparent');_map .setConfigProperty ('basemap', 'colorRoads', 'transparent');});
Drawing lines
Add a GeoJSON line source and layer:
tsx_map .addSource ('route', {type : 'geojson',data : {type : 'Feature',properties : {},geometry : {type : 'LineString',coordinates :lineCoordinates ,},},});_map .addLayer ({type : 'line',source : 'route',id : 'line',paint : {'line-color': '#000000','line-width': 5,},layout : {'line-cap': 'round','line-join': 'round',},});
Animating lines
Use linear interpolation for lines that appear straight on the map:
tsxconstframe =useCurrentFrame ();const {durationInFrames } =useVideoConfig ();const {delayRender ,continueRender } =useDelayRender ();constprogress =interpolate (frame , [0,durationInFrames - 1], [0, 1], {extrapolateLeft : 'clamp',extrapolateRight : 'clamp',easing :Easing .inOut (Easing .cubic ),});conststart =lineCoordinates [0];constend =lineCoordinates [1];constcurrentLng =start [0] + (end [0] -start [0]) *progress ;constcurrentLat =start [1] + (end [1] -start [1]) *progress ;constsource =map ?.getSource ('route') asmapboxgl .GeoJSONSource ;source ?.setData ({type : 'Feature',properties : {},geometry : {type : 'LineString',coordinates : [start , [currentLng ,currentLat ]],},});
For curved geodesic paths (like flight routes), use Turf.js:
tsximport * asturf from '@turf/turf';
tsxconstrouteLine =turf .lineString (lineCoordinates );constrouteDistance =turf .length (routeLine );constcurrentDistance =Math .max (0.001,routeDistance *progress );constslicedLine =turf .lineSliceAlong (routeLine , 0,currentDistance );
Animating the camera
Move the camera along a path using Turf.js and setFreeCameraOptions():
tsxconstframe =useCurrentFrame ();const {fps } =useVideoConfig ();const {delayRender ,continueRender } =useDelayRender ();useEffect (() => {if (!map ) return;consthandle =delayRender ('Moving camera...');// @ts-expect-error (Only in docs, should work in your project)constrouteDistance =turf .length (turf .lineString (lineCoordinates ));constprogress =Math .max (0.0001,interpolate (frame /fps , [0,animationDuration ], [0, 1], {easing :Easing .inOut (Easing .sin ),}),);// @ts-expect-error (Only in docs, should work in your project)constalongRoute =turf .along (turf .lineString (lineCoordinates ),routeDistance *progress ).geometry .coordinates ;constcamera =map .getFreeCameraOptions ();camera .lookAtPoint ({lng :alongRoute [0],lat :alongRoute [1],});map .setFreeCameraOptions (camera );map .once ('idle', () =>continueRender (handle ));}, [frame ,fps ,map ,delayRender ,continueRender ]);
Adding markers
Add circle markers with labels:
tsx_map .on ('style.load', () => {_map .addSource ('cities', {type : 'geojson',data : {type : 'FeatureCollection',features : [{type : 'Feature',properties : {name : 'Los Angeles'},geometry : {type : 'Point',coordinates :LA_COORDS },},],},});_map .addLayer ({id : 'city-markers',type : 'circle',source : 'cities',paint : {'circle-radius': 40,'circle-color': '#FF4444','circle-stroke-width': 4,'circle-stroke-color': '#FFFFFF',},});_map .addLayer ({id : 'labels',type : 'symbol',source : 'cities',layout : {'text-field': ['get', 'name'],'text-font': ['DIN Pro Bold', 'Arial Unicode MS Bold'],'text-size': 50,'text-offset': [0, 0.5],'text-anchor': 'top',},paint : {'text-color': '#FFFFFF','text-halo-color': '#000000','text-halo-width': 2,},});});
Rendering
Render map animations with --gl=angle to enable the GPU:
shnpx remotion render --gl=angle