最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • three.js 实现图片滚动扭曲特效 | 技术点评

    正文概述 掘金(alphardex)   2021-03-08   766

    前言

    大家好,这里是 CSS 魔法使——alphardex。

    平时我们见过很多的图片悬浮和滚动特效,大部分是用CSS和SVG实现的,但是有一种特效它们绝对实现不了——扭曲特效。为何?CSS擅长直线型变换,而SVG擅长曲线型变换。扭曲特效则两者都不是,它是像素级变换,能做到像素级变换的只有canvas,而webgl的片元着色器其实特别擅长这一点,我们可以利用它来实现各种酷炫的扭曲特效。以下是最终实现的效果图

    three.js 实现图片滚动扭曲特效 | 技术点评

    撒,哈吉马路由!

    准备工作

    笔者的three.js模板:点击右下角的fork即可复制一份

    实现思路

    实现本文效果最重要的一点是:将HTML世界与webgl世界同步起来!

    一旦两个世界同步了,各种炫酷的效果都可以应用在原生HTML元素上,且不影响基本的交互!

    世界同步

    搭建HTML

    第一步:将你想展示的所有图片搭成一个简单的HTML页面

    <main class="overflow-hidden">
      <div data-scroll>
        <div class="relative w-screen h-screen flex-center">
          <img class="w-240 h-120" src="https://i.loli.net/2019/11/16/cqyJiYlRwnTeHmj.jpg"  crossorigin="anonymous" />
        </div>
        <div class="relative w-screen h-screen flex-center">
          <img class="w-240 h-120" src="https://i.loli.net/2019/10/18/Ujf6n75o8TtIsWX.jpg"  crossorigin="anonymous" />
        </div>
        <div class="relative w-screen h-screen flex-center">
          <img class="w-240 h-120" src="https://i.loli.net/2019/10/18/buDT4YS6zUMfHst.jpg"  crossorigin="anonymous" />
        </div>
        <div class="relative w-screen h-screen flex-center">
          <img class="w-240 h-120" src="https://i.loli.net/2019/10/18/uXF1Kx7lzELB6wf.jpg"  crossorigin="anonymous" />
        </div>
        <div class="relative w-screen h-screen flex-center">
          <img class="w-240 h-120" src="https://i.loli.net/2019/11/03/RtVq2wxQYySDb8L.jpg"  crossorigin="anonymous" />
        </div>
        <div class="relative w-screen h-screen flex-center">
          <img class="w-240 h-120" src="https://i.loli.net/2019/11/16/FLnzi5Kq4tkRZSm.jpg"  crossorigin="anonymous" />
        </div>
      </div>
    </main>
    <div class="twisted-gallery fixed -z-1 inset-0 w-screen h-screen"></div>
    
    .w-240 {
      width: 60rem;
    }
    
    .h-120 {
      height: 30rem;
    }
    

    看到图片整齐的排列后,可以把它们隐藏起来了

    img {
      opacity: 0;
    }
    

    在我们的主类中,先创建一个总函数,里面未实现的函数可以先注释掉,下文会慢慢地实现它们

    class TwistedGallery extends Base {
      ...
      async init() {
        this.createScene();
        this.createPerspectiveCamera();
        this.createRenderer();
        this.createPlane();
        // await preloadImages();
        // this.createDistortImageMaterial();
        // this.createImageDOMMeshObjs();
        // this.setImagesPosition();
        // this.listenScroll();
        // this.createPostprocessingEffect()
        this.createLight();
        this.createOrbitControls();
        this.addListeners();
        this.setLoop();
      }
      ...
    }
    
    const start = () => {
      const twistedGallery = new TwistedGallery(".twisted-gallery", true);
      twistedGallery.init();
    };
    

    将HTML和webgl的单位同步

    为了在webgl中渲染出HTML里的所有img,我们需要把HTML里的img的像素信息同步给webgl

    问题来了,如何使得webgl里的1单位长度===HTML里元素的1px?

    这时我们就要搬上那张经典的透视示意图了,我们需要根据公式来计算出视场角fov

    three.js 实现图片滚动扭曲特效 | 技术点评

    class TwistedGallery extends Base {
      constructor(sel: string, debug: boolean) {
        ...
        this.cameraPosition = new THREE.Vector3(0, 0, 600);
        const fov = this.getScreenFov();
        this.perspectiveCameraParams = {
          fov,
          near: 100,
          far: 2000,
        };
      }
      getScreenFov() {
        return ky.rad2deg(
          2 * Math.atan(window.innerHeight / 2 / this.cameraPosition.z)
        );
      }
    }
    

    为了验证,我们将createPlane函数里的PlaneBufferGeometry的宽高都调为100

    three.js 实现图片滚动扭曲特效 | 技术点评

    画面中的平面宽高皆为100px,和HTML完全一致!

    createPlane函数完成了它的测试使命,可以删掉了

    确保图片加载完毕

    在显示图片之前,我们要确保图片全部加载完毕,故用imagesLoaded库来进行判断

    import imagesLoaded from "https://cdn.skypack.dev/imagesloaded@4.1.4";
    
    const preloadImages = (sel = "img") => {
      return new Promise((resolve) => {
        imagesLoaded(sel, { background: true }, resolve);
      });
    };
    

    将图片显示在webgl上

    我们创建一个DOMMeshObject类,该类是一座桥梁,负责将DOM里的信息同步至webgl世界

    首先获取DOM元素的长宽和位置,再根据这些数据计算出它们在webgl中对应的位置和大小

    class DOMMeshObject {
      constructor(
        el: Element,
        scene: THREE.Scene,
        material: THREE.Material = new THREE.MeshBasicMaterial({ color: 0xff0000 })
      ) {
        this.el = el;
        const rect = el.getBoundingClientRect();
        this.rect = rect;
        const { width, height } = rect;
        const geometry = new THREE.PlaneBufferGeometry(width, height, 10, 10);
        const mesh = new THREE.Mesh(geometry, material);
        scene.add(mesh);
        this.mesh = mesh;
      }
      setPosition() {
        const { mesh, rect } = this;
        const { top, left, width, height } = rect;
        const x = left + width / 2 - window.innerWidth / 2;
        const y = -(top + height / 2 - window.innerHeight / 2) + window.scrollY;
        mesh.position.set(x, y, 0);
      }
    }
    

    在webgl世界中创建出图片材质,并将HTML里的img元素作为贴图传给着色器,再同步好位置即可

    class TwistedGallery extends Base {
      constructor(sel: string, debug: boolean) {
        ...
        this.images = [...document.querySelectorAll("img")];
        this.imageDOMMeshObjs = [];
      }
      // 创建材质
      createDistortImageMaterial() {
        const distortImageMaterial = new THREE.ShaderMaterial({
          vertexShader: twistedGalleryMainVertexShader,
          fragmentShader: twistedGalleryMainFragmentShader,
          side: THREE.DoubleSide,
          uniforms: {
            uTexture: {
              value: 0
            }
          }
        });
        this.distortImageMaterial = distortImageMaterial;
      }
      // 创建图片DOM物体
      createImageDOMMeshObjs() {
        const { images, scene, distortImageMaterial } = this;
        const imageDOMMeshObjs = images.map((image) => {
          const texture = new THREE.Texture(image);
          texture.needsUpdate = true;
          const material = distortImageMaterial.clone();
          material.uniforms.uTexture.value = texture;
          const imageDOMMeshObj = new DOMMeshObject(image, scene, material);
          return imageDOMMeshObj;
        });
        this.imageDOMMeshObjs = imageDOMMeshObjs;
      }
      // 设置图片位置
      setImagesPosition() {
        const { imageDOMMeshObjs } = this;
        imageDOMMeshObjs.forEach((obj) => {
          obj.setPosition();
        });
      }
    }
    

    顶点着色器twistedGalleryMainVertexShader跟模板一致

    varying vec2 vUv;
    
    void main(){
        vec4 modelPosition=modelMatrix*vec4(position,1.);
        vec4 viewPosition=viewMatrix*modelPosition;
        vec4 projectedPosition=projectionMatrix*viewPosition;
        gl_Position=projectedPosition;
        
        vUv=uv;
    }
    

    片元着色器twistedGalleryMainFragmentShader将贴图作为颜色显示了出来

    uniform sampler2D uTexture;
    
    varying vec2 vUv;
    
    void main(){
        vec2 newUv=vUv;
        vec4 texture=texture2D(uTexture,newUv);
        vec3 color=texture.rgb;
        gl_FragColor=vec4(color,1.);
    }
    

    three.js 实现图片滚动扭曲特效 | 技术点评

    经过一番努力,图片总算显示在画面上了,接下来我们就要开始让它跟随画面滚动起来了

    滚动起来

    这里的滚动监听原生的scroll事件即可实现,但本文并不会这么做,为什么呢?因为原生的scroll事件只能获取滚动的位置,而无法获取滚动的速度。如果用户滚得快,我们也要在我们的特效上体现出来。因此,我们将采用locomotive-scroll这个库,它能捕获到用户滚动的位置和速度

    import LocomotiveScroll from "https://cdn.skypack.dev/locomotive-scroll@4.1.0";
    
    class TwistedGallery extends Base {
      // 监听滚动
      listenScroll() {
        const scroll = new LocomotiveScroll({
          getSpeed: true
        });
        scroll.on("scroll", () => {
          this.setImagesPosition();
        });
        this.scroll = scroll;
      }
    }
    

    three.js 实现图片滚动扭曲特效 | 技术点评

    图片终于在WEBGL的世界里滚动起来了,两个世界同步完成~

    接下来开始我们的重头戏——扭曲特效!

    扭曲特效

    开头的那个特效可以看出是全屏幕上对图片进行扭曲的,因此我们将采用后期处理postprocessing来实现,它提供了屏幕tDiffuse

    import { RenderPass } from "https://cdn.skypack.dev/three@0.124.0/examples/jsm/postprocessing/RenderPass.js";
    import { ShaderPass } from "https://cdn.skypack.dev/three@0.124.0/examples/jsm/postprocessing/ShaderPass.js";
    import gsap from "https://cdn.skypack.dev/gsap@3.6.0";
    
    class TwistedGallery extends Base {
      constructor(sel: string, debug: boolean) {
        ...
        this.scrollSpeed = 0;
      }
      // 创建后期处理特效
      createPostprocessingEffect() {
        const composer = new EffectComposer(this.renderer);
        const renderPass = new RenderPass(this.scene, this.camera);
        composer.addPass(renderPass);
        const customPass = new ShaderPass({
          vertexShader: twistedGalleryPostprocessingVertexShader,
          fragmentShader: twistedGalleryPostprocessingFragmentShader,
          uniforms: {
            tDiffuse: {
              value: null
            },
            uRadius: {
              value: 0.75
            },
            uPower: {
              value: 0
            }
          }
        });
        customPass.renderToScreen = true;
        composer.addPass(customPass);
        this.composer = composer;
        this.customPass = customPass;
      }
      // 设置滚动速度
      setScrollSpeed() {
        const scrollSpeed = this.scroll.scroll.instance.speed || 0;
        gsap.to(this, {
          scrollSpeed: Math.min(Math.abs(scrollSpeed) * 1.25, 2),
          duration: 1
        });
      }
      // 动画
      update() {
        if (this.customPass) {
          this.setScrollSpeed();
          this.customPass.uniforms.uPower.value = this.scrollSpeed;
        }
      }
    }
    

    可以看出我们将滚动速度作为uniform传入了着色器,并且用gsap来实现缓动效果,接下来就是着色器部分了

    顶点着色器twistedGalleryPostprocessingVertexShader跟上文所说的模板一致,故略过

    片元着色器twistedGalleryPostprocessingFragmentShader负责动态计算uv坐标,实现起来稍有难度,关键是多调,里面的值用gl_FragColor显示出来,这样就容易理解值的变化

    uniform sampler2D tDiffuse;
    uniform float uRadius;
    uniform float uPower;
    
    varying vec2 vUv;
    
    void main(){
        vec2 pivot=vec2(.5);
        vec2 d=vUv-pivot;
        float rDist=length(d);
        float gr=pow(rDist/uRadius,uPower);
        float mag=2.-cos(gr-1.);
        vec2 uvR=pivot+d*mag;
        vec4 color=texture2D(tDiffuse,uvR);
        gl_FragColor=color;
    }
    

    three.js 实现图片滚动扭曲特效 | 技术点评

    项目地址

    Twisted Gallery

    最后

    本文所实现的特效仅仅是众多特效中的一种,但真正重要的是如何将HTML与webgl世界所同步起来的这个过程,一旦掌握了这一方法,想做出一个非常酷炫的网站将不再是一个难事。

    本文正在参与「掘金 2021 春招闯关活动」, 点击查看活动详情


    起源地下载网 » three.js 实现图片滚动扭曲特效 | 技术点评

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元