Skip to content
Customize Camera Animations

Customize Camera Animations

Camera animations in Maptoolkit Maps JS accept an AnimationOptions object that controls duration, easing function, and whether the animation can be interrupted. You can pass a custom easing function to create spring-like, bouncy, or decelerated motion instead of the default ease-in-out curve. Use this to match the feel of your application, create dramatic fly-in effects, or implement consistent animation timing across all map interactions.

const API_KEY = 'YOUR_API_KEY';

    // 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(t) {
            return t * t * t;
        },
        // Start fast with a long, slow wind-down
        easeOutQuint(t) {
            return 1 - Math.pow(1 - t, 5);
        },
        // Slow start and finish with fast middle
        easeInOutCirc(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(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', (e) => {
        durationValueSpan.innerHTML = `${e.target.value / 1000} seconds`;
    });

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

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        center: [-95, 40],
        zoom: 3,
        attributionControl: { compact: false }
    });

    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    map.on('load', () => {
        // 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: {
                'text-field': 'Center: [-94, 40]',
                'text-offset': [0, 0.6],
                'text-anchor': 'top'
            }
        });

        map.addLayer({
            id: 'center-circle',
            type: 'circle',
            source: 'center',
            paint: {
                'circle-radius': 6,
                'circle-color': '#007cbf'
            }
        });

        const animateButton = document.getElementById('animateButton');
        animateButton.addEventListener('click', () => {
            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,
                easing: easingFn,
                offset: [offsetX, offsetY],
                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)}]`
            );
        });
    });
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Customize Camera Animations - Maptoolkit Maps JS</title>
    <meta property="og:description" content="Customize camera animations using AnimationOptions." />
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://unpkg.com/@maptoolkit/[email protected]/dist/maptoolkit.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/@maptoolkit/[email protected]/dist/maptoolkit.css" />
    <style>
        html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
        #map { width: 100%; height: 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 for="easing">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>
    const API_KEY = 'YOUR_API_KEY';

    // 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(t) {
            return t * t * t;
        },
        // Start fast with a long, slow wind-down
        easeOutQuint(t) {
            return 1 - Math.pow(1 - t, 5);
        },
        // Slow start and finish with fast middle
        easeInOutCirc(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(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', (e) => {
        durationValueSpan.innerHTML = `${e.target.value / 1000} seconds`;
    });

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

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        center: [-95, 40],
        zoom: 3,
        attributionControl: { compact: false }
    });

    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    map.on('load', () => {
        // 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: {
                'text-field': 'Center: [-94, 40]',
                'text-offset': [0, 0.6],
                'text-anchor': 'top'
            }
        });

        map.addLayer({
            id: 'center-circle',
            type: 'circle',
            source: 'center',
            paint: {
                'circle-radius': 6,
                'circle-color': '#007cbf'
            }
        });

        const animateButton = document.getElementById('animateButton');
        animateButton.addEventListener('click', () => {
            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,
                easing: easingFn,
                offset: [offsetX, offsetY],
                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>

Use the prompt below with any LLM to get the same result. Make sure the Maptoolkit MCP server is connected first — check out AI Integration & MCP to get started.

Use the Maptoolkit Connector. Create an interactive map with zoom level 3, centered around [-95, 40]. Add a UI panel with controls for easing function, animation duration, animate toggle, and offset, with a button that flies to a random location using the selected settings.