全てのドキュメントMapbox GL JS周囲の音に基づき 3D 建物をアニメーション化

周囲の音に基づき 3D 建物をアニメーション化

runtime styling API を Web Audio API と接続し、3D 建物が周囲環境に適応するマップを作成します。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>周囲の音に基づき 3D 建物をアニメーション化</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v2.6.1/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v2.6.1/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
// TO MAKE THE MAP APPEAR YOU MUST
// ADD YOUR ACCESS TOKEN FROM
// https://account.mapbox.com
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
// Use a minimal variant of the Mapbox Dark style, with certain features removed.
const map = new mapboxgl.Map({
style: 'mapbox://styles/examples/cj68bstx01a3r2rndlud0pwpv',
center: {
lng: -74.0064,
lat: 40.7081
},
zoom: 15,
pitch: 55,
container: 'map',
antialias: true
});
map.addControl(new mapboxgl.FullscreenControl());
map.on('load', () => {
const bins = 16;
const maxHeight = 200;
const binWidth = maxHeight / bins;
// Divide the buildings into 16 bins based on their true height, using a layer filter.
for (let i = 0; i < bins; i++) {
map.addLayer({
'id': `3d-buildings-${i}`,
'source': 'composite',
'source-layer': 'building',
'filter': [
'all',
['==', 'extrude', 'true'],
['>', 'height', i * binWidth],
['<=', 'height', (i + 1) * binWidth]
],
'type': 'fill-extrusion',
'minzoom': 15,
'paint': {
'fill-extrusion-color': '#aaa',
'fill-extrusion-height-transition': {
duration: 0,
delay: 0
},
'fill-extrusion-opacity': 0.6
}
});
}
// Older browsers might not implement mediaDevices at all, so we set an empty object first
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
// Some browsers partially implement mediaDevices. We can't just assign an object
// with getUserMedia as it would overwrite existing properties.
// Here, we will just add the getUserMedia property if it's missing.
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = (constraints) => {
// First get ahold of the legacy getUserMedia, if present
const getUserMedia =
navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// Some browsers just don't implement it - return a rejected promise with an error
// to keep a consistent interface
if (!getUserMedia) {
return Promise.reject(
new Error(
'getUserMedia is not implemented in this browser'
)
);
}
// Otherwise, wrap the call to the old navigator.getUserMedia with a Promise
return new Promise((resolve, reject) => {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
navigator.mediaDevices
.getUserMedia({ audio: true })
.then((stream) => {
// Set up a Web Audio AudioContext and AnalyzerNode, configured to return the
// same number of bins of audio frequency data.
const audioCtx = new (window.AudioContext ||
window.webkitAudioContext)();
const analyser = audioCtx.createAnalyser();
analyser.minDecibels = -90;
analyser.maxDecibels = -10;
analyser.smoothingTimeConstant = 0.85;
const source = audioCtx.createMediaStreamSource(stream);
source.connect(analyser);
analyser.fftSize = bins * 2;
const dataArray = new Uint8Array(bins);
function draw(now) {
analyser.getByteFrequencyData(dataArray);
// Use that data to drive updates to the fill-extrusion-height property.
let avg = 0;
for (let i = 0; i < bins; i++) {
avg += dataArray[i];
map.setPaintProperty(
`3d-buildings-${i}`,
'fill-extrusion-height',
10 + 4 * i + dataArray[i]
);
}
avg /= bins;
// Animate the map bearing and light color over time, and make the light more
// intense when the audio is louder.
map.setBearing(now / 500);
const hue = (now / 100) % 360;
const saturation = Math.min(50 + avg / 4, 100);
map.setLight({
color: `hsl(${hue},${saturation}%,50%)`,
intensity: Math.min(1, (avg / 256) * 10)
});
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
})
.catch((err) => {
console.log('The following gUM error occurred:', err);
});
});
</script>
</body>
</html>