27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 14islands
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
20 | React App
21 |
22 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/ImageCube.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import { ScrollScene, UseCanvas, useScrollbar, useScrollRig, styles, useImageAsTexture } from '@14islands/r3f-scroll-rig'
3 | import { useFrame } from '@react-three/fiber'
4 | import { MeshWobbleMaterial } from '@react-three/drei'
5 | import { a, useSpring, config } from '@react-spring/three'
6 |
7 | export function ImageCube({ src, ...props }) {
8 | const el = useRef()
9 | const img = useRef()
10 | const { hasSmoothScrollbar } = useScrollRig()
11 | return (
12 | <>
13 |
14 |
22 |
23 |
24 | {hasSmoothScrollbar && (
25 |
26 | {(props) => }
27 |
28 | )}
29 | >
30 | )
31 | }
32 |
33 | function WebGLCube({ img, scale, inViewport }) {
34 | const mesh = useRef()
35 | const texture = useImageAsTexture(img)
36 | const { scroll } = useScrollbar()
37 |
38 | useFrame((_, delta) => {
39 | mesh.current.material.factor += scroll.velocity * 0.005
40 | mesh.current.material.factor *= 0.95
41 | })
42 |
43 | const spring = useSpring({
44 | scale: inViewport ? scale.times(1) : scale.times(0),
45 | config: inViewport ? config.wobbly : config.stiff,
46 | delay: inViewport ? 200 : 0
47 | })
48 |
49 | return (
50 |
51 |
52 |
63 |
64 | )
65 | }
66 |
--------------------------------------------------------------------------------
/src/Text.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import { ScrollScene, UseCanvas, useScrollRig, styles } from '@14islands/r3f-scroll-rig'
3 | import { MeshDistortMaterial } from '@react-three/drei'
4 | import { WebGLText } from '@14islands/r3f-scroll-rig/powerups'
5 |
6 | export const Headline = ({ children, ...props }) => (
7 |
8 | {children}
9 |
10 | )
11 |
12 | export const Subtitle = ({ children, ...props }) => (
13 |
14 | {children}
15 |
16 | )
17 |
18 | export const BodyCopy = Text
19 |
20 | export function Text({ children, wobble, className, font = 'fonts/Poppins-Regular.woff', as: Tag = 'span', ...props }) {
21 | const el = useRef()
22 | const { hasSmoothScrollbar } = useScrollRig()
23 | return (
24 | <>
25 | {/*
26 | This is the real DOM text that we want to replace with WebGL
27 | `styles.transparentColorWhenSmooth` sets the text to transparent when SmoothScrollbar is enabled
28 | The benefit of using transparent color is that the real DOM text is still selectable
29 |
30 | display: 'block' gives a more solid calculation for spans
31 | */}
32 |
33 | {children}
34 |
35 |
36 | {hasSmoothScrollbar && (
37 |
38 |
39 | {(props) => (
40 | // WebGLText is a helper component from the scroll-rig that will
41 | // use getComputedStyle to match font size, letter spacing and color
42 |
48 | {wobble && }
49 | {children}
50 |
51 | )}
52 |
53 |
54 | )}
55 | >
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/src/Lens.jsx:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { useRef, useState } from "react";
3 | import { createPortal, useFrame, useThree } from "@react-three/fiber";
4 | import { useFBO, useGLTF, MeshTransmissionMaterial } from "@react-three/drei";
5 | import { easing } from "maath";
6 |
7 | export function Lens({ children, damping = 0.14, ...props }) {
8 | const ref = useRef();
9 | const { nodes } = useGLTF("glb/lens-transformed2.glb");
10 | const buffer = useFBO();
11 | const viewport = useThree((state) => state.viewport);
12 | const [scene] = useState(() => new THREE.Scene());
13 | useFrame((state, delta) => {
14 | // Tie lens to the pointer
15 | // getCurrentViewport gives us the width & height that would fill the screen in threejs units
16 | // By giving it a target coordinate we can offset these bounds, for instance width/height for a plane that
17 | // sits 15 units from 0/0/0 towards the camera (which is where the lens is)
18 | const viewport = state.viewport.getCurrentViewport(state.camera, [0, 0, 1]);
19 |
20 | easing.damp3(
21 | ref.current.position,
22 | [
23 | (state.pointer.x * viewport.width) / 2,
24 | (state.pointer.y * viewport.height) / 2,
25 | 1,
26 | ],
27 | damping,
28 | delta
29 | );
30 | // This is entirely optional but spares us one extra render of the scene
31 | // The createPortal below will mount the children of into the new THREE.Scene above
32 | // The following code will render that scene into a buffer, whose texture will then be fed into
33 | // a plane spanning the full screen and the lens transmission material
34 | state.gl.setRenderTarget(buffer);
35 | state.gl.setClearColor("#ecedef");
36 | state.gl.render(scene, state.camera);
37 | state.gl.setRenderTarget(null);
38 | });
39 |
40 | return (
41 | <>
42 | {createPortal(children, scene)}
43 |
44 |
45 |
46 |
47 |
54 |
64 |
65 | >
66 | );
67 | }
68 |
69 | useGLTF.preload("/lens-transformed2.glb");
70 |
--------------------------------------------------------------------------------
/src/Image.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, Suspense } from "react";
2 | import {
3 | UseCanvas,
4 | useScrollRig,
5 | useImageAsTexture,
6 | styles,
7 | } from "@14islands/r3f-scroll-rig";
8 | import { ParallaxScrollScene } from "@14islands/r3f-scroll-rig/powerups";
9 | import { Image as DreiImage, Circle } from "@react-three/drei";
10 | import { useFrame } from "@react-three/fiber";
11 | import { clamp } from "three/src/math/MathUtils";
12 | import { DoubleSide } from "three";
13 |
14 | export function Image({ src, parallaxSpeed = 1, ...props }) {
15 | const el = useRef();
16 | const img = useRef();
17 | const { hasSmoothScrollbar } = useScrollRig();
18 | return (
19 | <>
20 |
21 |
29 |
30 |
31 | {hasSmoothScrollbar && (
32 |
33 |
34 | {(props) => (
35 | }>
36 |
37 |
38 | )}
39 |
40 |
41 | )}
42 | >
43 | );
44 | }
45 |
46 | function WebGLImage({ imgRef, scrollState, dir, ...props }) {
47 | const ref = useRef();
48 |
49 | // Load texture from the and suspend until it's ready
50 | const texture = useImageAsTexture(imgRef);
51 |
52 | useFrame(({ clock }) => {
53 | // scrollState.visibility is 0 when image enters viewport at bottom and 1 when image is fully visible
54 | ref.current.material.grayscale = clamp(
55 | 1 - scrollState.visibility ** 3,
56 | 0,
57 | 1
58 | );
59 | // scrollState.progress is 0 when image enters viewport at bottom and 1 when image left the viewport at the top
60 | ref.current.material.zoom = 1 + scrollState.progress * 0.66;
61 | // scrollState.viewport is 0 when image enters viewport at bottom and 1 when image reached top of viewport
62 | ref.current.material.opacity = clamp(scrollState.viewport * 3, 0, 1);
63 | });
64 |
65 | // Use the component from Drei
66 | return ;
67 | }
68 |
69 | function LoadingIndicator({ scale }) {
70 | const box = useRef();
71 | useFrame(({ clock }) => {
72 | box.current.rotation.y = clock.getElapsedTime() * 5;
73 | });
74 | return (
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | );
84 | }
85 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense, useRef, useState } from "react";
2 | import { GlobalCanvas, SmoothScrollbar } from "@14islands/r3f-scroll-rig";
3 | import { Environment, Loader } from "@react-three/drei";
4 |
5 | import { BodyCopy, Headline, Subtitle } from "./Text";
6 | import { Image } from "./Image";
7 | import { ImageCube } from "./ImageCube";
8 | import { WebGLBackground } from "./WebGLBackground";
9 | import { Lens } from "./Lens";
10 | import CodropsFrame from "./CodropsFrame";
11 | import EffectsToggle from "./EffectsToggle";
12 |
13 | import "@14islands/r3f-scroll-rig/css";
14 |
15 | // Photos by Maxim Berg on Unsplash
16 |
17 | export default function App() {
18 | const eventSource = useRef();
19 | const [enabled, setEnabled] = useState(true);
20 |
21 | return (
22 | // We attach events onparent div in order to get events on both canvas and DOM
23 |
63 |
64 | Progressively enhance your React website with WebGL using
65 | r3f-scroll-rig, React Three Fiber and Three.js
66 |
67 |
68 |
69 |
70 |
74 |
75 |
76 |
77 | We use CSS to create a responsive layout.
78 |
79 |
80 | A Canvas on top tracks DOM elements and enhance them with WebGL.
81 |
82 |
83 |
84 |
85 |
86 | Try turning off WebGL using the button in the sticky header.
87 | You’ll notice smooth scrolling is disabled, and all scroll-bound
88 | WebGL effects disappears.
89 |
90 |
91 |
92 |
93 |
98 |
103 |
104 |
105 |
106 |
107 | Thanks to Threejs we can also render 3D geometry or models. The
108 | following image is replaced by a box. Try scrolling hard to make
109 | it wiggle.
110 |
111 |
112 |
113 |
114 |
118 |
119 |
120 |
121 | Most websites use a mix of WebGL and HTML.
122 |
123 |
124 | However, the Lens refraction requires all images and text to be
125 | WebGL.
126 |
127 |
128 |