前面把星系和 div 部分都画完了:

这节加上滚动的交互:
首先我们监听滚动事件,拿到当前是第几屏:

let curPageNo;
window.addEventListener('scroll', () => {
const pageNo = Math.round(window.scrollY /window.innerHeight);
if(pageNo !== curPageNo) {
curPageNo = pageNo;
console.log(pageNo);
}
});
export function getCurPageNo() {
return curPageNo;
}

可以看到,能拿到正确的页码。
然后我们在滚动到每一页的时候,改一下相机位置:
就像前面控制人物行走时一样:

把相机加到和星球一个 group 里就行了。
把星球放到一个个 group 里:

注释掉之前的位置。
把行星放到一个个 group 里。
const planetGroup = new THREE.Group();
planetGroup.name = 'planetGroup' + index;
planetGroup.add(planet);
group.add(planetGroup);
为什么要注释掉位置呢?
因为有了 group,位置就是相对于局部坐标系了。
我们加个 AxesHelper 看看:

const helper = new THREE.AxesHelper(1000);
planetGroup.add(helper);
改变位置的不再是行星,而是整个 group:

顺便把太阳调小一点:

看下效果:

可以看到,每个星球都有一个单独的 group 的局部坐标系。
然后我们把 camera 放到每个 group 里,并调整下位置,滚动切换页码的时候调整下 group:

window.addEventListener('scroll', () => {
const pageNo = Math.round(window.scrollY /window.innerHeight);
if(pageNo !== curPageNo) {
curPageNo = pageNo;
console.log(pageNo);
if(curPageNo === 0) {
return;
}
camera.parent?.remove(camera);
const targetGroup = mesh.getObjectByName('planetGroup' + (curPageNo - 1));
targetGroup.add(camera);
const planet = targetGroup.children[0];
const r = planet.geometry.parameters.radius;
camera.position.set(0, 0, r * 5);
}
});
把 scroll 的处理逻辑拿到 init 函数里,这样可以访问到 camera。
第 0 页是介绍,不用处理,从第 1 开始依次是太阳、水星。。。
拿到对应的 group,把 camera 放里面,并且改一下相机位置在半径 * 5 的位置。
去掉最开始的相机位置:

看下效果:

这样,滚动到对应页码的时候,相机就会定位到对应的星球。
去掉所有 AxesHelper,把星轨透明度调高一点,并且点取得更密一点:

看下效果:

这样,太阳系科普网站就完成了。
案例代码上传了小册仓库
总结
这节我们实现了滚动切换星球到视野中央的功能。
滚动的页码计算之前讲过,就是用 window.scrollY 和 window.innerHeight 来计算。
定位星球位置到中央,可以把相机加到星球所在的 group 中,让它跟着 group 一起动。
两者结合,就可以实现滚动页面,切换视野中的行星的功能。
这算是一个综合性的小实战,以后遇到需要滚动控制一些交互效果的需求,就可以用这种方式实现。