Collective #646








Collective 646 Item Image

How We Improved SmashingMag Performance

In this article, you’ll learn about the changes made on Smashing Magazine — running on JAMStack with React — to optimize the web performance and improve the Core Web Vitals metrics.

Read it













Collective 646 Item Image

Wldlght

Follow an amazing journey in Japan with this projection mapping project, Wldlght.

Check it out






Collective 646 Item Image

Formality

A designless, multistep, conversational, secure, all-in-one WordPress forms plugin.

Check it out





The post Collective #646 appeared first on Codrops.

Awesome Demos Roundup #14

These past few weeks we’ve collected some really nice web experiments: from Shader magic to SVG filter trickery, interactive poetry and particle madness — there’s something for every creative coder’s heart.

We hope you enjoy this collection and find it inspiring!

Stacking Cards Effect

by Claudia Romano

Bouncing Balls

by Meto Trajkovski

Starfields GLShader

by Paul J Karlik

verlet

by Kitasenju Design

Spring pagination

by Mikael Ainalem

Turbulence

by Janxalot

Flight

by Mat Sz

ThreeJS Maths of Heart

by TheFrost

Calm Spikes

by Anna the Scavenger

Editable Neumorphic Text

by Adam Kuhn

Care Bear NEEDS Love (mousedown/touchstart)

by Jhey Tompkins

Curl Simulation

by Daniel Velasquez

Banksy – Valentine’s Day

by David Fitzgibbon

Wavy Color Cube

by Ryan Mulligan

intimacy

by Thibaud Goiffon

What’s behind ?

by Kevin Levron

threejs-nuxt-sample

by Misaki Nakano

The Three Graces (React App)

by Paul Henschel

Wind field – How To

by Louis Hoebregts

Pure CSS Claw Crane

by Jon Kantner

void-merge-2048

by Arthur

Corgo’s with Jason

by Mandy Michael

Pixel Dust

by Paul Neave

Pure CSS Responsive Browser Template

by Adam Marsden

Random, Cos and Sin

by Kevin Levron

Diagonal Layouts in 2020

by Nils Binder

r3f cannon instanced physics

by Paul Henschel

Liquid Grid

by by Kevin Levron

Memphis Beauty

by Anna the Scavenger

Tower Time

by Adrian Rampy

shader moire

by masuwa

Isometric City w/ Airplane

by Adam Kuhn

Awesome Demos Roundup #14 was written by Mary Lou and published on Codrops.

How to Unroll Images with Three.js

Do you like to roll up things? Or maybe you prefer rolling them out?

I spent my childhood doing crepes. I loved those rolls.

I guess, the time has come to unroll all kinds of things, including images. And to unroll as many rolls as possible I decided to automate this process with a bit of JavaScript and WebGL.

rolled crepe

The setup

I will be using Three.js for this animation, and set up a basic scene with some planes.

Just as in this tutorial by Paul Henschel, we will replace all the images with those PlaneGeometry objects. So, we will simply work with HTML images:

<img src="owl.jpg" class="js-image" />

Once the page loads and a “loaded” class is set to the body, we will hide those images:

.loaded .js-image {
	opacity: 0;
}

Then we’ll get the dimensions of each image and position them into our 3D Planes, exactly there where the DOM image elements were.

MY_SCENE.add(
	new THREE.Mesh(
		new PlaneGeometry(640,480),  // some size of image
		new Material({texture:’owl.jpg’) // texture i took from DOM
	)
)

Because rolling things with CSS or SVG is next to impossible, I needed all the power of WebGL, and I moved parts of my page into that world. But I like the idea of progressive enhancement, so we still have the usual images there in the markup, and if for some reason the JavaScript or WebGL isn’t working, we would still see them.

After that, its quite easy to sync the scroll of the page with the 3D scene position. For that I used a custom smooth scroll described in this tutorial. You should definitely check it out, as it works regardless of platform and actions that you use to scroll. You know this is usually the biggest pain with custom scrolling libraries.

Let’s rock and roll

So, we have what we want in 3D! Now, with power of shaders we can do anything!

Well, anything we are capable of, at least =).

Red parts are WebGL, everything else is HTML

I started from the end. I imagine every animation as some function which takes a number between 0 and 1 (I call it ‘progress’) and returns a visual. So the result of my imaginary RollFunction(0) should be something rolled. And RollFunction(1), should be just the default state of the plane. That’s how I got the last line of my animation:

vec3 finalPosition = mix(RolledPosition, DefaultPosition, progress);

I had DefaultPosition from the start, it’s usually called ‘position’. So all I needed is to create the RolledPosition and change my progress!

I figured out a couple of ways to do that. I could have had another object (like an .obj file) done in some editor, or even fully exported the animation from Blender or another program.

But I decided to transform DefaultPosition into RolledPosition with a couple of math functions inside my Vertex shader. So, imagine we have a plane lying in the Z plane, so, to roll something like that, you could do the following:

RolledPosition.x = RADIUS*cos(position.x);
RolledPosition.y = position.y; // stays the same
RolledPosition.z = RADIUS*sin(position.x);

If you ever tried to draw a circle yourself, you can easily guess where this is coming from. If not here is a famous GIF illustrating that visualizes that sheds some light on it:

See how those two functions are essentially part of circle

Of course this would just make a (not so) perfect tube out of our plane, but just adding a couple of parameters here, we can make it into a real roll:

RADIUS *= 1 - position.x; // so it gets smaller when we roll the plane
newposition.z =  RADIUS*sin(position.x*TWO_PI);
newposition.x =  RADIUS*cos(position.x*TWO_PI); 

And you will get something like that:

This is done with the help of the D3 library, but the idea is the same.

This two-dimensional animation has really helped me to get the idea of rolling things. So I recommend that you to dig into the code, it’s quite interesting!

After that step, it was a matter of time and arithmetics to play a bit with the progress, so I got this kind of animation for my plane:

There are a number of other steps, to make the angle parametric, and to make it a bit more beautiful with subtle shadows, but that’s the most important part right here. Sine and Cosine functions are often at the core of all the cool things you see on the web! =).

So let me know if you like these rolling effects, and how much time you have spent scrolling back and forth just to see ‘em roll! Have a nice day! =)

How to Unroll Images with Three.js was written by Yuriy Artyukh and published on Codrops.

Scroll, Refraction and Shader Effects in 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
  • handle async assets and loading screens via React.Suspense
  • add shader effects and tie them to scroll
  • and as a bonus: add an instanced variant of Jesper Vos multiside refraction shader

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.

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
  • and pages, each 100vh long, this defines how long the scroll area is
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.

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

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
  • a factor, which gets added to the offset position and subtracted using scrollTop, it will control the blocks speed and direction

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.

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):

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}>

Mixing HTML and canvas, and dealing with assets

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.

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.

<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.

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.

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
  • size, the size of the screen in pixels
const { viewport, size } = useThree()

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

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.

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.

<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.

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:

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.

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.

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.

Scroll, Refraction and Shader Effects in Three.js and React was written by Paul Henschel and published on Codrops.

Collective #540


C540_paint

Largest Contentful Paint

Philip Walton explains how to make it easier to know when a page’s important content has loaded using the Largest Contentful Paint (LCP) API.

Read it





C540_extracss

Extra.css

A CSS Houdini library giving some cool features to your site (turn on Experimental Web Platform features or use Chrome Canary to see the effects). By Una Kravets.

Check it out









C540_accessibilityamp

Amphora.

Ethan Marcotte discovers some accessibility issues with the AMP Story format.

Read it



C540_inter

The birth of Inter

Learn how the new open-source typeface used by GitHub and Mozilla came to be in this article by Carmel DeAmicis.

Read it










C540_dotnet

Uno Platform

In case you missed it: Uno let’s you build mobile, desktop and WebAssembly apps with C# and XAML.

Check it out

Collective #540 was written by Pedro Botelho and published on Codrops.

Creating Animations Using React Spring

Have you ever needed animation in your React application? Traditionally, implementing animation has not an easy feat to accomplish. But now, thanks to Paul Henschel, we there’s a new React tool just for that. react-spring inherits from animated and react-motion for interpolations, optimized performance, and a clean API.

In this tutorial, we will be looking at two of the five hooks included in react-spring, specifically useSpring and useTrail. The examples we’ll implement make use of both APIs.

If you want to follow along, install react-spring to kick things off:

## yarn
yarn add react-spring

## npm
npm install react-spring --save

Spring

The Spring prop can be used for moving data from one state to another. We are provided with a from and to prop to help us define the animation’s starting and ending states. The from prop determines the initial state of the data during render, while we use to in stating where it should to be after the animation completes.

In the first example, we will make use of the render prop version of creating spring animation.

See the Pen
react spring 1
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

On initial render, we want to hide a box, and slide it down to the center of the page when a button is clicked. It’s possible to do this without making use of react-spring, of course, but we want to animate the entrance of the box in to view and only when the button is clicked.

class App extends React.Component {
  state = {
    content: false
  }

  displayContent = (e) => {
    e.preventDefault()
    this.setState({ content: !this.state.content })
  }

  render() {
    return (
      <div className="container">
          // The button that toggles the animation
          <div className="button-container">
            <button
              onClick={this.displayContent}
              className="button">
                Toggle Content
            </button>
          </div>
          {
            !this.state.content ?
              (
                // Content in the main container
                <div>
                  No Content
                </div>
              )
            : (
              // We call Spring and define the from and to props
              <Spring
                from={{
                  // Start invisible and offscreen
                  opacity: 0, marginTop: -1000,
                }}
                to={{
                  // End fully visible and in the middle of the screen
                  opacity: 1, marginTop: 0,
                }}
              >
                { props => (
                  // The actual box that slides down
                  <div  className="box" style={ props }>
                    <h1>
                      This content slid down. Thanks to React Spring
                    </h1>
                  </div>
              )}
            </Spring>
            )
        }
      </div>
    )
  }
}

Most of the code is basic React that you might already be used to seeing. We make use of react-spring in the section where we want to conditionally render the content after the value of content has been changed to true. In this example, we want the content to slide in from the top to the center of the page, so we make use of marginTop and set it to a value of -1000 to position it offscreen, then define an opacity of 0 as our values for the from prop. This means the box will initially come from the top of the page and be invisible.

Clicking the button after the component renders updates the state of the component and causes the content to slide down from the top of the page.

We can also implement the above example using the hooks API. For this, we’ll be making use of the useSpring and animated hooks, alongside React’s built-in hooks.

const App = () => {
  const [contentStatus, displayContent] = React.useState(false);
  // Here's our useSpring Hook to define start and end states
  const contentProps = useSpring({
    opacity: contentStatus ? 1 : 0,
    marginTop: contentStatus ? 0 : -1000
  })
  return (
    <div className="container">
      <div className="button-container">
        <button
          onClick={() => displayContent(a => !a)}
          className="button">Toggle Content</button>
      </div>
        {
          !contentStatus ?
            (
              <div>
                No Content
              </div>
            )
          : (
            // Here's where the animated hook comes into play
            <animated.div className="box" style={ contentProps }>
              <h1>
                This content slid down. Thanks to React Spring
              </h1>
            </animated.div>
          )
        }
    </div>
  )
}

First, we set up the state for the component. Then we make use of useSpring to set up the animations we need. When contentStatus is true, we want the values of marginTop and opacity to be 0 and 1, respectively. Else, they should be -1000 and 0. These values are assigned to contentProps which we then pass as props to animated.div.

When the value of contentStatus changes, as a result of clicking the button, the values of opacity and marginTop changes alongside. This cause the content to slide down.

See the Pen
react spring 2
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

Trail

The Trail prop animates a list of items. The animation is applied to the first item, then the siblings follow suit. To see how that works out, we’ll build a component that makes a GET request to fetch a list of users, then we will animate how they render. Like we did with Spring, we’ll see how to do this using both the render props and hooks API separately.

First, the render props.

class App extends React.Component {
  state = {
    isLoading: true,
    users: [],
    error: null
  };
  
  // We're using the Fetch <abbr>API</abbr> to grab user data
  // https://css-tricks.com/using-data-in-react-with-the-fetch-api-and-axios/
  fetchUsers() {
    fetch(`https://jsonplaceholder.typicode.com/users`)
      .then(response => response.json())
      .then(data =>
        // More on setState: https://css-tricks.com/understanding-react-setstate/
        this.setState({
          users: data,
          isLoading: false,
        })
      )
      .catch(error => this.setState({ error, isLoading: false }));
  }

  componentDidMount() {
    this.fetchUsers();
  }

  render() {
    const { isLoading, users, error } = this.state;
    return (
      <div>
        <h1>Random User</h1>
        {error ? <p>{error.message}</p> : null}
        {!isLoading ? (
          // Let's define the items, keys and states using Trail
          <Trail
            items={users}
            keys={user => user.id}
            from={{ marginLeft: -20, opacity: 0, transform: 'translate3d(0,-40px,0)' }}
            to={{ marginLeft: 20, opacity: 1, transform: 'translate3d(0,0px,0)' }}
          >
          {user => props => (
          <div style={props} className="box">
            {user.username}
          </div>
        )}
      </Trail>
        ) : (
          <h3>Loading...</h3>
        )}
      </div>
    );
  }
}

When the component mounts, we make a request to fetch some random users from a third-party API service. Then, we update this.state.users using the data the API returns. We could list the users without animation, and that will look like this:

users.map(user => {
  const { username, name, email } = user;
  return (
    <div key={username}>
      <p>{username}</p>
    </div>
  );
})

But since we want to animate the list, we have to pass the items as props to the Trail component:

<Trail
  items={users}
  keys={user => user.id}
  from={{ marginLeft: -20, opacity: 0, transform: 'translate3d(0,-40px,0)' }}
  to={{ marginLeft: 20, opacity: 1, transform: 'translate3d(0,0px,0)' }}
>
  {user => props => (
    <div style={props} className="box">
      {user.username}
    </div>
  )}
</Trail>

We set the keys to the ID of each user. You can see we are also making use of the from and to props to determine where the animation should start and end.

Now our list of users slides in with a subtle animation:

See the Pen
React Spring - Trail 1
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

The hooks API gives us access to useTrail hook. Since we are not making use of a class component, we can make use of the useEffect hook (which is similar to componentDidMount and componentDidUpdate lifecycle methods) to fetch the users when the component mounts.

const App = () => {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    fetch(`https://jsonplaceholder.typicode.com/users`)
      .then(response => response.json())
      .then(data =>
        setUsers(data)
      )
  }, [])
  
  const trail = useTrail(users.length, {
    from: { marginLeft: -20, opacity: 0, transform: 'translate3d(0,-40px,0)' },
    to: { marginLeft: 20, opacity: 1, transform: 'translate3d(0,0px,0)' }
  })

  return (
    <React.Fragment>
      <h1>Random User</h1>
      {trail.map((props, index) => {
        return (
          <animated.div
            key={users[index]}
            style={props}
            className="box"
          >
            {users[index].username}
          </animated.div>
        )
      })}
    </React.Fragment>
  );
}

We have the initial state of users set to an empty array. Using useEffect, we fetch the users from the API and set a new state using the setUsers method we created with help from the useState hook.

Using the useTrail hook, we create the animated style passing it values for from and to, and we also pass in the length of the items we want to animate. In the part where we want to render the list of users, we return the array containing the animated props.

See the Pen
React Spring -Trail 2
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

Go, spring into action!

Now you have a new and relatively easy way to work with animations in React. Try animating different aspects of your application where you see the need. Of course, be mindful of user preferences when it comes to animations because they can be detrimental to accessibility.

While you’re at it, ensure you check out the official website of react-spring because there are tons of demo to get your creative juices flowing with animation ideas.

The post Creating Animations Using React Spring appeared first on CSS-Tricks.

Pulling Apart SVGs with Reusable WebGL Components Using React-three-fiber

We will be looking at how to pull apart SVGs in 3D space with Three.js and React, using abstractions that allow us to break the scene graph into reusable components.

React and Three.js, what’s the problem?

My background in the past had more to do with front-end work than design, and React has been my preferred tool for a couple of years now. I like it because it pretty much maps the way i think. The ideas in my head are puzzle-pieces, which in React turn to composable components. It makes prototyping faster, and from a visual/design standpoint, it’s even fun, because it allows you to play around without repercussions. If everything is a self-contained lego-brick, you can rip it out, place it here, or there, and observe the result from different angles and perspectives. Especially for visual coding this can make a difference.

The problems that arise when handling programming tasks in an imperative way are always the same. Once we have created a sufficiently complex dependency-graph then things tend to be cobbled together, which causes the whole to be less flexible. Adding, updating or deleting items in sync with state and other operations can get complex. Orchestrating animations makes it even worse, because now you need to await animations to conclude before you continue with other operations and so on. Without a clear component-model it can be a reasonable challenge to keep it all together.

We run into this when working with user interfaces, as well as when creating scenes with Three.js, which can lend to especially unwieldy structures as it forces us to create a ton of objects that we have to track, mutate and manage. But React can solve that, too.

Think of React as a standard that defines what a component is and how it functions. React needs a so called “reconciler” to tell it what to do with these components and how to render them into a host. The browsers dom is a host, hence the react-dom package, which instructs React about the dom. React-native is another one you may be familiar with, but really there are dozens, reaching into all kinds of platforms, from AR, VR, console shells to, you guessed it, Three.js. The reconciler we will be using in this tutorial is called react-three-fiber, it renders components into a Three.js scene graph. Think of it as a portal into Three.js.

Let’s build!

Setting up the scene

Our portal into Three.js will be react-three-fiber’s “Canvas” component. Everything that goes in there will be cast into Three.js-native objects. The following will create a responsive canvas with some lights in it.

function App() {
  return (
    <Canvas>
      <ambientLight intensity={0.5} />
      <spotLight intensity={0.5} position={[300, 300, 4000]} />
    </Canvas>
  )
}

Converting SVGs into shapes

Our goal is to extract SVG paths, once we have that we can display them in all sorts of interesting ways. We will be using fairly simple sketches for that, they won’t create many layers and the effect will be less pronounced.

example

In order to transform SVGs into shape geometries we use Three.js’s SVGLoader. The following will give us a nested array of objects that contains the shapes and colors. We collect the index, too, which we will be using to offset the z-vector.

const svgResource = new Promise(resolve =>
  new loader().load(url, shapes =>
    resolve(
      flatten(
        shapes.map((group, index) =>
          group.toShapes(true).map(shape => ({ shape, color: group.color, index }))
        )
      )
    )
  )
)

Next we define a “Shape” component which renders a single shape. Each shape is offset 50 units by its own index.

function Shape({ shape, position, color, opacity, index }) {
  return (
    <mesh position={[0, 0, index * 50]}>
      <meshPhongMaterial attach="material" color={color} />
      <shapeBufferGeometry attach="geometry" args={[shape]} />
    </mesh>
  )
}

All we are missing now is a component that maps through the shapes we have created. Since the resource we have created is a promise we have to await its resolved state. Once it has loaded, we wrote it into the local component state and forward each shape to the “Shape” component we have just created.

function Scene() {
  const [shapes, set] = useState([])
  useEffect(() => void svgResource.then(set), [])
  return (
    <group>
      {shapes.map(item => <Shape key={item.shape.uuid} {...item} />)}
    </group>
  )
}

This is it, our canvas shows an offset SVG.

Adding animations

If you wanted to animate Three.js you would most likely do it manually and use tools like GSAP. And since we want to animate elements that go in and out you need to have some system in place that orchestrates it, which is not an easy task to pull off.

Here comes the nice part, we are rendering React components and that opens up a lot of possibilities. We can use pretty much everything that exists in the eco system, including animation and transition tools. In this case we use react-spring.

Really all we need to do is convert out shapes into a transition-group. A transition group is something that watches state for changes and helps to retain and transition old state until it can be safely removed. In react-springs case it is called “useTransition”. It takes the original data, shapes in this case, keys in order to identify changes in the data-set, and a couple of lifecycles in which we can define what happens when state is added, removed or changed.

The following takes care of everything. If shapes are added, they will transition into the scene in a trailed motion. If shapes are removed, they will transition out.

const transitions = useTransition(shapes, item => item.shape.uuid, {
  from: { position: [0, 50, -200], opacity: 0 },
  enter: { position: [0, 0, 0], opacity: 1 },
  leave: { position: [0, -50, 10], opacity: 0 },
})

return (
  <group>
    {transitions.map(({ item, key, props }) => <Shape key={key} {...item} {...props} />)}
  </group>
)

useTransition creates an array of objects which contain generated keys, the data items (our shapes) and animated properties. We spread everything over the Shape component. Now we just need to prepare that component to receive animated values and we are done.

react-spring exports a little helper called “animated”, as well as a shortcut called “a”. If you extend any element with it, it will be able to handle these properties. Basically, if you had a div, it would become a.div, if you had a mesh, it now becomes a.mesh.

I hope you had fun! You will find detailed explanations for everything in the respective docs for react-three-fiber and react-spring. The full code for the original demo can be found here.

Pulling Apart SVGs with Reusable WebGL Components Using React-three-fiber was written by Paul Henschel and published on Codrops.