View Transition
Runnable deck.gl example: View Transition. Open the live demo, read the complete source, and copy the file into your project. No build step or API key.
single HTML file · CDN dependencies onlyinterface may contain Chinese labels
Source
<!--
示例:deck.gl 视图过渡动画
要点:插值 viewState 的经纬度、缩放、俯仰与方位角,在几座城市之间平滑飞行
依赖:deck.gl 9.0 · jsDelivr CDN
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta name="robots" content="noindex,follow">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>视图过渡动画</title>
<style>
html,body{height:100%;margin:0;background:#0d1117}
body{display:flex;flex-direction:column;overflow:hidden;
font:13px/1.6 -apple-system,"Segoe UI","PingFang SC",sans-serif;color:#e6e8ec}
.bar{position:relative;z-index:5;flex:0 0 auto;padding:8px 12px;background:#161b22;border-bottom:1px solid #252c36;
display:flex;gap:8px;align-items:center;font-size:12px;color:#8b93a1;flex-wrap:wrap}
.bar b{color:#58a6ff;font-weight:600}
.bar button{font:inherit;font-size:12px;padding:3px 10px;border-radius:5px;cursor:pointer;
border:1px solid #303845;background:#1d2531;color:#c8d2e0}
.bar button:hover{background:#253040}
.bar button.on{background:#2c4a7a;border-color:#4f8ad6;color:#dfe9ff}
.bar .sep{width:1px;height:18px;background:#2a313c;margin:0 4px}
.progress{position:relative;flex:0 0 140px;height:4px;background:#242c38;border-radius:2px;
overflow:hidden}
.progress i{position:absolute;left:0;top:0;bottom:0;width:0;background:#4f9ef8}
#cv{flex:1 1 auto;min-height:0;width:100%}
</style>
</head>
<body>
<div class="bar">
<span>目标 <b id="name">全国</b></span>
<span class="sep"></span>
<button data-idx="0" class="on">全国</button>
<button data-idx="1">北京</button>
<button data-idx="2">上海</button>
<button data-idx="3">成都</button>
<button data-idx="4">广州</button>
<span class="progress"><i id="bar"></i></span>
<span>飞行中暂停拖拽 · 结束后可自由操作</span>
</div>
<canvas id="cv"></canvas>
<script src="https://cdn.jsdelivr.net/npm/deck.gl@9.0.35/dist.min.js"></script>
<script>
(function () {
'use strict';
if (typeof deck === 'undefined') {
document.body.innerHTML =
'<div style="padding:24px;color:#ff8c74">deck.gl 加载失败,请检查能否访问 cdn.jsdelivr.net</div>';
return;
}
var targets = [
{ name: '全国', longitude: 108.5, latitude: 32.8, zoom: 3.9, pitch: 20, bearing: 0 },
{ name: '北京', longitude: 116.39, latitude: 39.90, zoom: 9.8, pitch: 48, bearing: -12 },
{ name: '上海', longitude: 121.47, latitude: 31.23, zoom: 10.2, pitch: 55, bearing: -24 },
{ name: '成都', longitude: 104.06, latitude: 30.57, zoom: 10.0, pitch: 50, bearing: 22 },
{ name: '广州', longitude: 113.26, latitude: 23.12, zoom: 10.3, pitch: 52, bearing: 8 }
];
var clusters = [
{ name: 'BEIJING', lng: 116.39, lat: 39.90, color: [255, 150, 90, 210] },
{ name: 'SHANGHAI', lng: 121.47, lat: 31.23, color: [90, 180, 255, 210] },
{ name: 'CHENGDU', lng: 104.06, lat: 30.57, color: [120, 220, 170, 210] },
{ name: 'GUANGZHOU', lng: 113.26, lat: 23.12, color: [200, 160, 255, 210] }
];
var points = [];
for (var c = 0; c < clusters.length; c++) {
var cl = clusters[c];
for (var i = 0; i < 5000; i++) {
var r = Math.sqrt(Math.random()) * 0.55;
var a = Math.random() * Math.PI * 2;
points.push({
lng: cl.lng + Math.cos(a) * r,
lat: cl.lat + Math.sin(a) * r * 0.8,
color: cl.color,
v: Math.random()
});
}
}
var pointLayer = new deck.ScatterplotLayer({
id: 'points',
data: points,
getPosition: function (d) { return [d.lng, d.lat]; },
getRadius: 2.6,
radiusUnits: 'pixels',
radiusMinPixels: 1.4,
radiusMaxPixels: 5,
getFillColor: function (d) { return d.color; },
pickable: false
});
var labelLayer = new deck.TextLayer({
id: 'cluster-labels',
data: clusters,
getText: function (d) { return d.name; },
getPosition: function (d) { return [d.lng, d.lat]; },
getSize: 14,
sizeUnits: 'pixels',
getColor: [222, 234, 250, 255],
getPixelOffset: [0, -20],
getTextAnchor: 'middle',
getAlignmentBaseline: 'center',
characterSet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ',
background: true,
getBackgroundColor: [10, 18, 30, 205],
backgroundPadding: [4, 2],
outlineWidth: 0.08,
outlineColor: [8, 14, 26, 210],
billboard: true
});
var view = targets[0];
var deckgl = new deck.Deck({
canvas: 'cv',
width: '100%',
height: '100%',
viewState: view,
controller: true,
onViewStateChange: function (e) {
view = e.viewState;
deckgl.setProps({ viewState: view });
},
layers: [pointLayer, labelLayer]
});
var flying = false;
var progress = document.getElementById('bar');
var nameLabel = document.getElementById('name');
function easeInOut(t) {
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
}
function lerp(a, b, t) { return a + (b - a) * t; }
function flyTo(idx, btn) {
if (flying) return;
var end = targets[idx];
var start = {
longitude: view.longitude,
latitude: view.latitude,
zoom: view.zoom,
pitch: view.pitch || 0,
bearing: view.bearing || 0
};
var t0 = performance.now();
var span = 1500;
flying = true;
nameLabel.textContent = end.name;
deckgl.setProps({ controller: false });
var buttons = document.querySelectorAll('[data-idx]');
for (var i = 0; i < buttons.length; i++) {
buttons[i].classList.toggle('on', buttons[i] === btn);
}
function step(now) {
var t = Math.min(1, (now - t0) / span);
var k = easeInOut(t);
var next = {
longitude: lerp(start.longitude, end.longitude, k),
latitude: lerp(start.latitude, end.latitude, k),
zoom: lerp(start.zoom, end.zoom, k),
pitch: lerp(start.pitch, end.pitch, k),
bearing: lerp(start.bearing, end.bearing, k)
};
view = next;
deckgl.setProps({ viewState: next });
progress.style.width = Math.round(t * 100) + '%';
if (t < 1) {
requestAnimationFrame(step);
} else {
flying = false;
view = {
longitude: end.longitude,
latitude: end.latitude,
zoom: end.zoom,
pitch: end.pitch,
bearing: end.bearing
};
deckgl.setProps({ viewState: view, controller: true });
}
}
requestAnimationFrame(step);
}
var buttons = document.querySelectorAll('[data-idx]');
for (var b = 0; b < buttons.length; b++) {
buttons[b].addEventListener('click', function (e) {
flyTo(Number(e.currentTarget.getAttribute('data-idx')), e.currentTarget);
});
}
window.__deck = deckgl;
})();
</script>
</body>
</html>
Language: the code and this page are in English; the full tutorial and API notes for this example are in Chinese. The demo itself is the authoritative version — it runs exactly as shown.