前面我们介绍过基于css的视差滚动效果,这次行歌教大家如何用js在H5页面中实现更加酷炫的视差特效,先来看一下我们这次要实现的案例

平滑滚动

在这个效果中我们用到了2个技术,分别是平滑滚动和three.js的shader编程。

平滑滚动

要实现丝般润滑的滚动效果,这里我们关闭了浏览器自带的滚动效果,选择使用css的translate3d属性来动态设置滚动容器的位置,并对容器设置绝对定位使其脱离文档流,在进行translate3d变换时,浏览器会默认开启硬件加速,避免布局和重绘造成的性能损失。

在滚动容器脱离文档流后,我们需要将body的高度设为整个滚动内容的高度,用来后面获得窗口的滚动位置。

document.body.style.height = `${scroll.getBoundingClientRect().height}px`

为了让滚动更加自然平滑,这里使用了线性插值公式,也就是我们的老朋友线性缓动公式。

function lerp(start, end, t) {
  return (1 - t) * start + end * t
}

根据当前窗口的滚动位置,我们用缓动公式来设置滚动容器的当前位置,完整代码请参考源文件。

function smoothScroll() {
  target = window.scrollY //滚动位置
  current = lerp(current, target, ease)
  scroll.style.transform = `translate3d(0, ${-current}px, 0)`
}

图片效果的实现

要实现图片跟随滚动变形的特效,这里就是shader大显身手的时候了,在之前的教程中行歌介绍过如何实现类似的图形特效,它们的原理都差不多,都是通过shader编程来动态改变图形网格的顶点。

在实现shader之前,我们还有一个问题需要解决:如何将页面中的图片转化为three.js中的geometry对象,并正确设置它们的大小和位置?

答案是:可以通过调整相机的视角范围值fov来实现,假设我们设定相机和视平面的距离perspective是1000,就可以通过反正切公式得到相机的fov值。

demo
const fov = (180 * (2 * Math.atan(window.innerHeight / 2 / perspective))) / Math.PI

然后设置相机的fov和z值。

this.camera = new THREE.PerspectiveCamera(fov, this.viewport.aspectRatio, 1, 1000)
this.camera.position.set(0, 0, perspective)

设置好相机后,我们就可以用getBoundingClientRect方法获得图片在页面中的大小和位置信息,用得到的信息来设置three.js中的mesh对象,这里需要注意:three.js中mesh坐标原点是在中心,而网页中的图片原点默认在左上角,所以需要做一次坐标位移变换。

const { width, height, top, left } = this.element.getBoundingClientRect()
this.sizes.set(width, height)
this.offset.set(
  left - window.innerWidth / 2 + width / 2,
  -top + window.innerHeight / 2 - height / 2
)
this.mesh.position.set(this.offset.x, this.offset.y, 0)
this.mesh.scale.set(this.sizes.x, this.sizes.y, 0)

让图片跟随滚动变形

图片网格是根据当前滚动位置动态改变的,它是个名为uOffset的uniform。

this.uniforms.uOffset.value.set(0, -(target - current) * 0.0003)

在顶点着色器中,根据uOffset值来动态改变顶点位置。

vec3 deformationCurve(vec3 position, vec2 uv, vec2 offset) {
  position.x = position.x + (sin(uv.y * M_PI) * offset.x);
  position.y = position.y + (sin(uv.x * M_PI) * offset.y);
  return position;
}
void main() {
  vUv = uv;
  vec3 newPosition = deformationCurve(position, uv, uOffset);
  gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}

总结

借助shader编程可以实现其他技术无法做到的炫酷特效,如果大家有兴趣,我会在后面介绍更多有趣效果的实现方法。

源码下载地址:https://gitee.com/imokya/smooth-scroll-tutorial

关注