react.js取消滚动条_Three.js和React中的滚动,折射和着色器效果

react.js取消滚动条

react.js取消滚动条

Featured image for the tutorial Scroll, Refraction and Shader Effects with Three.js and React

In this tutorial I will show you how to take a couple of established techniques (like tying things to the scroll-offset), and cast them into re-usable components. Composition will be our primary focus.

在本教程中,我将向您展示如何采用一些已建立的技术(例如,将内容绑定到滚动偏移),并将其转换为可重用的组件。 作文将是我们的主要重点。

In this tutorial we will:

在本教程中,我们将:

  • build a declarative scroll rig

    建立一个声明式滚动装备
  • mix HTML and canvas

    混合HTML和画布
  • handle async assets and loading screens via React.Suspense

    通过React.Suspense处理异步资产并加载屏幕
  • add shader effects and tie them to scroll

    添加着色器效果并将其绑定到滚动
  • and as a bonus: add an instanced variant of Jesper Vos multiside refraction shader

    并作为奖励:添加Jesper Vos多边折射着色器的实例变体

配置(Setting up)

We are using React, hooks, Three.js and react-three-fiber. The latter is a renderer for Three.js which allows us to declare the scene graph by breaking up tasks into self-contained components. However, you still need to know a bit of Three.js. All there is to know about react-three-fiber you can find on the GitHub repo’s readme. Check out the tutorial on alligator.io, which goes into the why and how.

我们正在使用ReacthooksThree.jsreact-three-fiber 。 后者是Three.js的渲染器,它允许我们通过将任务分解为独立的组件来声明场景图。 但是,您仍然需要了解Three.js。 您可以在GitHub存储库的自述文件中找到关于react-three-fiber的所有知识。 查看alligator.io上教程,其中介绍了原因和方式。

We don’t emulate a scroll bar, which would take away browser semantics. A real scroll-area in front of the canvas with a set height and a listener is all we need.

我们没有模拟滚动条,滚动条会占用浏览器的语义。 我们需要的是在画布前面的真实滚动区域,具有一个固定的高度和一个侦听器。

I decided to divide the content into:

我决定将内容分为:

  • virtual content sections

    虚拟内容sections

  • and pages, each 100vh long, this defines how long the scroll area is

    pages ,每个100vh长,这定义了滚动区域的长度

function App() {
  const scrollArea = useRef()
  const onScroll = e => (state.top.current = e.target.scrollTop)
  useEffect(() => void onScroll({ target: scrollArea.current }), [])
  return (
    <>
      <Canvas orthographic>{/* Contents ... */}</Canvas>
      <div ref={scrollArea} onScroll={onScroll}>
        <div style={{ height: `${state.pages * 100}vh` }} />
      </div>

scrollTop is written into a reference because it will be picked up by the render-loop, which is carrying out the animations. Re-rendering for often occurring state doesn’t make sense.

scrollTop写入到引用中,因为它将被正在执行动画的render-loop拾取。 对经常发生的状态进行重新渲染没有任何意义。

A first-run effect synchronizes the local scrollTop with the actual one, which may not be zero.

首次运行效果将使本地scrollTop与实际值同步,该值可能不为零。

建立声明式滚动装置 (Building a declarative scroll rig)

演示地址

There are many ways to go about it, but generally it would be nice if we could distribute content across the number of sections in a declarative way while the number of pages defines how long we have to scroll. Each content-block should have:

有很多解决方法,但总的来说,如果我们可以以声明性的方式在多个部分中分配内容,而页面数则定义了我们需要滚动多长时间,那将是很好的选择。 每个内容块应具有:

  • an offset, which is the section index, given 3 sections, 0 means start, 2 means end, 1 means in between

    一个offset ,它是部分索引,给定3个部分,0表示开始,2表示结束,1表示中间

  • a factor, which gets added to the offset position and subtracted using scrollTop, it will control the blocks speed and direction

    一个factor ,它被添加到偏移位置并使用scrollTop减去,它将控制块的速度和方向

Blocks should also be nestable, so that sub-blocks know their parents’ offset and can scroll along.

块也应该是可嵌套的,以便子块知道其父对象的偏移量并可以滚动。

const offsetContext = createContext(0)

function Block({ children, offset, factor, ...props }) {
  const ref = useRef()
  // Fetch parent offset and the height of a single section
  const { offset: parentOffset, sectionHeight } = useBlock()
  offset = offset !== undefined ? offset : parentOffset
  // Runs every frame and lerps the inner block into its place
  useFrame(() => {
    const curY = ref.current.position.y
    const curTop = state.top.current
    ref.current.position.y = lerp(curY, (curTop / state.zoom) * factor, 0.1)
  })
  return (
    <offsetContext.Provider value={offset}>
      <group {...props} position={[0, -sectionHeight * offset * factor, 0]}>
        <group ref={ref}>{children}</group>
      </group>
    </offsetContext.Provider>
  )
}

This is a block-component. Above all, it wraps the offset that it is given into a context provider so that nested blocks and components can read it out. Without an offset it falls back to the parent offset.

这是一个块组件。 最重要的是,它将给定的偏移量包装到上下文提供程序中,以便嵌套的块和组件可以读出该偏移量。 如果没有偏移,它将回退到父偏移。

It defines two groups. The first is for the target position, which is the height of one section multiplied by the offset and the factor. The second, inner group is animated and cancels out the factor. When the user scrolls to the given section offset, the block will be centered.

它定义了两个组。 第一个是针对目标位置的,即目标部分的高度乘以偏移量和系数。 第二个内部组具有动画效果,并消除了该因子。 当用户滚动到给定的部分偏移时,块将居中。

We use that along with a custom hook which allows any component to access block-specific data. This is how any component gets to react to scroll.

我们将其与自定义钩子一起使用,该钩子允许任何组件访问特定于块的数据。 这就是任何组件对滚动做出React的方式。

function useBlock() {
  const { viewport } = useThree()
  const offset = useContext(offsetContext)
  const canvasWidth = viewport.width / zoom
  const canvasHeight = viewport.height / zoom
  const sectionHeight = canvasHeight * ((pages - 1) / (sections - 1))
  // ...
  return { offset, canvasWidth, canvasHeight, sectionHeight }
}

We can now compose and nest blocks conveniently:

现在,我们可以方便地组合和嵌套块了:

<Block offset={2} factor={1.5}>
  <Content>
    <Block factor={-0.5}>
      <SubContent />
    </Block>
  </Content>
</Block>

Anything can read from block-data and react to it (like that spinning cross):

任何东西都可以从块数据中读取并对其作出React(就像旋转的十字一样):

function Cross() {
  const ref = useRef()
  const { viewportHeight } = useBlock()
  useFrame(() => {
    const curTop = state.top.current
    const nextY = (curTop / ((state.pages - 1) * viewportHeight)) * Math.PI
    ref.current.rotation.z = lerp(ref.current.rotation.z, nextY, 0.1)
  })
  return (
    <group ref={ref}>

混合HTML和canvas,并处理资产 (Mixing HTML and canvas, and dealing with assets)

演示地址

使HTML与3D世界保持同步(Keeping HTML in sync with the 3D world)

We want to keep layout and text-related things in the DOM. However, keeping it in sync is a bit of a bummer in Three.js, messing with createElement and camera calculations is no fun.

我们希望在DOM中保留与布局和文本相关的内容。 但是,在Three.js中,使其保持同步有点令人讨厌,将createElement和照相机计算弄混是没有意思的。

In three-fiber all you need is the <Dom /> helper (@beta atm). Throw this into the canvas and add declarative HTML. This is all it takes for it to move along with its parents’ world-matrix.

在三光纤中,您需要的是<Dom />帮助器(@beta atm)。 将其扔到画布上并添加声明性HTML。 这是它与父母的世界矩阵一起前进所需要的一切。

<group position={[10, 0, 0]}>
  <Dom><h1>hello</h1></Dom>
</group>
辅助功能 (Accessibility)

If we strictly divide between layout and visuals, supporting a11y is possible. Dom elements can be behind the canvas (via the prepend prop), or in front of it. Make sure to place them in front if you need them to be accessible.

如果我们严格区分布局和视觉效果,则可以支持a11y。 Dom元素可以在画布后面(通过prepend道具),也可以在画布前面。 如果需要方便使用,请确保将它们放在前面。

响应度,媒体查询等 (Responsiveness, media-queries, etc.)

While the DOM fragments can rely on CSS, their positioning overall relies on the scene graph. Canvas elements on the other hand know nothing of the sort, so making it all work on smaller screens can be a bit of a challenge.

尽管DOM片段可以依赖CSS,但它们的总体定位取决于场景图。 另一方面,Canvas元素对此一无所知,因此使其全部在较小的屏幕上工作可能会有些挑战。

Fortunately, three-fiber has auto-resize inbuilt. Any component requesting size data will be automatically informed of changes.

幸运的是,三芯内置了自动调整大小功能。 任何要求尺寸数据的组件都会自动通知更改。

You get:

你得到:

  • viewport, the size of the canvas in its own units, must be divided by camera.zoom for orthographic cameras

    viewport (画布本身的大小)必须除以camera.zoom用于正交相机)

  • size, the size of the screen in pixels

    size ,屏幕大小(以像素为单位)

const { viewport, size } = useThree()

Most of the relevant calculations for margins, maxWidth and so on have been made in useBlock.

有关margin,maxWidth等的大多数相关计算都是在useBlock

通过React.Suspense处理异步资产和加载屏幕 (Handling async assets and loading screens via React.Suspense)

Concerning assets, Reacts Suspense allows us to control loading and caching, when components should show up, in what order, fallbacks, and how errors are handled. It makes something like a loading screen, or a start-up animation almost too easy.

关于资产,Reacts Suspense允许我们控制加载和缓存,何时显示组件,以什么顺序,回退以及如何处理错误。 它使诸如加载屏幕或启动动画之类的操作几乎变得太容易了。

The following will suspend all contents until each and every component, even nested ones, have their async data ready. Meanwhile it will show a fallback. When everything is there, the <Startup /> component will render along with everything else.

下面的命令将暂停所有内容,直到每个组件(甚至是嵌套的组件)都已准备好其异步数据为止。 同时,它将显示回退。 当所有内容都存在时, <Startup />组件将与其他所有内容一起呈现。

<Suspense fallback={<Fallback />}>
  <AsyncContent />
  <Startup />
</Suspense>

In three-fiber you can suspend a component with the useLoader hook, which takes any Three.js loader, then loads (and caches) assets with it.

在三光纤中,您可以使用useLoader挂接挂起组件,该挂接使用任何Three.js加载器,然后使用它加载(和缓存)资产。

function Image() {
  const texture = useLoader(THREE.TextureLoader, "/texture.png")
  // It will only get here if the texture has been loaded
  return (
    <mesh>
      <meshBasicMaterial attach="material" map={texture} />

添加着色器效果并将其绑定以滚动 (Adding shader effects and tying them to scroll)

演示地址

The custom shader in this demo is a Frankenstein based on the Three.js MeshBasicMaterial, plus:

此演示中的自定义着色器是基于Three.js MeshBasicMaterial的科学怪人,以及:

The relevant portion of code in which we feed the shader block-specific scroll data is this one:

我们向其中提供特定于着色器块的滚动数据的代码的相关部分是这一部分:

material.current.scale =
  lerp(material.current.scale, offsetFactor - top / ((pages - 1) * viewportHeight), 0.1)
material.current.shift =
  lerp(material.current.shift, (top - last) / 150, 0.1)

添加钻石 (Adding Diamonds)

演示地址

The technique is explained in full detail in the article Real-time Multiside Refraction in Three Steps by Jesper Vos. I placed Jesper’s code into a re-usable component, so that it can be mounted and unmounted, taking care of all the render logic. I also changed the shader slightly to enable instancing, which now allows us to draw dozens of these onto the screen without hitting a performance snag anytime soon.

Jesper Vos的“三步实时多面折射”中详细介绍了该技术。 我将Jesper的代码放入一个可重用的组件中,以便可以将其挂载和卸载,并注意所有渲染逻辑。 我还略微更改了着色器以启用实例化,这现在使我们能够将数十个着色器绘制到屏幕上,而不会很快遇到性能障碍。

The component reads out block-data like everything else. The diamonds are put into place according to the scroll offset by distributing the instanced meshes. This is a relatively new feature in Three.js.

该组件像其他所有内容一样读取块数据。 通过分配实例化的网格,根据滚动偏移量将钻石放置到位。 这是Three.js中的一个相对较新的功能

结语 (Wrapping up)

This tutorial may give you a general idea, but there are many things that are possible beyond the generic parallax; you can tie anything to scroll. Above all, being able to compose and re-use components goes a long way and is so much easier than dealing with a soup of code fragments whose implicit contracts span the codebase.

本教程可能为您提供一个总体思路,但是除了通用视差之外,还有许多其他可能。 您可以绑任何东西来滚动。 最重要的是,能够组成和重用组件很长的路要走,而且比处理其隐式契约跨越代码库的大量代码片段要容易得多。

翻译自: https://tympanus.net/codrops/2019/12/16/scroll-refraction-and-shader-effects-in-three-js-and-react/

react.js取消滚动条

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值