UNL Map JSUNL Map JS DocsExamplesCustomize camera animations

Customize camera animations

Customize camera animations using AnimationOptions.

Note
If you want to test this example, edit it in JSFiddle or CodePen and replace the "YOUR-OWN-API-KEY" and "YOUR-OWN-VPM-ID" placeholders with your actual api key and vpm id.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Customize camera animations</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://unpkg.com/unl-map-js@0.1.5/lib/unl-map-js.js"></script>
<link href="https://unpkg.com/unl-map-js@0.1.5/lib/unl-map-js.css" rel="stylesheet" />
<link
href="https://unpkg.com/maplibre-gl@2.1.9/dist/maplibre-gl.css"
rel="stylesheet"
/>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</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>
// 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.
var 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) {
var n1 = 7.5625;
var 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
var durationValueSpan = document.getElementById("durationValue");
var durationInput = document.getElementById("duration");
durationValueSpan.innerHTML = durationInput.value / 1000 + " seconds";
durationInput.addEventListener("change", function (e) {
durationValueSpan.innerHTML = e.target.value / 1000 + " seconds";
});
var animateLabel = document.getElementById("animateLabel");
var animateValue = document.getElementById("animate");
animateValue.addEventListener("change", function (e) {
animateLabel.innerHTML = e.target.checked ? "Yes" : "No";
});
var map = new UnlSdk.Map({
container: "map",
apiKey: "YOUR-OWN-API-KEY",
vpmId: "YOUR-OWN-VPM-ID",
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": ["Fira GO Regular"],
"text-offset": [0, 0.6],
"text-anchor": "top",
},
});
var animateButton = document.getElementById("animateButton");
animateButton.addEventListener("click", function () {
var easingInput = document.getElementById("easing");
var easingFn =
easingFunctions[easingInput.options[easingInput.selectedIndex].value];
var duration = parseInt(durationInput.value, 10);
var animate = animateValue.checked;
var offsetX = parseInt(document.getElementById("offset-x").value, 10);
var offsetY = parseInt(document.getElementById("offset-y").value, 10);
var 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.
var 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>