GSAP ScrollTrigger Tube Demo


As part of the GSAP ScrollTrigger Beta I was asked to create examples using ScrollTrigger. I have my own scroll-based animation library called VectorScrolling and had built a demo illustrating a dynamic camera movement along a poly-line using percentages. I adapted it to use GSAP as one of my submissions for the pre-launch which was selected to be showcased on the main documentation page of what can be accomplished.
Its a cool effect and in this article I’m going to attempt to break down the technique so you can pull something like this off in your own project.
What am I seeing?
A tube is created from a path of points. A camera is placed on the path, looking ahead in the direction of the path. As you scroll, the camera and the point at which the camera is looking animate forward along the path.
The Path
/*
I use an array to store th X, Y, and Z position of points.
This defines the bends
*/
let points = [
[10, 89, 0],
[50, 88, 10],
[76, 139, 20],
[126, 141, 12],
[150, 112, 8],
[157, 73, 0],
[180, 44, 5],
[207, 35, 10],
[232, 36, 0]
];
/*
Then I loop over the points array and turn them into vertices of a Vector3 object
*/
for (var i = 0; i < points.length; i++) {
let x = points[i][0];
let y = points[i][2];
let z = points[i][1];
points[i] = new THREE.Vector3(x, y, z);
}
/*
Use the array of Vector3 objects to generate a CatmullRom Spline
*/
let path = new THREE.CatmullRomCurve3(points);
path.tension = .5;
There are different types of curves in computer science. A popular one that most web developers know is the Bezier curve. Beziers are great because you have control over a set of control points that help to define a smooth curve. (play with Beziers).
Alternatively there’s a curve called a Catmull-Rom curve. Its curve is defined by the distance between each point along the curve. Essentially if you put a bunch of random dots on a piece of paper, and drew a curvy line that goes through each point you got yourself a Catmull-Rom curve (wikipedia).
So its that, but in 3d space.
The Tube
/*
Define a TubeGeometry based on the Path
with a 300 segment length
with a radius of 4
with a 32 sides
and no end caps
Then turn it into a Mesh
*/
let geometry = new THREE.TubeGeometry( path, 300, 4, 32, false );
let tube = new THREE.Mesh( geometry, material );
Seriously. Yup, just like that you got yourself a tube. I did some fanciness by making a second tube that is just the lines of the geometry and gave the outer tube a space texture, but you can see all of that in the CodePen.
The Camera
let camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.001, 200);
camera.rotation.y = cameraRotationProxyX;
camera.rotation.z = cameraRotationProxyY;
let c = new THREE.Group();
c.position.z = 400;
c.add(camera);
scene.add(c);
The camera sits within a parent Group called c
and the camera is set pretty far back in that group.
As you scroll a percentage grows from 0 to .96 from the top of the screen to the bottom of the screen. This percentage is needed for moving the camera group along the path.
gsap.registerPlugin(ScrollTrigger);
let tubePerc = {
percent: 0
}
let cameraTargetPercentage = 0;
let tl = gsap.timeline({
scrollTrigger: {
trigger: ".scrollTarget",
start: "top top",
end: "bottom 100%",
scrub: 5,
markers: {color: "white"}
}
})
tl.to(tubePerc, {
percent:.96,
ease: Linear.easeNone,
duration: 10,
onUpdate: function() {
cameraTargetPercentage = tubePerc.percent;
}
});
So we now know that cameraTargetPercentage
is the percentage from the top of the screen to the bottom of the screen. So we use that to position the camera group from within a render loop.
function render(){
let p1 = path.getPointAt(cameraTargetPercentage);
let p2 = path.getPointAt(cameraTargetPercentage + 0.03);
c.position.set(p1.x,p1.y,p1.z);
c.lookAt(p2);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
So p1
is the point along the path, and p2
is a point just a little ahead on the path. This is why we animate to .96 instead of 1. If percentage gets to 1, then the camera is looking at 1.03 which is not on the line, and the camera freaks out.
That’s it?
Yup, that’s the effect. In my demo I do some extra stuff, like tracking the cursor across the screen in order to add a little extra rotation to the camera in order to look around the screen.
let Mathutils = {
normalize: function($value, $min, $max) {
return ($value - $min) / ($max - $min);
},
interpolate: function($normValue, $min, $max) {
return $min + ($max - $min) * $normValue;
},
map: function($value, $min1, $max1, $min2, $max2) {
if ($value < $min1) {
$value = $min1;
}
if ($value > $max1) {
$value = $max1;
}
var res = this.interpolate(this.normalize($value, $min1, $max1), $min2, $max2);
return res;
}
};
document.addEventListener('mousemove', function(evt) {
cameraRotationProxyX = Mathutils.map(evt.clientX, 0, window.innerWidth, 3.24, 3.04);
cameraRotationProxyY = Mathutils.map(evt.clientY, 0, window.innerHeight, -0.1, 0.1);
});
function render() {
camera.rotation.y += (cameraRotationProxyX - camera.rotation.y) / 15;
camera.rotation.x += (cameraRotationProxyY - camera.rotation.x) / 15;
...
}
...
Thanks for following along. The code can be viewed on my CodePen - ScrollTriggering Through a Tube GSAP - ScrollTrigger Demo.
Subscribe to my newsletter
Read articles from Aaron Sherrill directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
