聲音(Audio)

更多有趣示例 盡在小紅磚社區

示例

在這裏插入圖片描述

HTML

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <meta name="theme-color" content="#ff24a3">
    <meta name="application-name" content="Ready Player One">
    <meta name="description" content="Threejs Audio Visualizer #6">
    <meta name="keywords" content="Ready Player One, SynthWave, Threejs, Javascript, Ion Drimba Filho, WebAudio, Visualizer">
    <meta name="subject" content="Threejs Audio Visualizer #6">
    <meta name="copyright" content="Ion Drimba Filho">
    <meta name="robots" content="index,follow">
    <meta name="topic" content="">
    <meta name="summary" content="ThreeJs Audio Visualizer #6">
    <meta name="author" content="Ion Drimba Filho">
    <meta name="url" content="http://iondrimba.github.io/threejs-audio-visualizer-six/public/index.html">
    <meta name="pagename" content="Ready Player One">
    <meta name="category" content="">
    <meta name="coverage" content="Worldwide">
    <meta name="distribution" content="Global">
    <meta name="rating" content="General">
    <meta name="subtitle" content="ThreeJs Audio Visualizer #6">
    <meta name="target" content="all">
    <meta http-equiv="cleartype" content="on">
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:site" content="http://iondrimba.github.io/threejs-audio-visualizer-six/public/index.html">
    <meta name="twitter:creator" content="Ion Drimba Filho">
    <meta name="twitter:title" content="Ready Player One">
    <meta name="twitter:description" content="ThreeJs Audio Visualizer #6">
    <meta name="twitter:image:src" content="https://raw.githubusercontent.com/iondrimba/images/master/six.PNG">
    <meta property="og:url" content="http://iondrimba.github.io/threejs-audio-visualizer-six/public/index.html">
    <meta property="og:type" content="website">
    <meta property="og:title" content="Ready Player One">
    <meta property="og:image" content="https://raw.githubusercontent.com/iondrimba/images/master/six.PNG">
    <meta property="og:description" content="ThreeJs Audio Visualizer #6">
    <meta property="og:site_name" content="Ready Player One">
    <meta property="article:author" content="https://iondrimbafilho.me/">
    <meta property="article:publisher" content="https://iondrimbafilho.me/">
    <meta itemprop="name" content="Ready Player One">
    <meta itemprop="description" content="ThreeJs Audio Visualizer #6">
    <meta itemprop="image" content="https://raw.githubusercontent.com/iondrimba/images/master/six.PNG">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="mobile-web-app-capable" content="yes">
    <title>Ready Player One</title>
    <link href="https://fonts.googleapis.com/css?family=Ropa+Sans" rel="stylesheet">
</head>

<body>
    <button class="play-intro">
        <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 32 32"
            data-tags="play,media control">
            <g transform="scale(0.03125 0.03125)">
                <path d="M192 0v1024l640-511.264-640-512.736z" />
            </g>
        </svg>
    </button>
    <div class="credits" title="Music by Droid Bishop">
        <h1>
            <a href="https://droidbishop.bandcamp.com/album/lost-in-symmetry" target="_blank" rel="noopener noreferrer">Droid Bishop </a>/
            <a href="https://droidbishop.bandcamp.com/track/ready-player-one" target="_blank" rel="noopener noreferrer">Ready Player One</a>
        </h1>
        <div class="controls">
            <button class="play">
                <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="32" height="32" viewBox="0 0 25 32"
                    data-tags="play,media control">
                    <g transform="scale(0.03125 0.03125)">
                        <path d="M192 0v1024l640-511.264-640-512.736z" />
                    </g>
                </svg>
            </button>
            <button class="pause">
                <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32"
                    data-tags="pause,media control">
                    <g transform="scale(0.03125 0.03125)">
                        <path d="M352 0h-192c-17.696 0-32 14.336-32 32v960c0 17.696 14.304 32 32 32h192c17.696 0 32-14.304 32-32v-960c0-17.664-14.304-32-32-32zM864 0h-192c-17.696 0-32 14.336-32 32v960c0 17.696 14.304 32 32 32h192c17.696 0 32-14.304 32-32v-960c0-17.664-14.304-32-32-32z"
                        />
                    </g>
                </svg>
            </button>
        </div>
    </div>
    <div class="loader"></div>
    <audio id="audio" crossOrigin="anonymous"></audio>
</body>

</html>

CSS

html, body {
  margin: 0;
  padding: 0;
  font-family: 'Ropa Sans', sans-serif;
  background-color: #DDA0DD;
  color: #0000FF;
  box-sizing: border-box;
  overflow: hidden;
  cursor: -webkit-grab;
  cursor: -moz-grab;
}

canvas {
  width: 100%;
  height: 100%;
}

h1 {
  padding: 0;
  margin: 0;
  color: inherit;
}

button {
  background-color: transparent;
  border: 0;
  cursor: pointer;
}

.play-intro {
  position: absolute;
  top: 50%;
  left: 50%;
  display: none;
  transform: translate(-50%, -50%);
  height: 100px;
  width: 100px;
  cursor: pointer;
}
.play-intro svg {
  width: 100%;
  fill: #ffffff;
}

.controls {
  position: absolute;
  right: 30px;
  top: 10px;
}
.controls svg {
  fill: #ffffff;
}

.play, .pause {
  display: none;
}

.control-show {
  display: block;
}

.loader {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  z-index: 0;
  transform: scale(0, 1);
  transform-origin: left;
  transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1);
  background: linear-gradient(to right, #0346fd, #008cff);
}

a {
  color: inherit;
  text-decoration: none;
}

.credits {
  margin: 20px;
  position: absolute;
  z-index: 1;
  bottom: 10px;
  width: 100%;
}
.credits h1 {
  font-size: 20pt;
  width: 80%;
  line-height: 1.4;
}
.credits .controls svg {
  width: 50px;
}

.removeLoader {
  transition: transform .3s .3s ease-out;
}

@media screen and (min-width: 768px) {
  .credits {
    margin-left: 20px;
    margin-top: 20px;
    position: absolute;
    z-index: 1;
    bottom: inherit;
    width: 100%;
  }
  .credits h1 {
    font-size: 20pt;
    width: auto;
    line-height: 1.4;
  }
  .credits .controls {
    right: 50px;
  }
  .credits .controls svg {
    width: auto;
  }
}

JS

class Loader {
  constructor() {
    this.callback = null;
  }

  load(file) {
    const request = new XMLHttpRequest();

    request.open('GET', file, true);
    request.onprogress = (evt) => {
      let percent = Math.floor((evt.loaded / evt.total) * 100);

      this.callback(percent);
    };

    request.onload = () => { this.complete(file); };
    request.send();
  }

  progress(callback) { this.callback = callback; };

  complete() { }
}

const { physics, value } = window.popmotion;

class App {
  constructor() {
    this.songFile = 'https://iondrimbafilho.me/ready_player_one.mp3';
    this.percent = 0;
    this.playing = false;
    this.volume = 1;
    this.sceneBackGroundColor = 0x7FFF00;
    this.spotLightColor = 0x7FFF00;
    this.ambientLightColor = 0xADFF2F;

    this.groupSpheres = new THREE.Object3D();
    this.groupCones = new THREE.Object3D();

    this.tiles = [];

    this.loader = new Loader();
    this.loader.progress((percent) => { this.progress(percent); });
    this.loaderBar = document.querySelector('.loader');
    this.loader.load(this.songFile);
    this.playIntro = document.querySelector('.play-intro');
    this.loader.complete = this.complete.bind(this);

  }

  progress(percent) {
    this.loaderBar.style.transform = `scale(${(percent / 100) + .1}, 1.1)`;
    
    if (percent === 100) {
      setTimeout(() => {
        requestAnimationFrame(() => {
          this.playIntro.classList.add('control-show');
          this.loaderBar.classList.add('removeLoader');
          this.loaderBar.style.transform = 'scale(1, 0)';
        })
      }, 800);
    }
  }

  complete(file) {
    this.setupAudio();
    this.addSoundControls();
    this.createScene();
    this.createCamera();
    this.addAmbientLight(this.ambientLightColor);
    this.addSpotLight(new THREE.SpotLight(this.spotLightColor), 50, 400, 50);
    this.addCameraControls();

    this.playSound(file);
    this.addEventListener();

    this.addGrid();

    this.floorShape = this.createShape();
    this.createGround(this.floorShape);

    this.animate();
    
    document.addEventListener('visibilitychange', (evt) => {
      if(evt.target.hidden) {
        this.pause();
      } else {
        this.play();
      }
    }, false);
  }

  createShape() {
    const size = 200;
    const vectors = [
      new THREE.Vector2(-size, size),
      new THREE.Vector2(-size, -size),
      new THREE.Vector2(size, -size),
      new THREE.Vector2(size, size)
    ];
    const shape = new THREE.Shape(vectors);
    
    shape.autoClose = true;

    this.createHoles(shape, {
      cols: {
        start: -4,
        end: 5
      },
      rows: {
        start: -5,
        end: 0
      },
      elementCreate: this.createSphere,
      onAdd: (element, x, y, i, j) => {
        this.addPhysics(element);

        element.position.set(x - (i * 1), -.5, y - (j * 1));

        this.groupSpheres.add(element);

        this.tiles.push(element);
      }
    });

    this.createHoles(shape, {
      cols: {
        start: -4,
        end: 5
      },
      rows: {
        start: 1,
        end: 6
      },
      elementCreate: this.createCone,
      onAdd: (element, x, y, i, j) => {
        element.position.set(x - (i * 1), -10, y - (j * 1));

        this.groupCones.add(element);

        this.tiles.push(element);
      }
    });

    this.scene.add(this.groupSpheres);
    this.scene.add(this.groupCones);

    return shape;
  }

  createHoles(shape, props) {
    const radius = .5;
    const finalPos = (i) => {
      return (-i * 1);
    };

    for (let i = props.cols.start; i < props.cols.end; i++) {
      for (let j = props.rows.start; j < props.rows.end; j++) {
        const holePath = new THREE.Path();
        const x = finalPos(i);
        const y = finalPos(j);

        holePath.moveTo(x, y);
        holePath.ellipse(x, y, radius, radius, 0, Math.PI * 2);
        holePath.autoClose = true;
        shape.holes.push(holePath);

        props.onAdd(props.elementCreate(), x, y, i, j);
      }
    }
  }

  createGround(shape) {
    const materials = [
      new THREE.MeshStandardMaterial({
        color: 0xff1876,
        roughness: 1,
        metalness: 0.1,
        flatShading: THREE.FlatShading
      }),
      new THREE.MeshStandardMaterial({
        color: 0xf324ff,
        flatShading: THREE.FlatShading
      })
    ];

    const props = {
      steps: 1,
      depth: 4,
      bevelEnabled: false
    };

    const geometry = new THREE.ExtrudeGeometry(shape, props);
    const bufferGeometry = new THREE.BufferGeometry().fromGeometry(geometry);
    const mesh = new THREE.Mesh(geometry, materials);
    
    mesh.rotation.set(Math.PI * 0.5, 0, 0);
    
    this.scene.add(mesh);
  }

  drawWave() {
    let velocity = 0;
    let freq = 0;
    let scale = 0;

    if (this.playing) {
      this.analyser.getByteFrequencyData(this.frequencyData);

      for (let i = 0; i < this.frequencyData.length; i++) {
        freq = this.frequencyData[i];

        if (this.tiles[i] && this.tiles[i].hasPhysics && !this.tiles[i].falling) {
          velocity = this.map(freq, 0, 255, -50, 80);
          
          this.tiles[i].gravity
            .set(Math.min(0, this.tiles[i].posY.get()))
            .setVelocity(-(velocity));

          this.tiles[i].falling = true;
        }

        if (this.tiles[i] && !this.tiles[i].hasPhysics) {
          scale = this.map(freq, 0, 255, 0.001, 2);
          
          TweenMax.to(this.tiles[i].scale, .4, { y: scale });
        }
      }
    }
  }

  createSphere() {
    const geometry = new THREE.SphereGeometry(.5, 16, 16);
    const material = new THREE.MeshPhongMaterial({
      color: 0x3a88ff,
      specular: 0xffffff,
      shininess: 2,
      emissive: 0x0b57b9,
      side: THREE.DoubleSide
    });


    let sphere = new THREE.Mesh(geometry, material);
    
    sphere.position.y = 0;
    sphere.castShadow = true;
    sphere.receiveShadow = true;

    return sphere;
  }

  addPhysics(element) {
    const posY = value(10, (v) => {
      element.position.y = -Math.min(10, v);
    });
    element.posY = posY;
    element.falling = false;
    element.hasPhysics = true;

    const gravity = physics({
      acceleration: 250,
      restSpeed: false
    }).start((v) => {
      if (element.falling) {
        if (v < 10) {
          posY.update(v);
        } else {
          element.falling = false;
        }
      }
    });

    element.gravity = gravity;
  }

  createCone() {
    const geometry = new THREE.CylinderGeometry(.4, .4, 10, 59);
    const material = new THREE.MeshPhongMaterial({
      color: 0x12ff31,
      flatShading: THREE.FlatShading,
      side: THREE.DoubleSide
    });

    const obj = new THREE.Mesh(geometry, material);
    
    obj.castShadow = true;
    obj.receiveShadow = true;
    obj.position.y = 5;

    const pivot = new THREE.Object3D();
    
    pivot.add(obj);
    pivot.size = 2;
    
    return pivot;
  }

  addSpotLight(spotLight, x, y, z) {
    this.spotLight = spotLight;
    this.spotLight.position.set(x, y, z);
    this.spotLight.castShadow = true;

    this.scene.add(this.spotLight);
  }

  addAmbientLight(color) {
    const light = new THREE.AmbientLight(color, .5);
    
    this.scene.add(light);
  }

  addGrid() {
    const size = 25;
    const divisions = size;
    const gridHelper = new THREE.GridHelper(size, divisions);
    
    gridHelper.position.set(0, 0, 0);
    gridHelper.material.opacity = 0;
    gridHelper.material.transparent = true;
    
    this.scene.add(gridHelper);
  }

  playSound(file) {
    this.audioElement.src = file;
  }

  map(value, start1, stop1, start2, stop2) {
    return (value - start1) / (stop1 - start1) * (stop2 - start2) + start2
  }

  addSoundControls() {
    this.btnPlay = document.querySelector('.play');
    this.btnPause = document.querySelector('.pause');

    this.btnPlay.addEventListener('click', this.play.bind(this));
    this.btnPause.addEventListener('click', this.pause.bind(this));
  }

  pause() {
    this.audioElement.pause();
    this.btnPause.classList.remove('control-show');
    this.btnPlay.classList.add('control-show');
  }

  play() {
    this.audioCtx.resume();
    this.audioElement.play();
    this.btnPlay.classList.remove('control-show');
    this.btnPause.classList.add('control-show');
  }

  createScene() {
    this.scene = new THREE.Scene();
    this.scene.background = new THREE.Color(this.sceneBackGroundColor);

    this.renderer = new THREE.WebGLRenderer({ antialias: true });
    this.renderer.setSize(window.innerWidth, window.innerHeight);

    this.scene1 = new THREE.Scene();

    this.renderer.shadowMap.enabled = true;
    this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;

    document.body.appendChild(this.renderer.domElement);
  }

  addEventListener() {
    this.playIntro.addEventListener('click', (evt) => {
      evt.currentTarget.classList.remove('control-show');
      this.play();
    });

    document.body.addEventListener('mouseup', () => {
      requestAnimationFrame(() => {
        document.body.style.cursor = '-moz-grab';
        document.body.style.cursor = '-webkit-grab';
      });
    });

    document.body.addEventListener('mousedown', () => {
      requestAnimationFrame(() => {
        document.body.style.cursor = '-moz-grabbing';
        document.body.style.cursor = '-webkit-grabbing';
      });
    });

    document.body.addEventListener('keyup', (evt) => {
      if (evt.keyCode === 32 || evt.code === 'Space') {
        this.playIntro.classList.remove('control-show');
        this.playing ? this.pause() : this.play();
      }
    });
  }

  createCamera() {
    this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
    this.camera.position.set(26, 23, -17);
    this.scene.add(this.camera);
  }

  addCameraControls() {
    this.controls = new THREE.OrbitControls(this.camera);
  }

  animate() {
    this.controls.update();

    this.renderer.render(this.scene, this.camera);

    this.drawWave();

    requestAnimationFrame(this.animate.bind(this));
  }

  radians(degrees) {
    return degrees * Math.PI / 180;
  }

  onResize() {
    const ww = window.innerWidth;
    const wh = window.innerHeight;
    
    this.camera.aspect = ww / wh;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(ww, wh);
  }

  setupAudio() {
    this.audioElement = document.getElementById('audio');
    this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();

    this.analyser = this.audioCtx.createAnalyser();
    this.analyser.smoothingTimeConstant = 0.4;

    this.source = this.audioCtx.createMediaElementSource(this.audioElement);
    this.source.connect(this.analyser);
    this.source.connect(this.audioCtx.destination);

    this.bufferLength = this.analyser.frequencyBinCount;

    this.waveform = new Uint8Array(this.analyser.fftSize);
    this.frequencyData = new Uint8Array(this.analyser.fftSize);
    this.audioElement.volume = this.volume;

    this.audioElement.addEventListener('playing', () => {
      this.playing = true;
    });
    
    this.audioElement.addEventListener('pause', () => {
      this.playing = false;
    });
    
    this.audioElement.addEventListener('ended', () => {
      this.playing = false;
      this.pause();
    });
  }
}

window.app = new App();

window.addEventListener('resize', app.onResize.bind(app));

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章