7.3 Example codes (21~30)

Click the Open Sandbox button. Then, you can run all the examples on this page by changing the value of the variable example in App.jsx.

import React, { Suspense, useEffect, useRef, useState } from "react";
import {
  useAnimate,
  useFrame,
  useLoader,
  useRefCallback,
  useRefEffect,
  useSearch,
  useThree,
} from "threefy";
import { Model } from "./ThreeModels";
import { fitCameraToObject } from "./ThreeUtils";
import * as THREE from "three";

import crate_gif from "../public/images/crate.gif";
import brickWall_diffuse_jpg from "../public/images/brickWall/diffuse.jpg";
import brickWall_normal_jpg from "../public/images/brickWall/normal.jpg";
import moss_diffuse_jpg from "../public/images/moss/diffuse.jpg";
import moss_normal_jpg from "../public/images/moss/normal.jpg";
import moss_displacement_jpg from "../public/images/moss/displacement.jpg";
import raymarch_pebbles_jpg from "../public/images/raymarch/pebbles.jpg";
import bathroom_diffuse_jpg from "../public/images/bathroom/diffuse.jpg";
import gravel_diffuse_jpg from "../public/images/gravel/diffuse.jpg";
import gravel_normal_jpg from "../public/images/gravel/normal.jpg";
import gravel_aoRoughMetal_jpg from "../public/images/gravel/aoRoughMetal.jpg";
import rockMossy_diffuse_jpg from "../public/images/rockMossy/diffuse.jpg";

import Soldier_glb from "../public/models/Soldier.glb";
import jump_animation_glb from "../public/models/jump-animation.glb";
import ratamahatta_md2_zip from "../public/models/ratamahatta_md2.zip";
import Cow_zip from "../public/models/Cow.zip";
import LeePerrySmith_glb from "../public/models/LeePerrySmith.glb";

const DemoUseThree = () => {
  const { list, get, set } = useThree();

  //======================
  // get( target )
  //======================

  if (true) {
    const results = [];

    // objects (= threefy properties)
    results.push(get("scene"));
    results.push(get("camera"));
    results.push(get("renderer"));
    results.push(get("clock"));
    results.push(get("controls"));
    results.push(get("animator"));
    results.push(get("raycaster"));

    // object properties
    results.push(get("scene.background"));
    results.push(get("scene.fog"));
    results.push(get("scene.children"));
    results.push(get("camera.aspect"));
    results.push(get("renderer.outputColorSpace"));
    results.push(get("renderer.info"));
    results.push(get("clock.startTime"));
    results.push(get("controls.keys"));
    results.push(get("animator.mixer"));
    results.push(get("animator.mixer.timeScale"));
    results.push(get("raycaster.ray"));
    results.push(get("raycaster.params"));

    // object methods
    results.push(get("camera.getViewSize")(1, new THREE.Vector2()));
    results.push(get("camera.getWorldDirection")(new THREE.Vector3()));
    results.push(get("renderer.getPixelRatio")());
    results.push(get("renderer.getSize")(new THREE.Vector2()));
    results.push(get("renderer.getViewport")(new THREE.Vector4()));
    results.push(get("renderer.getClearColor")(new THREE.Color()));
    results.push(get("clock.getElapsedTime")());
    results.push(get("controls.getDistance")());
    results.push(get("animator.mixer.getRoot")());

    // multiple gets
    results.push(
      get("scene.id", "camera.projectionMatrix", "renderer.toneMapping")
    );

    // example: creating a new function with object methods
    const [delta, update] = get("clock.getDelta", "controls.update");
    const updateControls = () => update(delta());
    results.push({ updateControls });

    // example: renderer size
    const [width, height] = get("width", "height");
    results.push({ width, height });

    // example: viewport
    const viewport = () => {
      const v = new THREE.Vector4();
      get("renderer.getViewport")(v);
      return { x: v.x, y: v.y, width: v.z, height: v.w };
    };
    results.push({ viewport: viewport() });

    // example: canvas element
    results.push({ canvas: get("renderer.domElement") });

    // example: device pixel ratio (or dpr)
    results.push({ dpr: get("renderer.getPixelRatio")() });

    console.log(results);
  }

  //======================
  // list( target )
  //======================

  if (true) {
    const results = [];
    results.push(list(get("scene")));
    results.push(list(get("camera")));
    results.push(list(get("composer")));
    results.push(list(get("animator.mixer")));
    console.log(results);
  }

  //======================
  // set( target, value )
  //======================

  useEffect(() => {
    // const createScene = () =>
    // {
    //     const scene = new THREE.Scene();
    //     scene.add( new THREE.AmbientLight() );
    //     scene.add( new THREE.DirectionalLight() );
    //     const g = new THREE.SphereGeometry( 1, 32, 32 );
    //     const m = new THREE.MeshStandardMaterial( { color: 0xff0000 } );
    //     scene.add( new THREE.Mesh( g, m ) );
    //     return scene;
    // }

    const createOrthographicCamera = (w, h) => {
      w = w ?? window.innerWidth;
      h = h ?? (w * window.innerHeight) / window.innerWidth;
      const orthographicCamera = new THREE.OrthographicCamera(
        -w / 2,
        w / 2,
        h / 2,
        -h / 2,
        0.1,
        1000
      );
      orthographicCamera.position.z = 500;
      return orthographicCamera;
    };

    const renderer = get("renderer");
    const mesh = get("scene.children[1]");

    // replace the camera
    set("camera", createOrthographicCamera(50));

    // replace the scene
    // set( 'scene', createScene() );

    // NOTE*:
    // target (= string, not object) should start with one of the
    // scene, camera, renderer, clock, controls, animator, or raycaster,
    // like target = 'scene.background', target = 'camera.fov', etc.

    // 4 ways to use
    const usage = 4;
    if (usage === 1) {
      set("renderer.outputColorSpace", THREE.SRGBColorSpace);
      set("renderer.toneMapping", THREE.ACESFilmicToneMapping);
    } else if (usage === 2) {
      set({
        "renderer.outputColorSpace": THREE.SRGBColorSpace,
        "renderer.toneMapping": THREE.ACESFilmicToneMapping,
      });
    } else if (usage === 3) {
      set("renderer", {
        outputColorSpace: THREE.SRGBColorSpace,
        toneMapping: THREE.ACESFilmicToneMapping,
      });
    } else if (usage === 4) {
      set(renderer, {
        outputColorSpace: THREE.SRGBColorSpace,
        toneMapping: THREE.ACESFilmicToneMapping,
      });
    }

    // example: scene.background
    switch (3) {
      case 1:
        set("scene.background", 0xcdcdcd);
        break;
      case 2:
        set("scene.background", "skyblue");
        break;
      case 3:
        set("scene.background", brickWall_diffuse_jpg);
        break;
      case 4:
        set(
          "scene.background",
          new THREE.TextureLoader().load(
            brickWall_diffuse_jpg,
            (tex) => (tex.colorSpace = THREE.SRGBColorSpace)
          )
        );
        break;
    }

    // example: renderer (modified by its methods)
    set(renderer, {
      setPixelRatio: 0.5, // renderer.setPixelRatio( 0.5 )
      setViewport: [100, 100, 700, 700], // renderer.setViewport( 100, 100, 700, 700 )
    });

    // example: mesh material
    if (false) {
      set("scene.children[4].material", {
        color: [1, 1, 0],
        map: brickWall_diffuse_jpg,
      });
    } else {
      set(mesh.material, {
        color: [1, 1, 0],
        map: brickWall_diffuse_jpg,
      });
    }

    // example: mesh transform
    if (false) {
      set("scene.children[4]", {
        rotation: [Math.PI / 6, Math.PI / 3, 0],
        scale: [20, 10, 10], // or 20
      });
    } else {
      set(mesh, {
        rotation: [Math.PI / 6, Math.PI / 3, 0],
        scale: [20, 10, 10], // or 20
      });
    }
  }, []);

  return <box scale={10} type={"phong"} color={"red"} />;
};

const DemoShadow = () => {
  const [left, right, top, bottom, near, far] = [-20, 20, 20, -20, 0.5, 70];

  const ref = useRefEffect((light, scene) => {
    scene.add(new THREE.DirectionalLightHelper(light, 2));
  });

  useFrame((t) => {
    if (!ref.current) return;
    const light = ref.current;
    light.position.x = 30 * Math.cos(t);
    light.position.z = 30 * Math.sin(t);
  });

  return (
    <>
      <directionalLight
        ref={ref}
        castShadow
        position={[10, 30, 0]}
        shadow-mapSize={[512, 512]}
        shadow-camera-left={left}
        shadow-camera-right={right}
        shadow-camera-top={top}
        shadow-camera-bottom={bottom}
        shadow-camera-near={near}
        shadow-camera-far={far}
      />
      <box
        castShadow
        position={[-10, 15, 0]}
        scale={5}
        type={"lambert"}
        color={"red"}
      />
      <box
        castShadow
        position={[0, 15, 0]}
        scale={5}
        type={"lambert"}
        color={"green"}
      />
      <box
        castShadow
        position={[10, 15, 0]}
        scale={5}
        type={"lambert"}
        color={"blue"}
      />
      <box
        castShadow
        position={[0, 15, -10]}
        scale={5}
        type={"lambert"}
        color={"yellow"}
      />
      <box
        castShadow
        position={[0, 15, 10]}
        scale={5}
        type={"lambert"}
        color={"cyan"}
      />
      <box
        receiveShadow
        position={[0, 0, 0]}
        scale={[80, 2, 80]}
        type={"lambert"}
        color={"white"}
      />
    </>
  );
};

const DemoShadow2 = () => {
  const MossGround = ({ sizes }) => {
    const textures = useLoader([
      moss_diffuse_jpg,
      moss_normal_jpg,
      moss_displacement_jpg,
    ]);
    textures.forEach((tex, i) => {
      tex.wrapS = THREE.RepeatWrapping;
      tex.wrapT = THREE.RepeatWrapping;
      tex.repeat.set(4, 4);
      if (i === 0) tex.colorSpace = THREE.SRGBColorSpace;
    });

    return (
      <plane
        receiveShadow
        rotation-x={Math.PI / -2}
        args={[...sizes, 64, 64]}
        type={"standard"}
        map={textures[0]}
        normalMap={textures[1]}
        displacementMap={textures[2]}
        displacementScale={5}
      />
    );
  };

  const ref = useRefEffect((group, scene) => {
    group.children.forEach((light) => {
      if (light.isLight) {
        scene.add(new THREE.DirectionalLightHelper(light, 2));
      }
    });
  });

  useFrame((t) => {
    if (!ref.current) return;
    const group = ref.current;
    group.rotation.y = t * 0.5;
  });

  return (
    <>
      <MossGround sizes={[300, 300]} />
      <box
        castShadow
        position={[-60, 20, 0]}
        scale={20}
        type={"lambert"}
        color={"red"}
        update={function (dt, ct) {
          this.rotation.x += Math.sin(dt) * 2.0;
        }}
      />
      <box
        castShadow
        position={[0, 20, 0]}
        scale={20}
        type={"lambert"}
        color={"green"}
        update={function (dt, ct) {
          this.rotation.y += Math.sin(dt) * 1.0;
        }}
      />
      <box
        castShadow
        position={[60, 20, 0]}
        scale={20}
        type={"lambert"}
        color={"blue"}
        update={function (dt, ct) {
          this.rotation.z += Math.sin(dt) * -2.0;
        }}
      />
      <box
        castShadow
        position={[0, 20, -60]}
        scale={20}
        type={"lambert"}
        color={"yellow"}
        update={function (dt, ct) {
          this.rotation.x += Math.sin(dt) * 3.0;
        }}
      />
      <box
        castShadow
        position={[0, 20, 60]}
        scale={20}
        type={"lambert"}
        color={"cyan"}
        update={function (dt, ct) {
          this.rotation.z += Math.sin(dt) * 2.0;
        }}
      />
      <group ref={ref}>
        <shadowDirectionalLight position={[0, 100, 0]} intensity={5} />
        <shadowDirectionalLight position={[50, 70, 50]} intensity={5} />
      </group>
    </>
  );
};

const DemoShadow3 = () => {
  const example = 2;

  // shadowDirectionalLight
  if (example === 1) {
    const ref = useRefEffect((light, scene) => {
      // turn off the existing three-point-lighting object
      const searched = useSearch("Object3D", "threePointLighting");
      searched[0].visible = false;

      scene.add(new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 0.2));
      scene.add(new THREE.DirectionalLightHelper(light, 5));
    });

    const onLoad = (model) => {
      model.traverse((child) => {
        if (child.isMesh) {
          child.castShadow = true; // required
          child.receiveShadow = true; // optional
        }
      });
    };

    useFrame((t) => {
      const light = ref.current;
      if (light) {
        light.position.x = Math.cos(t / 2) * -40;
        light.position.z = Math.sin(t / 2) * -40;
      }
    });

    return (
      <group position-y={-10}>
        <shadowDirectionalLight
          ref={ref}
          position={[-40, 40, 0]}
          color={"skyblue"}
          intensity={Math.PI}
        />
        <shadowPlaneReceiver width={100} type={"shadow"} />
        <fogExp2 args={[0xcccccc, 0.001]} />
        <background color={0x494949} />
        <Model url={Soldier_glb} scale={12} onLoad={onLoad} />
      </group>
    );
  }

  // shadowSpotLight
  if (example === 2) {
    const ref = useRefEffect((light, scene) => {
      // turn off the existing three-point-lighting object
      const searched = useSearch("Object3D", "threePointLighting");
      searched[0].visible = false;

      scene.add(new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 0.2));
      scene.add(new THREE.SpotLightHelper(light));
    });

    const onLoad = (model) => {
      model.traverse((child) => {
        if (child.isMesh) {
          child.castShadow = true; // required
          child.receiveShadow = true; // optional
        }
      });
    };

    useFrame((t) => {
      const light = ref.current;
      if (light) {
        light.position.x = Math.cos(t / 3) * 40;
        light.position.z = Math.sin(t / 3) * 40;
      }
    });

    return (
      <group position-y={-10}>
        <shadowSpotLight
          ref={ref}
          position={[40, 50, 0]}
          intensity={1000}
          color={"skyblue"}
          map={raymarch_pebbles_jpg}
        />
        <shadowPlaneReceiver
          width={500}
          height={500}
          type={"phong"}
          color={0xffffff}
        />
        <fogExp2 args={[0xcccccc, 0.001]} />
        <background color={0x494949} />
        <Model url={Soldier_glb} scale={12} onLoad={onLoad} />
      </group>
    );
  }

  // shadowPointLight
  if (example === 3) {
    const ref = useRefEffect((light, scene) => {
      // turn off the existing three-point-lighting object
      const searched = useSearch("Object3D", "threePointLighting");
      searched[0].visible = false;

      scene.add(new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 0.2));
      scene.add(new THREE.PointLightHelper(light, 1));
    });

    const onLoad = (model) => {
      model.traverse((child) => {
        if (child.isMesh) {
          child.castShadow = true; // required
          child.receiveShadow = true; // optional
        }
      });
    };

    useFrame((t) => {
      const light = ref.current;
      if (light) {
        light.position.x = Math.cos(t / 3) * 40;
        light.position.z = Math.sin(t / 3) * 40;
      }
    });

    return (
      <group position-y={-10}>
        <shadowPointLight
          ref={ref}
          position={[20, 50, 0]}
          intensity={300}
          color={"skyblue"}
        />
        <shadowPlaneReceiver
          width={500}
          height={500}
          type={"phong"}
          color={0xffffff}
          map={bathroom_diffuse_jpg}
        />
        <fogExp2 args={[0xcccccc, 0.001]} />
        <background color={0x494949} />
        <Model url={Soldier_glb} scale={12} onLoad={onLoad} />
      </group>
    );
  }
};

const DemoUseLoader = () => {
  const example = 2;

  if (example === 1) {
    const onAnimate = (model) => {
      fitCameraToObject(model);

      if (model.animations) {
        const { animator } = useThree();
        animator.playAction(model, model.animations[0]);
      }
    };

    return <Model url={jump_animation_glb} scale={1} onLoad={onAnimate} />;
  }

  if (example === 2) {
    const { animator } = useThree();

    const onAnimate = (model) => {
      if (model.animations) {
        animator.playAction(model, model.animations[0]);
      }
    };
    const onTransform = (model) => {
      model.position.y += 1.2;
      model.position.z += 2.0;
      model.scale.setScalar(0.05);
      model.rotation.y = Math.PI / 4;
      if (model.animations) {
        animator.playAction(model, model.animations[0]);
      }
    };

    return (
      <group scale={10}>
        <Model url={jump_animation_glb} onLoad={onAnimate} />
        <Model url={ratamahatta_md2_zip} onLoad={onTransform} />
      </group>
    );
  }

  if (example === 3) {
    const [tex1, tex2, tex3] = useLoader([
      crate_gif,
      brickWall_diffuse_jpg,
      brickWall_normal_jpg,
    ]);
    tex1.colorSpace = THREE.SRGBColorSpace;
    tex2.colorSpace = THREE.SRGBColorSpace;
    tex3.colorSpace = THREE.SRGBColorSpace;

    const g = new THREE.BoxGeometry(10, 10, 10);
    const m1 = new THREE.MeshBasicMaterial({ map: tex1 });
    const m2 = new THREE.MeshStandardMaterial({ map: tex2, normalMap: tex3 });

    return (
      <group>
        <mesh geometry={g} material={m1} position-x={-8} />
        <mesh geometry={g} material={m2} position-x={8} />
      </group>
    );
  }
};

const DemoUseSearch = () => {
  const example = 1;

  const urls = [jump_animation_glb, ratamahatta_md2_zip];

  const objects = useLoader(urls);

  const onLoad = (model) => {
    if (example === 1) {
      const searched = useSearch("Object3D", urls[0]);
      const target = objects[0];
      const found = searched[0];
      console.log("object to search:", target);
      console.log("object searched:", found);
      console.log("same?", target.uuid === found.uuid);
    }

    if (example === 2) {
      const searched = useSearch("Mesh", "Ch03");
      console.log(searched);
    }

    if (example === 3) {
      const searched = useSearch("Texture");
      console.log(searched);
    }

    if (example === 4) {
      const searched = useSearchObject(objects[0], "Material");
      console.log(searched);
    }

    if (example === 5) {
      const searched = useSearchObject(objects[0], "BufferGeometry");
      console.log(searched);
    }
  };

  return <primitive object={objects[0]} scale={10} onLoad={onLoad} />;
};

const DemoUseRefCallback = () => {
  const [params, setParams] = useState({
    rx: 0,
    ry: 0,
    col: [0, 0, 0],
  });

  const setMaterialMap = useRefCallback(
    (object, url, mapType = "map", repeat = [1, 1]) => {
      const tex = useLoader(url);
      object.material[mapType] = tex;

      tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
      tex.repeat.set(repeat[0], repeat[1]);

      switch (mapType) {
        case "map":
        case "emissiveMap":
          tex.colorSpace = THREE.SRGBColorSpace;
          break;
        case "envMap":
        case "lightMap":
          tex.colorSpace = THREE.LinearSRGBColorSpace;
          break;
        default:
          tex.colorSpace = THREE.NoColorSpace;
          break;
      }
    }
  );

  const ref = useRefEffect((box) => {
    box.geometry.attributes.uv2 = box.geometry.attributes.uv;
    setMaterialMap(box, gravel_diffuse_jpg, "map", [2, 2]);
    setMaterialMap(box, gravel_normal_jpg, "normalMap", [2, 2]);
    setMaterialMap(box, gravel_aoRoughMetal_jpg, "aoMap", [2, 2]);
    setMaterialMap(box, gravel_aoRoughMetal_jpg, "roughnessMap", [2, 2]);
    setMaterialMap(box, gravel_aoRoughMetal_jpg, "metalnessMap", [2, 2]);
    box.material.normalScale = new THREE.Vector2(5, 5);
  });

  useFrame((t) => {
    const r = THREE.MathUtils.lerp(0, 1, Math.sin(t) * 0.5 + 0.5);
    const g = THREE.MathUtils.lerp(0, 1, Math.cos(t * 2) * 0.5 + 0.5);
    setParams({
      rx: t * 0.25,
      ry: t * 0.5,
      col: [r, g, 1 - r],
    });
  });

  return (
    <box
      ref={ref}
      scale={30}
      rotation={[params.rx, params.ry, 0]}
      type={"standard"}
      color={params.col}
    />
  );
};

const DemoSuspense = () => {
  const example = 2;

  if (example === 1) {
    // rendering order: box > urls[0 or 1]
    const urls = [ratamahatta_md2_zip, jump_animation_glb];
    return (
      <Suspense fallback={<box scale={5} color="red" />}>
        <Model url={urls[0]} scale={0.25} />
        <Model url={urls[1]} scale={10} />
      </Suspense>
    );
  }

  if (example === 2) {
    // rendering order: urls[0] > urls[1 or 2] > urls[3]
    const urls = [
      Cow_zip,
      ratamahatta_md2_zip,
      LeePerrySmith_glb,
      jump_animation_glb,
    ];
    return (
      <Suspense fallback={<Model url={urls[0]} scale={2} />}>
        <Model url={urls[1]} scale={0.25} />
        <Suspense fallback={<Model scale={0.01} url={urls[2]} />}>
          <Model url={urls[3]} scale={10} />
        </Suspense>
      </Suspense>
    );
  }
};

const DemoHoverClick = () => {
  const example = 2;

  if (example === 1) {
    const ref = useRef(null);
    const [index, setIndex] = useState(0);
    const [hovered, setHovered] = useState(false);

    const sizes = [7, 10, 13, 16];
    const colors = ["red", "green", "blue", "yellow"];
    const size = sizes[index];
    const color = hovered ? colors[index] : "white";

    useFrame((t) => {
      if (ref.current) ref.current.rotation.y = t;
    });

    return (
      <mesh
        ref={ref}
        onClick={() => setIndex((index + 1) % 4)}
        onPointerOver={() => setHovered(true)}
        onPointerOut={() => setHovered(false)}
      >
        <boxGeometry args={[size, size, size]} />
        <meshStandardMaterial color={color} />
      </mesh>
    );
  }

  if (example === 2) {
    const ref = useRef(null);

    const types = ["box", "sphere", "cylinder", "torus"];

    const argss = [
      [16, 16, 16],
      [10, 32, 16],
      [10, 10, 18],
      [10, 4, 24, 48],
    ];

    const diffuses = [
      crate_gif,
      brickWall_diffuse_jpg,
      moss_diffuse_jpg,
      rockMossy_diffuse_jpg,
    ];

    const [index, setIndex] = useState(0);
    const [hovered, setHovered] = useState(false);

    const type = types[index];
    const args = argss[index];
    const diffuse = diffuses[index];
    const opacity = hovered ? 1.0 : 0.2;

    const onHover = (value) => setHovered(value);
    const onClick = () => setIndex((index + 1) % 4);

    useFrame((t) => {
      if (ref.current) {
        ref.current.rotation.x = t * 0.8;
        ref.current.rotation.y = t;
      }
    });

    return (
      <mesh
        ref={ref}
        onClick={onClick}
        onPointerOver={() => onHover(true)}
        onPointerOut={() => onHover(false)}
      >
        <geometry type={type} args={args} />
        <meshStandardMaterial transparent opacity={opacity} map={diffuse} />
      </mesh>
    );
  }
};

const DemoUseAnimate = () => {
  const ref = useRef(null);

  const [hovered, setHovered] = useState(false);
  const [scale, setScale] = useState(1);

  const { replay, pause, flush } = useAnimate();

  const color = hovered ? "green" : "white";

  const onHover = (value) => {
    setHovered(value);
    value ? pause() : replay();
  };

  const onClick = () => {
    setScale(scale === 1 ? 1.5 : 1);
    flush();
  };

  useFrame((t) => {
    if (ref.current) {
      ref.current.rotation.y = t;
    }
  });

  return (
    <mesh
      ref={ref}
      scale={scale}
      onClick={onClick}
      onPointerOver={() => onHover(true)}
      onPointerOut={() => onHover(false)}
    >
      <boxGeometry args={[7, 15, 9]} />
      <meshStandardMaterial color={color} map={brickWall_diffuse_jpg} />
    </mesh>
  );
};

const DemoExamples = ({ example }) => {
  switch (example) {
    case 21:
      return <DemoUseThree />;
    case 22:
      return <DemoShadow />;
    case 23:
      return <DemoShadow2 />;
    case 24:
      return <DemoShadow3 />;
    case 25:
      return <DemoUseLoader />;
    case 26:
      return <DemoUseSearch />;
    case 27:
      return <DemoUseRefCallback />;
    case 28:
      return <DemoSuspense />;
    case 29:
      return <DemoHoverClick />;
    case 30:
      return <DemoUseAnimate />;
  }
};

export { DemoExamples };

Last updated