This sample uses CIMSymbolAnimationMoveAlongLine to animate an airplane along a flight path from Los Angeles (LAX) to New York (JFK). The route is illustrative, not a live flight track.
How it works
Create the flight path
The flight path follows a geodesic curve created with geodeticDensifyOperator, accounting for the curvature of the Earth.
// Construct the geodesic flight path from LAX to JFK. const route = geodeticDensifyExecute( new Polyline({ spatialReference: { wkid: 4326 }, paths: [ [ [-118.4085, 33.9416], [-73.7781, 40.6413], ], ], }), 20, { unit: "kilometers", curveType: "geodesic" }, );A single Graphic uses a CIMSymbol to display the route, airport markers, and moving airplane.
Animate the airplane
The airplane marker starts at LAX and follows the route’s direction. Its move-along-line animation travels the full route in 20 seconds and repeats after a short delay, without updating the graphic’s geometry or using a JavaScript animation loop.
109 collapsed lines
<!doctype html><html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <title>Animation along a line | Sample | ArcGIS Maps SDK for JavaScript</title> <!-- Load the ArcGIS Maps SDK for JavaScript from CDN --> <script type="module" src="https://js.arcgis.com/5.2/"></script>
<style> html, body { height: 100%; margin: 0; }
arcgis-map { display: block; height: 100%; --calcite-color-brand: #64d9eb; --calcite-color-brand-hover: #8de5f2; --calcite-color-brand-press: #41bfd3; --calcite-color-text-2: #e8f0f7; --calcite-color-foreground-1: #15212ef2; }
.flight-card { width: min(310px, calc(100vw - 48px)); --calcite-card-border-color: #43505c; --calcite-card-corner-radius: 8px; --calcite-card-shadow: 0 8px 28px #0005; color: var(--calcite-color-text-2); }
.heading { font-size: var(--calcite-font-size-xs); letter-spacing: 0.16em; color: #97b0c3; }
h1 { margin: 12px 0 6px; font-size: 32px; font-weight: var(--calcite-font-weight-bold); letter-spacing: 0.04em; color: #ffffff; }
h1 span { color: #64d9eb; }
#status { margin-bottom: 0; font-size: var(--calcite-font-size-xs); } </style> </head>
<body class="calcite-mode-dark"> <arcgis-map basemap="dark-gray"> <arcgis-zoom slot="top-left"></arcgis-zoom> <calcite-card class="flight-card" slot="bottom-left"> <div class="heading">TRANSCONTINENTAL FLIGHT</div> <h1>LAX <span>→</span> JFK</h1> <p>Los Angeles / New York City</p> <calcite-button id="animation-toggle" icon-start="pause" width="full" disabled>Stop animation</calcite-button> <p id="status">Loading flight path...</p> </calcite-card> </arcgis-map>
<script type="module"> const [Graphic, Polyline, CIMSymbol, { geodeticDensifyLoad, geodeticDensifyExecute }] = await $arcgis.import([ "@arcgis/core/Graphic.js", "@arcgis/core/geometry/Polyline.js", "@arcgis/core/symbols/CIMSymbol.js", "@arcgis/core/geometry/operators/geodeticDensifyOperator.js", ]); const viewElement = document.querySelector("arcgis-map"); const toggle = document.querySelector("#animation-toggle"); const status = document.querySelector("#status");
// Check for reduced motion preference to set initial animation state const motionPreference = matchMedia("(prefers-reduced-motion: reduce)"); let playing = !motionPreference.matches;
// Create the animated plane symbol, airport points, and line symbol for the flight path. function createFlightSymbol(playAnimation) { return new CIMSymbol({ data: { type: "CIMSymbolReference", symbol: { type: "CIMLineSymbol", symbolLayers: [ { type: "CIMVectorMarker", enable: true, size: 28, frame: { xmin: -16, ymin: -14, xmax: 16, ymax: 14 }, respectFrame: true, scaleSymbolsProportionally: true, // Start one airplane at LAX and orient it along the route. markerPlacement: { type: "CIMMarkerPlacementAtExtremities", extremityPlacement: "JustBegin", angleToLine: true, keepUpright: false, },
animations: [ { type: "CIMSymbolAnimationMoveAlongLine", movementType: "Distance", // Travel 100% of the line during each animation cycle. distanceAlong: 100, animatedSymbolProperties: { type: "CIMAnimatedSymbolProperties", duration: 20, playAnimation, repeatType: "Loop", repeatDelay: 1.5, randomizeStartTime: false, startTimeOffset: 0, easing: "Linear", }, }, ],161 collapsed lines
markerGraphics: [ { type: "CIMMarkerGraphic", geometry: { rings: [ [ [16, 0], [12, 2], [3, 2], [-5, 13], [-8, 13], [-4, 2], [-12, 2], [-15, 6], [-16, 6], [-14, 0], [-16, -6], [-15, -6], [-12, -2], [-4, -2], [-8, -13], [-5, -13], [3, -2], [12, -2], [16, 0], ], ], }, symbol: { type: "CIMPolygonSymbol", symbolLayers: [ { type: "CIMSolidStroke", enable: true, width: 0.8, color: [15, 35, 48, 255] }, { type: "CIMSolidFill", enable: true, color: [255, 255, 255, 255] }, ], }, }, ], }, { // A separate marker layer keeps both airports stationary. type: "CIMVectorMarker", enable: true, size: 8, frame: { xmin: -5, ymin: -5, xmax: 5, ymax: 5 }, markerPlacement: { type: "CIMMarkerPlacementAtExtremities", extremityPlacement: "Both", angleToLine: false, keepUpright: false, }, markerGraphics: [ { type: "CIMMarkerGraphic", geometry: { rings: [ [ [5, 0], [3.54, 3.54], [0, 5], [-3.54, 3.54], [-5, 0], [-3.54, -3.54], [0, -5], [3.54, -3.54], [5, 0], ], ], }, symbol: { type: "CIMPolygonSymbol", symbolLayers: [ { type: "CIMSolidStroke", enable: true, width: 1.5, color: [100, 217, 235, 255] }, { type: "CIMSolidFill", enable: true, color: [21, 33, 46, 255] }, ], }, }, ], }, { type: "CIMSolidStroke", enable: true, width: 1.5, color: [100, 217, 235, 255] }, { type: "CIMSolidStroke", enable: true, width: 5, color: [100, 217, 235, 35] }, ], }, }, }); }
try { await Promise.all([viewElement.viewOnReady(), geodeticDensifyLoad()]);
// Construct the geodesic flight path from LAX to JFK. const route = geodeticDensifyExecute( new Polyline({ spatialReference: { wkid: 4326 }, paths: [ [ [-118.4085, 33.9416], [-73.7781, 40.6413], ], ], }), 20, { unit: "kilometers", curveType: "geodesic" }, );
if (!route) { throw new Error("Could not construct the LAX-to-JFK geodesic route."); }
// The line, airport markers, and airplane all belong to one graphic. const flight = new Graphic({ geometry: route, symbol: createFlightSymbol(playing), popupTemplate: { title: "Los Angeles (LAX) to New York (JFK)", content: "An illustrative geodesic route, not a scheduled airway or live aircraft position. The line, airport markers, and moving airplane belong to a single Graphic.", }, }); viewElement.graphics.add(flight); await viewElement.goTo(route.extent.expand(1.35), { animate: false });
function updateControls() { toggle.textContent = playing ? "Stop animation" : "Play animation"; toggle.setAttribute("icon-start", playing ? "pause" : "play"); status.textContent = playing ? "Flying LAX to JFK - loops every 21.5 seconds." : "Stopped at LAX. Play restarts the flight."; }
function setFlightAnimation(playAnimation) { // Clone and reassign so changes to the nested CIM JSON trigger a redraw. const symbol = flight.symbol.clone(); const airplane = symbol.data.symbol.symbolLayers[0]; airplane.animations[0].animatedSymbolProperties.playAnimation = playAnimation; flight.symbol = symbol;
playing = playAnimation; updateControls(); }
toggle.addEventListener("click", () => { setFlightAnimation(!playing); }); motionPreference.addEventListener("change", (event) => { if (event.matches) { setFlightAnimation(false); } }); updateControls(); toggle.removeAttribute("disabled"); } catch (error) { status.textContent = "Unable to load the flight path. See the console for details."; throw error; } </script> </body></html>Control playback
Use the button to stop or restart the flight from LAX. Playback changes are applied by cloning the symbol, updating playAnimation, and reassigning it to the graphic.
function setFlightAnimation(playAnimation) { // Clone and reassign so changes to the nested CIM JSON trigger a redraw. const symbol = flight.symbol.clone(); const airplane = symbol.data.symbol.symbolLayers[0]; airplane.animations[0].animatedSymbolProperties.playAnimation = playAnimation; flight.symbol = symbol;
playing = playAnimation; updateControls(); }The sample respects prefers-reduced-motion by disabling automatic playback and stopping the animation when reduced motion is requested.