---
title: "Customize camera animations | JavaScript maps SDK"
description: "Customize map camera animations using the AnimationOptions property."
---

> For AI agents: a documentation index is available at [/llms.txt](https://docs.maptiler.com/llms.txt). Markdown versions of all pages are available by appending .md to any URL.

# Customize camera animations

Customize camera animations using [`AnimationOptions`](https://docs.maptiler.com/sdk-js/api/properties/#animationoptions).

### NPM module setup

```bash
npm install --save @maptiler/sdk
```

#### main.js

```js
import { Map, MapStyle, config } from '@maptiler/sdk';
import '@maptiler/sdk/dist/maptiler-sdk.css';

config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';
// declare various easing functions.
// easing functions mathematically describe
// how fast a value changes during an animation.
// each function takes a parameter t that represents
// the progress of the animation.
// t is in a range of 0 to 1 where 0 is the initial
// state and 1 is the completed state.
const easingFunctions = {
    // start slow and gradually increase speed
    easeInCubic: function (t) {
        return t * t * t;
    },
    // start fast with a long, slow wind-down
    easeOutQuint: function (t) {
        return 1 - Math.pow(1 - t, 5);
    },
    // slow start and finish with fast middle
    easeInOutCirc: function (t) {
        return t < 0.5
            ? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2
            : (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2;
    },
    // fast start with a "bounce" at the end
    easeOutBounce: function (t) {
        const n1 = 7.5625;
        const d1 = 2.75;

        if (t < 1 / d1) {
            return n1 * t * t;
        } else if (t < 2 / d1) {
            return n1 * (t -= 1.5 / d1) * t + 0.75;
        } else if (t < 2.5 / d1) {
            return n1 * (t -= 2.25 / d1) * t + 0.9375;
        } else {
            return n1 * (t -= 2.625 / d1) * t + 0.984375;
        }
    }
};

// set up some helpful UX on the form
const durationValueSpan = document.getElementById('durationValue');
const durationInput = document.getElementById('duration');
durationValueSpan.innerHTML = durationInput.value / 1000 + ' seconds';
durationInput.addEventListener('change', function (e) {
    durationValueSpan.innerHTML = e.target.value / 1000 + ' seconds';
});

const animateLabel = document.getElementById('animateLabel');
const animateValue = document.getElementById('animate');
animateValue.addEventListener('change', function (e) {
    animateLabel.innerHTML = e.target.checked ? 'Yes' : 'No';
});

const map = new Map({
    container: 'map',
    style: MapStyle.STREETS,
    center: [-95, 40],
    zoom: 3
});

map.on('load', function () {
    // add a layer to display the map's center point
    map.addSource('center', {
        'type': 'geojson',
        'data': {
            'type': 'Point',
            'coordinates': [-94, 40]
        }
    });
    map.addLayer({
        'id': 'center',
        'type': 'symbol',
        'source': 'center',
        'layout': {
            'icon-image': 'marker-15',
            'text-field': 'Center: [-94, 40]',
            'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],
            'text-offset': [0, 0.6],
            'text-anchor': 'top'
        }
    });

    const animateButton = document.getElementById('animateButton');
    animateButton.addEventListener('click', function () {
        const easingInput = document.getElementById('easing');
        const easingFn =
            easingFunctions[
                easingInput.options[easingInput.selectedIndex].value
            ];
        const duration = parseInt(durationInput.value, 10);
        const animate = animateValue.checked;
        const offsetX = parseInt(
            document.getElementById('offset-x').value,
            10
        );
        const offsetY = parseInt(
            document.getElementById('offset-y').value,
            10
        );

        const animationOptions = {
            duration: duration,
            easing: easingFn,
            offset: [offsetX, offsetY],
            animate: animate,
            essential: true // animation will happen even if user has `prefers-reduced-motion` setting on
        };

        // Create a random location to fly to by offsetting the map's
        // initial center point by up to 10 degrees.
        const center = [
            -95 + (Math.random() - 0.5) * 20,
            40 + (Math.random() - 0.5) * 20
        ];

        // merge animationOptions with other flyTo options
        animationOptions.center = center;

        map.flyTo(animationOptions);
        // update 'center' source and layer to show our new map center
        // compare this center point to where the camera ends up when an offset is applied
        map.getSource('center').setData({
            'type': 'Point',
            'coordinates': center
        });
        map.setLayoutProperty(
            'center',
            'text-field',
            'Center: [' +
                center[0].toFixed(1) +
                ', ' +
                center[1].toFixed(1) +
                ']'
        );
    });
});
```

#### index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="robots" content="noindex">
  <title>Customize camera animations | NPM example</title>
  <link rel="stylesheet" href="./style.css">
</head>
<body>
  <style>
  .map-overlay {
      font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
      position: absolute;
      width: 200px;
      top: 0;
      left: 0;
      padding: 10px;
  }

  .map-overlay .map-overlay-inner {
      background-color: #fff;
      box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
      border-radius: 3px;
      padding: 10px;
      margin-bottom: 10px;
  }

  .map-overlay-inner fieldset {
      border: none;
      padding: 0;
      margin: 0 0 10px;
  }

  .map-overlay-inner fieldset:last-child {
      margin: 0;
  }

  .map-overlay-inner select {
      width: 100%;
  }

  .map-overlay-inner p {
      margin: 0;
  }

  .map-overlay-inner label {
      display: block;
      font-weight: bold;
  }

  .map-overlay-inner button {
      background-color: cornflowerblue;
      color: white;
      border-radius: 5px;
      display: inline-block;
      height: 20px;
      border: none;
      cursor: pointer;
  }

  .map-overlay-inner button:focus {
      outline: none;
  }

  .map-overlay-inner button:hover {
      background-color: blue;
      box-shadow: inset 0 0 0 3px rgba(0, 0, 0, 0.1);
      -webkit-transition: background-color 500ms linear;
      -ms-transition: background-color 500ms linear;
      transition: background-color 500ms linear;
  }

  .offset > label,
  .offset > input {
      display: inline;
  }

  #animateLabel {
      display: inline-block;
      min-width: 20px;
  }
  </style>

  <div id="map"></div>
  <div class="map-overlay top">
  <div class="map-overlay-inner">
      <fieldset>
          <label>Select easing function</label>
          <select id="easing" name="easing">
              <option value="easeInCubic">Ease In Cubic</option>
              <option value="easeOutQuint">Ease Out Quint</option>
              <option value="easeInOutCirc">Ease In/Out Circ</option>
              <option value="easeOutBounce">Ease Out Bounce</option>
          </select>
      </fieldset>
      <fieldset>
          <label for="duration">Set animation duration</label>
          <p id="durationValue"></p>
          <input
              type="range"
              id="duration"
              name="duration"
              min="0"
              max="10000"
              step="500"
              value="1000"
          />
      </fieldset>
      <fieldset>
          <label>Animate camera motion</label>
          <label for="animate" id="animateLabel">Yes</label>
          <input type="checkbox" id="animate" name="animate" checked />
      </fieldset>
      <fieldset class="offset">
          <label for="offset-x">Offset-X</label>
          <input
              type="number"
              id="offset-x"
              name="offset-x"
              min="-200"
              max="200"
              step="50"
              value="0"
          />
      </fieldset>
      <fieldset class="offset">
          <label for="offset-y">Offset-Y</label>
          <input
              type="number"
              id="offset-y"
              name="offset-y"
              min="-200"
              max="200"
              step="50"
              value="0"
          />
          <p>Offsets can be negative</p>
      </fieldset>
      <button type="button" id="animateButton" name="test-animation">
          Test Animation
      </button>
  </div>
  </div>
  <script type="module" src="./main.js"></script>
</body>
</html>
```

#### style.css

```css
body {margin: 0; padding: 0;}
#map {position: absolute; top: 0; bottom: 0; width: 100%;}
```

### Basic JavaScript setup

```html
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Customize camera animations</title>
  <script src="https://cdn.maptiler.com/maptiler-sdk-js/v4.2.0/maptiler-sdk.umd.min.js"></script>
  <link href="https://cdn.maptiler.com/maptiler-sdk-js/v4.2.0/maptiler-sdk.css" rel="stylesheet" />
  <style>
    body {
      margin: 0;
      padding: 0;
    }

    #map {
      position: absolute;
      top: 0;
      bottom: 0;
      width: 100%;
    }

    .map-overlay {
      font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
      position: absolute;
      width: 200px;
      top: 0;
      left: 0;
      padding: 10px;
    }

    .map-overlay .map-overlay-inner {
      background-color: #fff;
      box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
      border-radius: 3px;
      padding: 10px;
      margin-bottom: 10px;
    }

    .map-overlay-inner fieldset {
      border: none;
      padding: 0;
      margin: 0 0 10px;
    }

    .map-overlay-inner fieldset:last-child {
      margin: 0;
    }

    .map-overlay-inner select {
      width: 100%;
    }

    .map-overlay-inner p {
      margin: 0;
    }

    .map-overlay-inner label {
      display: block;
      font-weight: bold;
    }

    .map-overlay-inner button {
      background-color: cornflowerblue;
      color: white;
      border-radius: 5px;
      display: inline-block;
      height: 20px;
      border: none;
      cursor: pointer;
    }

    .map-overlay-inner button:focus {
      outline: none;
    }

    .map-overlay-inner button:hover {
      background-color: blue;
      box-shadow: inset 0 0 0 3px rgba(0, 0, 0, 0.1);
      -webkit-transition: background-color 500ms linear;
      -ms-transition: background-color 500ms linear;
      transition: background-color 500ms linear;
    }

    .offset>label,
    .offset>input {
      display: inline;
    }

    #animateLabel {
      display: inline-block;
      min-width: 20px;
    }
  </style>
</head>

<body>
  <div id="map"></div>
  <div class="map-overlay top">
    <div class="map-overlay-inner">
      <fieldset>
        <label>Select easing function</label>
        <select id="easing" name="easing">
          <option value="easeInCubic">Ease In Cubic</option>
          <option value="easeOutQuint">Ease Out Quint</option>
          <option value="easeInOutCirc">Ease In/Out Circ</option>
          <option value="easeOutBounce">Ease Out Bounce</option>
        </select>
      </fieldset>
      <fieldset>
        <label for="duration">Set animation duration</label>
        <p id="durationValue"></p>
        <input type="range" id="duration" name="duration" min="0" max="10000" step="500" value="1000" />
      </fieldset>
      <fieldset>
        <label>Animate camera motion</label>
        <label for="animate" id="animateLabel">Yes</label>
        <input type="checkbox" id="animate" name="animate" checked />
      </fieldset>
      <fieldset class="offset">
        <label for="offset-x">Offset-X</label>
        <input type="number" id="offset-x" name="offset-x" min="-200" max="200" step="50" value="0" />
      </fieldset>
      <fieldset class="offset">
        <label for="offset-y">Offset-Y</label>
        <input type="number" id="offset-y" name="offset-y" min="-200" max="200" step="50" value="0" />
        <p>Offsets can be negative</p>
      </fieldset>
      <button type="button" id="animateButton" name="test-animation">
        Test Animation
      </button>
    </div>
  </div>

  <script>
    maptilersdk.config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';
    // declare various easing functions.
    // easing functions mathematically describe
    // how fast a value changes during an animation.
    // each function takes a parameter t that represents
    // the progress of the animation.
    // t is in a range of 0 to 1 where 0 is the initial
    // state and 1 is the completed state.
    const easingFunctions = {
      // start slow and gradually increase speed
      easeInCubic: function (t) {
        return t * t * t;
      },
      // start fast with a long, slow wind-down
      easeOutQuint: function (t) {
        return 1 - Math.pow(1 - t, 5);
      },
      // slow start and finish with fast middle
      easeInOutCirc: function (t) {
        return t < 0.5
          ? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2
          : (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2;
      },
      // fast start with a "bounce" at the end
      easeOutBounce: function (t) {
        const n1 = 7.5625;
        const d1 = 2.75;

        if (t < 1 / d1) {
          return n1 * t * t;
        } else if (t < 2 / d1) {
          return n1 * (t -= 1.5 / d1) * t + 0.75;
        } else if (t < 2.5 / d1) {
          return n1 * (t -= 2.25 / d1) * t + 0.9375;
        } else {
          return n1 * (t -= 2.625 / d1) * t + 0.984375;
        }
      }
    };

    // set up some helpful UX on the form
    const durationValueSpan = document.getElementById('durationValue');
    const durationInput = document.getElementById('duration');
    durationValueSpan.innerHTML = durationInput.value / 1000 + ' seconds';
    durationInput.addEventListener('change', function (e) {
      durationValueSpan.innerHTML = e.target.value / 1000 + ' seconds';
    });

    const animateLabel = document.getElementById('animateLabel');
    const animateValue = document.getElementById('animate');
    animateValue.addEventListener('change', function (e) {
      animateLabel.innerHTML = e.target.checked ? 'Yes' : 'No';
    });

    const map = new maptilersdk.Map({
      container: 'map',
      style: maptilersdk.MapStyle.STREETS,
      center: [-95, 40],
      zoom: 3
    });

    map.on('load', function () {
      // add a layer to display the map's center point
      map.addSource('center', {
        'type': 'geojson',
        'data': {
          'type': 'Point',
          'coordinates': [-94, 40]
        }
      });
      map.addLayer({
        'id': 'center',
        'type': 'symbol',
        'source': 'center',
        'layout': {
          'icon-image': 'marker-15',
          'text-field': 'Center: [-94, 40]',
          'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],
          'text-offset': [0, 0.6],
          'text-anchor': 'top'
        }
      });

      const animateButton = document.getElementById('animateButton');
      animateButton.addEventListener('click', function () {
        const easingInput = document.getElementById('easing');
        const easingFn =
          easingFunctions[
          easingInput.options[easingInput.selectedIndex].value
          ];
        const duration = parseInt(durationInput.value, 10);
        const animate = animateValue.checked;
        const offsetX = parseInt(
          document.getElementById('offset-x').value,
          10
        );
        const offsetY = parseInt(
          document.getElementById('offset-y').value,
          10
        );

        const animationOptions = {
          duration: duration,
          easing: easingFn,
          offset: [offsetX, offsetY],
          animate: animate,
          essential: true // animation will happen even if user has `prefers-reduced-motion` setting on
        };

        // Create a random location to fly to by offsetting the map's
        // initial center point by up to 10 degrees.
        const center = [
          -95 + (Math.random() - 0.5) * 20,
          40 + (Math.random() - 0.5) * 20
        ];

        // merge animationOptions with other flyTo options
        animationOptions.center = center;

        map.flyTo(animationOptions);
        // update 'center' source and layer to show our new map center
        // compare this center point to where the camera ends up when an offset is applied
        map.getSource('center').setData({
          'type': 'Point',
          'coordinates': center
        });
        map.setLayoutProperty(
          'center',
          'text-field',
          'Center: [' +
          center[0].toFixed(1) +
          ', ' +
          center[1].toFixed(1) +
          ']'
        );
      });
    });
  </script>
</body>

</html>
```

## Related examples

### [Navigate the map with game-like controls](https://docs.maptiler.com/sdk-js/examples/game-controls/)

Examples

Navigate map using keyboard arrow keys with game controls.

### [Creates an animated map to fly between different locations](https://docs.maptiler.com/sdk-js/examples/heatmap-animated/)

Examples

Animate map flights between locations using a heatmap layer.

### [Fly to a location based on the scroll position](https://docs.maptiler.com/sdk-js/examples/scroll-fly-to/)

Examples

Fly to locations on the map by scrolling.
