<div class="container">
  <canvas></canvas>
</div>
html,body {
  height:100%;
}
body {
  display:flex;
  align-items:center;
  justify-content:center;
}
svg {
  width:100%;
}
.container {
  width:100%;
  height:100%;
  & canvas {
    display:block;
    width:100%;
    height:100%;
  }
}
View Compiled
console.clear();
let scene,camera,renderer,controls;
let canvas = document.querySelector("canvas");
let width = window.innerWidth;
let height = window.innerHeight;
let fov = 75; 
let aspectRatio = width / height; 
let near = 0.001;
let far = 100;
let mainGroup = new THREE.Group();
let colors = {
  blue: 0x71b6f7,
  brown: 0x744436,
  brown2:0xC88247,
  red : 0xfd4d50,
  green: 0xa4d740,
  green2: 0x66b888,
  green3 : 0x2C9D3E,
  house : 0xfce3ad,
  purple : 0x6e5370,
  gold:0xFFF09C,
  grey:0xB7B398,
  greyBrown : 0xB7B398
};

const setup = () => {
  // scene
  scene = new THREE.Scene();
  scene.fog = new THREE.FogExp2(0x9ac2fe, 0.14);
  // camera
  camera = new THREE.PerspectiveCamera(fov, aspectRatio, near, far);
  scene.add( camera );  
  // renderer
  renderer = new THREE.WebGLRenderer({
    antialias: true,
    canvas: canvas
  });
  renderer.setSize(width, height);
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setClearColor(0x9ac2fe);
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap
  renderer.shadowMapSoft = true;
  // controls
  controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.autoRotate = true;
controls.autoRotateSpeed = 1.6;
  camera.position.set(3.56, 2, 3.4);
  // lights 
  let light = new THREE.HemisphereLight(0xffffff, 0x5c9cfe, 1.1);
  scene.add(light);
  //
  let spot = new THREE.SpotLight(0xF6FAFD,0.06)
  spot.castShadow = true;
  spot.shadow.camera.left = -1;
  spot.shadow.camera.right = -1;
  spot.shadow.camera.top = -1;
  spot.shadow.camera.bottom = -1;
  spot.shadow.camera.near = 1;
  spot.shadow.camera.far = 100;
  spot.shadow.mapSize.width = 2048;
  spot.shadow.mapSize.height = 2048;
  spot.shadow.camera.fov = 100;
  spot.position.set(-3,3,0);
  camera.add(spot);
  // group for all other groups
  mainGroup = new THREE.Group();
  scene.add(mainGroup)
  camera.lookAt(0,0,0)
}

// objects
let islandGroup = new THREE.Group();
const createIsland = () => {
  // earth
  let coneGeo = new THREE.ConeBufferGeometry(2, 3, 8);
  let mat = new THREE.MeshLambertMaterial({
    color: colors.brown
  });
  let cone = new THREE.Mesh(coneGeo, mat);
  cone.rotation.x = THREE.Math.degToRad(180)
  cone.position.set(0, -1.75, 0);
  islandGroup.add(cone);
  // grass
  let boxGeo = new THREE.BoxBufferGeometry();
  let mat2 = new THREE.MeshLambertMaterial({
    color: colors.green
  });
  let grass = new THREE.Mesh(boxGeo, mat2);
  grass.scale.set(4, 0.5, 4);
  grass.receiveShadow = true;
  islandGroup.add(grass);
  // water
  let mat3 = new THREE.MeshLambertMaterial({
    color: colors.blue
  });
  let water = new THREE.Mesh(boxGeo, mat3);
  water.receiveShadow = true;
  islandGroup.add(water);
  water.scale.set(0.5, 0.5, 4);
  water.position.set(0.75, 0.005, 0.005);
  mainGroup.add(islandGroup);
}

// waterfall particles
let dropCount = 100;
let drops = [];
// water details
let detailCount = 24;
let dets = [];
let detailCount2 = 8;
let dets2 = [];

// creating particles function
let particleGeo,particleMat,particle;
const createParticles = (color,particleAmount,particleArray,scaleX,scaleY,scaleZ,posX,posX2,posY,posY2,posZ,posZ2,opacity,rotX,rotY,rotZ) => {
    particleGeo = new THREE.BoxBufferGeometry();
    particleMat = new THREE.MeshLambertMaterial({
        color:color,
        transparent:true
    });
    for (let i = 0; i< particleAmount;i++) {
        particle = new THREE.Mesh(particleGeo,particleMat);
        islandGroup.add(particle);
        particleArray.push(particle);
        particle.scale.set(scaleX,scaleY,scaleZ);
        particle.position.set(THREE.Math.randFloat(posX,posX2), THREE.Math.randFloat(posY,posY2),THREE.Math.randFloat(posZ,posZ2))
        particle.material.opacity = opacity;
        particle.rotation.set(rotX,THREE.Math.degToRad(rotY),rotZ);
    }
}

// function to create various box like shapes of the house and the mailbox
const createBoxShape =  (x,y,z,xPos,yPos,zPos,color,rShadow,cShadow,group) => {
  let geo = new THREE.BoxBufferGeometry(x,y,z);
  let mat = new THREE.MeshLambertMaterial({color:color});
  let mesh = new THREE.Mesh(geo,mat);
  mesh.position.set(xPos,yPos,zPos);
  mesh.receiveShadow = rShadow
  mesh.castShadow = cShadow
  group.add(mesh);  
}

let treeGroup = new THREE.Group();
const createTree = (trunkX, trunkY, trunkZ, leavesX, leavesY, leavesZ) => {
  // trunk
  let geo = new THREE.CylinderBufferGeometry(0.1, 0.1, 1, 10);
  let mat = new THREE.MeshLambertMaterial({
    color: colors.brown
  });
  let trunk = new THREE.Mesh(geo, mat);
  treeGroup.add(trunk);
  trunk.position.set(trunkX, trunkY, trunkZ);
  trunk.castShadow = true;
  trunk.receiveShadow = true;
  // leaves
  let geo2 = new THREE.SphereBufferGeometry(0.25, 12, 12);
  let mat2 = new THREE.MeshLambertMaterial({
    color: colors.green2
  });
  let treeLeaves = new THREE.Mesh(geo2, mat2);
  treeLeaves.position.set(leavesX, leavesY, leavesZ);
  treeLeaves.castShadow = true;
  treeLeaves.receiveShadow = true;
  treeGroup.add(treeLeaves);
  mainGroup.add(treeGroup);
};

const createBush = (x,y,z) => {
  let geo = new THREE.SphereBufferGeometry(0.15,8,8);
  let mat = new THREE.MeshLambertMaterial({color:colors.green3});
  let bush = new THREE.Mesh(geo,mat);
  bush.position.set(x,y,z);
  bush.receiveShadow = true;
  treeGroup.add(bush)
}

// house
let houseGroup = new THREE.Group();

// roof window function
const createRoofWindow = (x,y,z,color,radB,radT,height,segments) => {
  let geo = new THREE.CylinderBufferGeometry(radB,radT,height,segments);
  let mat = new THREE.MeshLambertMaterial({ color: color });
  let window = new THREE.Mesh(geo, mat);
  window.rotation.z = THREE.Math.degToRad(90);
  window.position.set(x,y,z);
  houseGroup.add(window);
}

const createHouse = () => {
  let boxGeo = new THREE.BoxBufferGeometry();
  // house
  let houseMat = new THREE.MeshLambertMaterial({ color: colors.house });
  let house = new THREE.Mesh(boxGeo, houseMat);
  house.position.set(-1, 0.75, 1);
  house.scale.set(1.2,1,1.5);
  house.receiveShadow = true;
  house.castShadow = true;
  houseGroup.add(house);
  // roof
  let roofGeo = new THREE.ConeBufferGeometry(1.1, 0.7, 4);
  let roofMat = new THREE.MeshLambertMaterial({ color: colors.red });
  let roof = new THREE.Mesh(roofGeo, roofMat);
  roof.position.set(-1, 1.6, 1);
  roof.rotation.y = THREE.Math.degToRad(45);
  roof.castShadow = true;
  roof.receiveShadow = true
  houseGroup.add(roof);
  // roof chimney
  let chimneyMat = new THREE.MeshLambertMaterial({ color:colors.house });
  let chimney = new THREE.Mesh(boxGeo,chimneyMat);
  chimney.position.set(-1,1.6,0.6);
  chimney.scale.set(0.2,0.3,0.2)
  chimney.receiveShadow = true;
  houseGroup.add(chimney);
  // door
  let doorMat = new THREE.MeshLambertMaterial({ color: colors.purple });
  let door = new THREE.Mesh(boxGeo, doorMat);
  door.scale.set(0.2,0.5,0.35);
  door.position.set(-0.49, 0.545, 1);
  houseGroup.add(door);
  // doorknob
  let knobG = new THREE.SphereBufferGeometry(0.025,8,8)
  let knobMat = new THREE.MeshLambertMaterial({color:colors.gold})
  let knob = new THREE.Mesh(knobG,knobMat);
  houseGroup.add(knob)
  knob.position.set(-0.39,0.5,1.14)
  // doorstep 
  createBoxShape(0.05,0.05,0.35,-0.38,0.27,1,colors.grey,false,false,houseGroup);
  createBoxShape(0.05,0.05,0.35,-0.34,0.25,1,colors.grey,false,false,houseGroup);  
  // windows 
  createRoofWindow(-0.65,1.6,1,colors.blue,0.1,0.1,0.2,12);
  createRoofWindow(-0.67,1.60,1,colors.red,0.12,0.12,0.22,12);
  // roof window bars
  createBoxShape(0.02,0.24,0.025,-0.55,1.6,1,colors.red,false,false,houseGroup);
  createBoxShape(0.02,0.02,0.2,-0.55,1.62,1,colors.red,false,false,houseGroup);
  // left window
  createBoxShape(0.05,0.3,0.3,-0.41,0.65,1.45,colors.blue,false,false,houseGroup);
  // vertical bars
  createBoxShape(0.05,0.3,0.025,-0.40,0.65,1.45,colors.brown2,false,false,houseGroup);
  createBoxShape(0.05,0.3,0.025,-0.40,0.65,1.60,colors.brown2,false,false,houseGroup);
  createBoxShape(0.05,0.3,0.025,-0.40,0.65,1.30,colors.brown2,false,false,houseGroup);
  // horizontal bars
  createBoxShape(0.05,0.025,0.325,-0.40,0.8,1.45,colors.brown2,false,false,houseGroup);
  createBoxShape(0.05,0.025,0.325,-0.40,0.65,1.45,colors.brown2,false,false,houseGroup);
  createBoxShape(0.05,0.025,0.325,-0.40,0.5,1.45,colors.brown2,false,false,houseGroup);
  // right window
  createBoxShape(0.05,0.3,0.3,-0.41,0.65,0.55,colors.blue,false,false,houseGroup);  
  // vertical bars
  createBoxShape(0.05,0.3,0.025,-0.40,0.65,0.55,colors.brown2,false,false,houseGroup);
  createBoxShape(0.05,0.3,0.025,-0.40,0.65,0.70,colors.brown2,false,false,houseGroup);
  createBoxShape(0.05,0.3,0.025,-0.40,0.65,0.40,colors.brown2,false,false,houseGroup);
  // horizontal bars
  createBoxShape(0.05,0.025,0.325,-0.40,0.8,0.55,colors.brown2,false,false,houseGroup);
  createBoxShape(0.05,0.025,0.325,-0.40,0.65,0.55,colors.brown2,false,false,houseGroup);
  createBoxShape(0.05,0.025,0.325,-0.40,0.5,0.55,colors.brown2,false,false,houseGroup);
  mainGroup.add(houseGroup);
}

// chimney smoke
const puffCount = 2;
let puffs = [];
let puff;
const createPuffs = () => {
  for (let i = 0; i < puffCount; i++) {
    const geo = new THREE.SphereBufferGeometry(0.1,6, 6);
    const mat = new THREE.MeshLambertMaterial({color:0xffffff,transparent:true});
    puff = new THREE.Mesh(geo,mat);
    puffs.push(puff);
    houseGroup.add(puff); 
    puff.position.set(-1,1.74,0.6);
    puff.scale.set(0,0,0);
  }
}

// mailbox
let mailBoxGroup = new THREE.Group();

const createMailBoxRoof = () => {
  let mailRoofG = new THREE.ConeBufferGeometry(0.16, 0.1, 4);
  let mailRoofMat = new THREE.MeshLambertMaterial({color:colors.brown2});
  let mailRoofMesh = new THREE.Mesh(mailRoofG,mailRoofMat);
  mailRoofMesh.position.set(0,0.34,0)
  mailRoofMesh.rotation.y = THREE.Math.degToRad(45);
  mailRoofMesh.receiveShadow = true;
  mailRoofMesh.castShadow = true;
  mailBoxGroup.add(mailRoofMesh);
}

const createMailbox = () => {
  createMailBoxRoof();
  // pole
  createBoxShape(0.05,0.3,0.05,0,0,0,colors.brown2,true,true,mailBoxGroup);
  // box
  createBoxShape(0.2,0.2,0.2,0,0.2,0,colors.brown2,true,true,mailBoxGroup);
  // hole
  createBoxShape(0.05,0.05,0.16,0.085,0.22,0,0x634326,false,false,mailBoxGroup);
  mainGroup.add(mailBoxGroup);
  mailBoxGroup.position.set(1.5,0.4,1.5);
}

// Bunny
let pivot1 = new THREE.Group();
let pivot2 = new THREE.Group();
let pivot3 = new THREE.Group();
let bunnyGroup1 = new THREE.Group();
let bunnyGroup2 = new THREE.Group();
let bunnyGroup3 = new THREE.Group();
const createBunnyShape = (group,x,y,z,color,xPos,yPos,zPos) => {
  let geo = new THREE.BoxBufferGeometry(x,y,z);
  let mat = new THREE.MeshLambertMaterial({color:color,transparent:true});
  let mesh = new THREE.Mesh(geo,mat);
  mesh.position.set(xPos,yPos,zPos);
  mesh.receiveShadow = true;
  mesh.castShadow = true;
  group.add(mesh);
}

const bunnyColors = [0xFFFFFF,0xEAEAEA,0xCFAF8E];
const createBunny = (group,pivotPositionX,pivotPositionY,pivotPositionZ,pivot) => {
  bunnyColor = bunnyColors[Math.round(Math.random() * 2)] 
  // head
  createBunnyShape(group,0.1,0.1,0.1,bunnyColor,0,0,0.2);
  // ears
  createBunnyShape(group,0.025,0.14,0.025,bunnyColor,0.025,0.05,0.23);
  createBunnyShape(group,0.025,0.14,0.025,bunnyColor,-0.025,0.05,0.23);
  // eyes
  createBunnyShape(group,0.02,0.02,0.02,"black",0.025,0.02,0.25);
  createBunnyShape(group,0.02,0.02,0.02,"black",-0.025,0.02,0.25);
  mainGroup.add(group); 
  // https://stackoverflow.com/questions/28848863/threejs-how-to-rotate-around-objects-own-center-instead-of-world-center
  let box = new THREE.Box3().setFromObject( group );
  box.getCenter( group.position ); // this re-sets the mesh position
  group.position.multiplyScalar( - 1 );
  pivot.add( group );
  pivot.position.set(pivotPositionX,pivotPositionY,pivotPositionZ);
  mainGroup.add( pivot );
  
}

// GSAP ANIMATIONS 
// make island floaty
const animateIsland = () => {
  gsap.to(mainGroup.position,{y:'+=0.065',repeat:-1,yoyo:true,ease:"sine.in",duration:2,yoyoEase:"sine.inOut"})
}
// animate single waterfall particle
const animateDrop = (drop) => {
  const tl = gsap.timeline({
    onStart: () => {
      gsap.set(drop.position,{ y: gsap.utils.random(-0.17,0,0.01) })
      gsap.set(drop.scale,{x:0.1,y:0.1,z:0.1})
    },
    onComplete: animateDrop,
    onCompleteParams: [drop]
  });

  tl.to(drop.position, {
    y: "-=1",
    ease: "linear",
    delay: gsap.utils.random(0, 2, 0.2),
    duration:1,
    onStart:() => {
      gsap.to(drop.scale,{x:0,y:0,z:0,delay:0.14,duration:0.86})
    }
  });
  return tl;
};
// animate single water detail
animateDet = (det) => {
  const tl = gsap.timeline(
    {defaults:{duration:1,ease:"sine.in"},
     onStart:() => {
       gsap.set(det.position,{x:gsap.utils.random(0.60,0.92),z:gsap.utils.random(-1.8,1.8)});
       gsap.set(det.rotation,{y:0,z:0});
       gsap.set(det.material,{opacity:0});
     },
     onComplete:animateDet,
     onCompleteParams:[det]}
  )
  tl.to(det.material,{keyframes:[{opacity:0.7},{opacity:0}]},'in')
  .to(det.position,{keyframes:[{z:"+=0.025"},{z:"-=0.025"}]},'in')
  .to(det.rotation,{keyframes:[{y:"-=0.2"},{z:"+=0.2"}]},'in')

  return tl;
}

animateDet2 = (det) => {
  const tl = gsap.timeline(
    {defaults:{duration:1,ease:"sine.in"},
     onStart:() => {
       gsap.set(det.position,{x:gsap.utils.random(0.60,0.92),y:gsap.utils.random(-0.18,0.20)});
       gsap.set(det.rotation,{y:0,z:0});
       gsap.set(det.material,{opacity:0});
     },
     onComplete:animateDet2,
     onCompleteParams:[det]}
  )
  tl.to(det.material,{keyframes:[{opacity:0.7},{opacity:0}]},'in')
  .to(det.position,{keyframes:[{y:"-=0.025"},{y:"+=0.025"}]},'in')
  .to(det.rotation,{keyframes:[{y:"-=0.2"},{z:"+=0.2"}]},'in')

  return tl;
}


// animate single puff
const animatePuff = (puff) => {
  const tl = gsap.timeline({onComplete:animatePuff,onCompleteParams:[puff],onStart:() => {
    gsap.set(puff.material,{opacity:1})
    gsap.set(puff.scale,{x:0,y:0,z:0})
    gsap.set(puff.position,{y:1.74})
  }})
  tl.to(puff.position,{y:'+=0.6',duration:2,delay:0.6,ease:"sine.inOut",onStart:() => {
    gsap.to(puff.scale,{keyframes:[{x:1,y:1.4,z:1},{z:1.4,duration:0.24},{y:0.8,delay:-0.44,duration:0.24},{x:1,y:1}]})
    gsap.to(puff.material,{opacity:0,duration:1.32,delay:0.68})
  }})
}
// function to loop over particles
const animateParticles = (fn,array) => {
  for (let i =0; i < array.length; i++) {
    fn(array[i])
  }
}

// animate the bunny
// eye blink
const animateBunnyEyes = (group,delay) => {
  const tl = gsap.timeline({repeat:-1,repeatDelay:1,defaults:{duration:0.2},delay:delay})
  const eyes = [group.children[3].material,group.children[4].material]
  tl.to(eyes,{keyframes:[{opacity:0},{opacity:1}]},'blink')
  return tl;
}

// hop and move
const animateBunny = (pivot,delay) => {
    const tl = gsap.timeline({repeat:-1,defaults:{duration:0.32},delay:delay})
  tl.to(pivot.position,{keyframes:[{y:'+=0.1',z:'+=0.1'},{y:'-=0.1'},{y:'+=0.1',z:'+=0.1'},{y:'-=0.1'}]})
    .to(pivot.rotation,{y:3,duration:0.6,delay:0.16})
    .to(pivot.position,{keyframes:[{y:'+=0.1',z:'-=0.1'},{y:'-=0.1'},{y:'+=0.1',z:'-=0.1'},{y:'-=0.1'}]})
    .to(pivot.rotation,{y:0,duration:0.6,delay:0.16})
  return tl;
}

// render
render = () => {
  
  controls.update()
  requestAnimationFrame(render);
  renderer.render(scene, camera);
};

// resize
const resizeHandler = () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
};

window.addEventListener("resize", () => {
  resizeHandler();
});

window.addEventListener('load',() => {
  setup();
  createIsland();
  animateIsland();
  // waterfall particles creation
  createParticles(
    colors.blue,
    dropCount,
    drops,
    0.1,0.1,0.1,
    0.56,0.95,0,-0.19,1.95,1.95,
    1,
    0,0,0
  );
  // animate waterfall particles 
  animateParticles(animateDrop,drops);
  // water details creation
  createParticles(
    "white",
    detailCount,
    dets,
    0.025,0.025,0.025,
    0.60,0.92,0.25,0.26,1.8,-1.8,
    0,
    0,0,0
  );
  createParticles(
    "white",
    detailCount2,
    dets2,
    0.025,0.025,0.025,
    0.60,0.92,-0.2,0.23,2,2,
    0,
    0,0,0
  );
  // animate water details
  animateParticles(animateDet,dets);
  animateParticles(animateDet2,dets2);
  // trees
  // trees next to house
  createTree(0, 0.75, -0.1, 0, 1.2, -0.1);
  createTree(-1.50, 0.75, -0.1, -1.50, 1.2, -0.1);
  createTree(-0.75, 0.75, -0.5, -0.75, 1.2, -0.5);
  createTree(0, 0.75, -1, 0, 1.2, -1);
  createTree(-1.50, 0.75, -1, -1.50, 1.2, -1);
  createTree(-0.75, 0.75, -1.5, -0.75, 1.2, -1.5);
  // other trees
  createTree(1.5, 0.75, -1.5, 1.5, 1.2, -1.5);
  createTree(1.5, 0.75, -0.5, 1.5, 1.2, -0.5);
  createTree(1.5, 0.75, 0.5, 1.5, 1.2, 0.5);
  // bushes next to house
  createBush(-0.7,0.28,-0.1);
  createBush(-0.7,0.28,-1);
  createBush(-1.55,0.28,-0.6);
  createBush(-1.35,0.28,-1.5);
  //
  createBush(1.5,0.28,1);
  createBush(1.5,0.28,0);
  createBush(1.5,0.28,-1);
  // house
  createHouse();
  // chimney smoke
  createPuffs();
  // animate the smoke
  animateParticles(animatePuff,puffs);
  // mailbox
  createMailbox();
  // bunnies 
  createBunny(bunnyGroup1,0,0.33,0.2,pivot1);
  createBunny(bunnyGroup2,-1,0.33,-1,pivot2);
  createBunny(bunnyGroup3,-0.2,0.33,-1.4,pivot3);
  animateBunny(pivot1,0);
  animateBunny(pivot2,gsap.utils.random(0,3,0.4));
  animateBunny(pivot3,gsap.utils.random(0,3,0.4));
  animateBunnyEyes(bunnyGroup1,0);
  animateBunnyEyes(bunnyGroup2,gsap.utils.random(0,3,0.4));
  animateBunnyEyes(bunnyGroup3,gsap.utils.random(0,3,0.4));  
  render();
})
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.4/gsap.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/three.js/r118/three.min.js
  3. https://cdn.jsdelivr.net/npm/three@0.118/examples/js/controls/OrbitControls.js