トップへ(mam-mam.net/)

Stunning Visual Effects! Advanced 3D Carousel Implementation [Dynamic Animation with Three.js]

Japanese

Creating a 3D Carousel (Using Three.js [r145]) Pattern 2

This article explains advanced animation techniques for a 3D carousel using Three.js!
Unlike a typical carousel, dynamic movement in 3D space allows you to create a visually impactful gallery.
We will provide a detailed explanation along with the source code.

See 3D Carousel (Pattern 1) here

Source Code

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="./three_r145/three.min.js"></script>
<script>
// X:→  Y:↑ Z:front, counterclockwise

var camera,spot;
var imageFiles=[
  './imgs/fc01.png',
  './imgs/fc02.png',
  './imgs/fc03.png',
  './imgs/fc04.png',
  './imgs/fc05.png',
  './imgs/fc06.png',
  ];
var imageLinkURL=[
  'https://mam-mam.net/javascript/three_js_material.html',
  'https://mam-mam.net/javascript/three_js_geometry.html',
  'https://mam-mam.net/javascript/three_js_baseball.html',
  'https://mam-mam.net/javascript/css_ul_li.html',
  'https://mam-mam.net/javascript/css_nav_horizontal_menu.html',
  'https://mam-mam.net/javascript/css_toggle_switch.html',
  ];
var rrr=10,// radius 10
    rrate=Math.PI/3,// rotation angle
    rrrate=1/rrate*Math.PI/2;
var moveImg=0,act=0,timer=0,moveStep={x:0,y:0,z:0,r:0},mesh=[],
  acts=[
    {"act":"w","time":120},
    {"act":"m","time":30},
  ];

async function loadImage(src){
  return new Promise(function(resolve,reject){
    let img=new Image();
    img.onload=function(){resolve(img);}
    img.onerror=function(e){reject(e);}
    img.src=src;
  });
}

window.addEventListener( 'DOMContentLoaded', async function(){
  var images=[];
  var material=[];
  var texture=[];

  // Create scene
  scene = new THREE.Scene();

  // Create and set camera
  var width  = document.getElementById("can").getAttribute("width");
  var height = document.getElementById("can").getAttribute("height");
  // field of view, aspect ratio, near, far
  camera = new THREE.PerspectiveCamera( 40, width/height, 1, 10000 );

  // Place renderer on DOM
  renderer = new THREE.WebGLRenderer({'canvas':document.getElementById('can') , antialias: true});// enable antialiasing
  renderer.setSize( width, height );
  renderer.shadowMap.enabled = true;// enable shadows
  renderer.shadowMapSoft = true;
  renderer.shadowMap.type = THREE.PCFShadowMap;

  document.getElementById('can').style.width="100%";
  document.getElementById('can').style.height="auto";


  // ■ Set background color
  //renderer.setClearColor(0x88ccff, 1);
  renderer.setClearColor(0x000000, 1);

  // Light ------------------------------------------------
  // Ambient light (light applied to all objects from all directions). Without this, unlit areas are completely black.
  var AmbientLight=new THREE.AmbientLight(0xffffff,0.2);
  scene.add( AmbientLight );


  // Load images
  images=await Promise.all(imageFiles.map(loadImage));
  // Load textures
  for(let i=0;i<imageFiles.length;i++){
    texture[i]=new THREE.TextureLoader().load(imageFiles[i]);
    material[i]=new THREE.MeshLambertMaterial({
      color: 0xffffff, // color
      map:texture[i],
      side:THREE.DoubleSide, //THREE.DoubleSide, THREE.FrontSide, THREE.BackSide
    });
  }

  // Create meshes
  for(let i=0;i<texture.length;i++){
    let w=10;// w:h = ww:hh   h=w*hh/ww
    let h=w/images[i].naturalWidth*images[i].naturalHeight;
    var plane=new THREE.PlaneGeometry(w, h, 1, 1);
    mesh[i]=new THREE.Mesh(plane,material[i]);
    mesh[i].position.x=Math.random()*20-10;
    mesh[i].position.y=Math.random()*20-10;
    mesh[i].position.z=-i*rrr;
    mesh[i].rotation.z=Math.random()*Math.PI*2;
    
    mesh[i].castShadow=true;
    scene.add(mesh[i]);
  }

  
  // Spotlight            color, intensity, distance, angle, penumbra[0-1], decay[1,1.5]
  spot=new THREE.SpotLight(0xffffff, 1.5, 24,Math.PI*0.35, 0.8, 1.2);
  spot.position.set(0,4,8);
  spot.target.position.set(0,-4,0);
  spot.castShadow=true;// enable shadows
  spot.shadow.mapSize.width = 4096;	// high quality shadows
  spot.shadow.mapSize.height = 4096;	// high quality shadows
  spot.shadow.radius=8;
  scene.add(spot);
  scene.add(spot.target);

  // Add events
  renderer.domElement.addEventListener('mouseup',onMouseDown,false);
  renderer.domElement.addEventListener('touchend',onMouseDown,false);

  // Camera position
  camera.position.set(mesh[0].position.x,mesh[0].position.y,mesh[0].position.z+rrr/2);
  camera.rotation.z=mesh[0].rotation.z;

  spot.position.set(mesh[0].position.x,mesh[0].position.y,mesh[0].position.z+rrr/2);
  spot.target.position.set(mesh[0].position.x,mesh[0].position.y,mesh[0].position.z);

  renderLoop();
});

function renderLoop () {
  // Execute scenario
  timer++;
  if(timer>=acts[act].time){
    timer=0;
    act++;
    if(act>=acts.length){
      act=0;
    }
  }
  if(acts[act].act=="w"){
    // just wait
  }else if(acts[act].act=="m"){
    if(timer==0){
      let fm=mesh[moveImg];
      moveImg++;
      if(moveImg>=mesh.length){moveImg=0;}
      let tm=mesh[moveImg];
      moveStep.x=(fm.position.x-tm.position.x)/acts[act].time;
      moveStep.y=(fm.position.y-tm.position.y)/acts[act].time;
      moveStep.z=(fm.position.z-tm.position.z)/acts[act].time;
      moveStep.r=(fm.rotation.z-tm.rotation.z)/acts[act].time;
    }
    camera.position.x-=moveStep.x;
    camera.position.y-=moveStep.y;
    camera.position.z-=moveStep.z;
    camera.rotation.z-=moveStep.r;
    
    spot.position.x-=moveStep.x;
    spot.position.y-=moveStep.y;
    spot.position.z-=moveStep.z;
    spot.target.position.x-=moveStep.x;
    spot.target.position.y-=moveStep.y;
    spot.target.position.z-=moveStep.z;
  }



  renderer.render( scene, camera );
  setTimeout(renderLoop,33);
}

function onMouseDown(event){
  event.preventDefault();
  // Get element position
  var clientRect = this.getBoundingClientRect() ;
  var positionX = clientRect.left;
  var positionY = clientRect.top;
  let st=window.getComputedStyle(document.getElementById("can"));
  let www=parseInt(st.width);
  let hhh=parseInt(st.height);
  let xxx,yyy;

  if(event.clientX){
    xxx=  ((event.clientX-positionX) / www) * 2 - 1;
    yyy= -((event.clientY-positionY) / hhh) * 2 + 1;
  }else{
    xxx =  ((event.changedTouches[0].pageX-positionX) / www) * 2 - 1;
    yyy = -((event.changedTouches[0].pageY-positionY) / hhh) * 2 + 1;
  }

  var pos=new THREE.Vector3(xxx,yyy,-10);
  pos.unproject(camera);

  // Create ray by passing start point and direction vector
  var ray = new THREE.Raycaster(camera.position, pos.sub(camera.position).normalize());
  var intersects=ray.intersectObjects(scene.children);
  if(intersects.length > 0){
    for(i=0;i<mesh.length;i++){
      for(j=0;j<intersects.length;j++){
        if(mesh[i]==intersects[j].object){
          if(imageLinkURL[i]!=""){
            window.open(imageLinkURL[i],'_blank');
          }
        }
      }
    }
  }
}
</script>
</head>
<body>
  <canvas id="can" style="width: 100%; height: auto; max-width:calc(100vw - 32px);max-height:calc(100vw - 32px);cursor:pointer;" 
  width="1000" height="300"></canvas>
</body>
</html>

Back to the list of 3D content with JavaScript