Hey reader ! 👋

If there’s one thing I truly love, it’s tunnel animations 😍
Don’t know what I mean? Check out these pens I’ve previously built:

I even used this kind of animation for my agency’s 2017 wishes card 🎆.

I will try in this post to explain the basic setup for one of this demo.
The first step will be to create a tube and to animate the camera in it. We will then see how to customize the tube.

For this demo I'm gonna use ThreeJs for the WebGL part. If you don't feel comfortable with it, you may want to check Rachel Smith's posts about it.

1. Setup the scene

For this first step I'm adding on my demo the basis needed to initialize a ThreeJs scene.

  • A canvas in my HTML
  • Some little CSS to display everything fine
  • A WebGL renderer, a scene, a camera and a red cube to make sure everything works fine.

Do not forget to include ThreeJs library in your demo / page

If you can see a red cube spinning, it means we are good to continue 📦 !

2. Create a tube geometry

To create a tube in ThreeJs you will first need to create a path. To achieve that we will use THREE.CatmullRomCurve3() constructor. It allows you to create a smooth spline from an array of vertices.
For this demo, I hard coded an array of points that I'm converting into Vector3().
When you have your array of vertices, you can create your path using the constructor function.

  //Hard coded array of points
var points = [
  [0, 2],
  [2, 10],
  [-1, 15],
  [-3, 20],
  [0, 25]
];

//Convert the array of points into vertices
for (var i = 0; i < points.length; i++) {
  var x = points[i][0];
  var y = 0;
  var z = points[i][1];
  points[i] = new THREE.Vector3(x, y, z);
}
//Create a path from the points
var path = new THREE.CatmullRomCurve3(points);

After getting your path, we can now create the tube based on it.

  //Create the tube geometry from the path
//1st param is the path
//2nd param is the amount of segments we want to make the tube
//3rd param is the radius of the tube
//4th param is the amount of segment along the radius
//5th param specify if we want the tube to be closed or not
var geometry = new THREE.TubeGeometry( path, 64, 2, 8, false );
//Basic red material
var material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
//Create a mesh
var tube = new THREE.Mesh( geometry, material );
//Add tube into the scene
scene.add( tube );

You should now see a red tube rotating in the scene 😊.

3. Create a tube from a SVG polygon

In most of the cases you won't want to hard code the points of your path. You could create a function to generate a random set of points with some random algorithms. But in this demo we will get the values from an SVG I created on Adobe Illustrator.
If you don't set any bezier curves on a path, Illustrator will export your path as a polygon like this :

  <svg viewBox="0 0 346.4 282.4">
    <polygon points="68.5,185.5 1,262.5 270.9,281.9 345.5,212.8 178,155.7 240.3,72.3 153.4,0.6 52.6,53.3 "/>
</svg>

From this polygon, we can convert it manually into an array :

  var points = [
    [68.5,185.5],
    [1,262.5],
    [270.9,281.9],
    [345.5,212.8],
    [178,155.7],
    [240.3,72.3],
    [153.4,0.6],
    [52.6,53.3],
    [68.5,185.5]
];
//Do not forget to set the last parameter to True, since we want our tube to be closed
var geometry = new THREE.TubeGeometry( path, 300, 2, 20, true );

If you are motivated, you could create a function to dynamically convert the SVG string into an array 😉

You can see the SVG polygon on the left side. The tube is now following the points we set.

4. Move the camera inside the tube

Now that we have our tube, there is still the main part remaining : the animation !
We are gonna use a useful function from our path path.getPointAt(t);.
This function returns any point along the path at a specific percentage. The percentage is a normalized value from 0 to 1. Zero is the first point of the path, and One is the latest point of it.
We are gonna use this function on each frame to position the camera along the path. We need to increase the percentage at each frame to to make the magic happen.

  //Start the percentage at 0
var percentage = 0;
function render(){
  //Increase the percentage
  percentage += 0.001;
  //Get the point at the specific percentage
  var p1 = path.getPointAt(percentage%1);
  //Place the camera at the point
  camera.position.set(p1.x,p1.y,p1.z);

  renderer.render(scene, camera);
  requestAnimationFrame(render);
}
requestAnimationFrame(render);

Since the .getPointAt() function only accepts values from 0 to 1, we need to 'modulo' our percentage to ensure it won't go further than 1

This is gonna work great for the position, but the camera will always look at the same direction. To fix this we will have to make our camera look at a point that is also along the path but a bit further. On each frame, we are gonna calculate the point where the camera will be set and also a point further where the camera will look at.

  var percentage = 0;
function render(){
  percentage += 0.001;
  var p1 = path.getPointAt(percentage%1);
  //Get another point along the path but further
  var p2 = path.getPointAt((percentage + 0.01)%1);
  camera.position.set(p1.x,p1.y,p1.z);
  //Rotate the camera into the orientation of the second point
  camera.lookAt(p2);

  renderer.render(scene, camera);
  requestAnimationFrame(render);
}

There are some options to update to our material. Currently we have a basic material but with its sides looking to the outside. But now that our camera is INSIDE the tube, the sides should be reversed. And since we don't have any lights in our scene, we can switch our material to wireframe so that we can easily see what's happening.

  var material = new THREE.MeshBasicMaterial({
  color: 0xff0000, //Red color
  side : THREE.BackSide, //Reverse the sides
  wireframe:true //Display the tube as a wireframe
});

And voilà, we now have the camera moving inside the tube ! 🎉

5. Add a light

I'm not gonna go into the details in this post but I'll show you how to set a basic light in our tube.
The principle will be the same as the motion of the camera. We will use the position of the point where the camera is looking at to place a light.

  • First we create a PointLight and we add it into the scene 💡.
  //Create a point light in our scene
var light = new THREE.PointLight(0xffffff,1, 50);
scene.add(light);

  • Then we switch our material into a one that gets affected by the lights.
  var material = new THREE.MeshLambertMaterial({
  color: 0xff0000,
  side : THREE.BackSide
});

  • And finally we update the render function to move our light.
  var percentage = 0;
function render(){
  percentage += 0.0003;
  var p1 = path.getPointAt(percentage%1);
  var p2 = path.getPointAt((percentage + 0.02)%1);
  camera.position.set(p1.x,p1.y,p1.z);
  camera.lookAt(p2);
  light.position.set(p2.x, p2.y, p2.z);

  renderer.render(scene, camera);
  requestAnimationFrame(render);
}

Here is the result !

6. Let's get crazy

Based on the the last step, I forked it and played with some parameters to create new kind of animations. Check the source code if you are curious 😉

For this pen, I'm setting a different colour for every face. That way we get a funny mosaic pattern.

In this one, I'm playing with the Y position of the points to generate my path. That way the tube is not only on one plane, but in three dimensions.

For the last one, I'm creating 5 tunnels with different radius and color. They also have a different opacity to make them well visible.

🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄

This is the end of the first part. For the next post I'll explain how to create a tube made of particles without using the TubeGeometry() from ThreeJS.

You can find all the demos from the pens into this collection if you want to see them together.

I hope you learned something from it ! Do not hesitate to poke me on Twitter if you have any questions.

Mamboleoo