GithubHelp home page GithubHelp logo

react-zdog's Introduction

npm install zdog react-zdog
# or
yarn add zdog react-zdog

npm npm

react-zdog is a declarative abstraction of zdog, a cute pseudo 3d-engine. Doing zdog in React allows you to break up your scene graph into declarative, re-usable components with clean, reactive semantics. Try a live demo here.

How it looks like

import ReactDOM from "react-dom";
import React from "react";
import { Illustration, Shape } from "react-zdog";

ReactDOM.render(
  <Illustration zoom={8}>
    <Shape stroke={20} color="lightblue" rotate={{ x: Math.PI }} />
  </Illustration>,
  document.getElementById("root")
);

Illustration

The Illustration object is your portal into zdog. It forwards unreserved properties to the internal Zdog.Illustration instance. The component auto adjusts to re-size changes and fills out the wrapping relative/absolute parent.

<Illustration element="svg" /> // Can be either 'svg' or 'canvas'
  • element: Sets the graphics rendering DOM Element. Can be either 'svg' or 'canvas'. Default is "svg"
  • frameloop: Determins the render loop behavior, Can be either 'always' or 'demand'. default is 'always'.
  • pointerEvents: enables pointer events on zdog elements if set to true. Default is False.
  • style: styles for main renderer dom elemeent container.
  • onDragStart: callback on illustration's on drag start event listener
  • onDragMove: callback on illustration's on drag move event listener
  • onDragEnd: callback on illustration's on drag end event listener

And all the other props you will pass will be attached to illustration object. So any other properties or methods that you wanna set on illustration can be passed as prop as it is.

Hooks

All hooks can only be used inside the Illustration element because they rely on context updates!

useRender(callback, dependencies=[])

If you're running effects that need to get updated every frame, useRender gives you access to the render-loop.

import { useRender } from "react-zdog";

function Spin({ children }) {
  const ref = useRef(undefined);
  useRender((t) => (ref.current.rotate.y += 0.01));
  return <Anchor ref={ref}>{children}</Anchor>;
}

useZdog()

Gives you access to the underlying state-model.

import { useZdog } from 'react-zdog'

function MyComponent() {
  const {
    illu,             // The parent Zdog.Illustration object
    scene,            // The Zdog.Anchor object that's being used as the default scene
    size,             // Current canvas size
  } = useZdog()

useInvalidate()

Gives you access to function that updates the one scene frame on each call. It is useful only if you're setting frameloop props on Illustration component as demand

function MyComponent() {
  const invalidate = useInvalidate()
  const boxRef = useRef()
  const rotate = () => {
    boxRef.current.rotate.x += 0.03;
    boxRef.current.rotate.y += 0.03; //this will update underlying javascript object
    invalidate() //But you need to call invalidate to render the changes on screen
  }

  return (
    <Box
      ref={boxRef}
      {/* ...other props */}
    />
  )}

Pointer Events

React-zdog supports the Click, Pointer Move, Pointer Enter and Pointer Leave events on Zdog elemets. To use pointer events just enable the pointer events by setting pointerEvents prop to true on Illustration component.

<Illustration pointerEvents={true} />

and use onClick, onPointerMove, onPointerEnter and OnPointerLeave on any zdog element.

const onClick = (e, ele) => {
  //runs when user clicks on box
};

const onPointerMove = (e, ele) => {
  //runs when user moves pointer over box
};

const onPointerEnter = (e, ele) => {
  //runs when user's pointer enters the box
};

const onPointerLeave = (e, ele) => {
  //runs when user's pointer leaves the box
};

return (
  <Box
    onClick={onClick}
    onPointerMove={onPointerMove}
    onPointerEnter={onPointerEnter}
    onPointerLeave={onPointerLeave}
  />
);
Note: zdog dosen't support pointer events out of the box, it is react-zdog specific feature which is added recently and was tested, but if you find some issue with events (and with any other thing) please open a issue and let us know.

Examples

Basic Example
import React, { useRef, useEffect } from 'react';
import { Illustration, useRender, useInvalidate, Box } from 'react-zdog';

// RotatingCube Component
const RotatingCube = () => {
const boxRef = useRef();

// Use the useRender hook to continuously update the rotation
useRender(() => {
if (boxRef.current) {
boxRef.current.rotate.x += 0.03;
boxRef.current.rotate.y += 0.03;
}
});

    return (
      <Box
        ref={boxRef}
        width={50}
        height={50}
        depth={50}
        color="#E44"
        leftFace="#4E4"
        rightFace="#44E"
        topFace="#EE4"
        bottomFace="#4EE"
      />
    );

};

// App Component
const App = () => {
return (
<Illustration zoom={4}>
<RotatingCube />
</Illustration>
);
};

export default App;
Pointer Events Example
import React, { useRef, useState } from 'react';
import { Illustration, useRender, Box } from 'react-zdog';

// InteractiveCube Component
const InteractiveCube = () => {
const [isClicked, setIsClicked] = useState(false);

const colorsBeforeClick = {
  main: "#E44",
  left: "#4E4",
  right: "#44E",
  top: "#EE4",
  bottom: "#4EE"
};

const colorsAfterClick = {
  main: "#FF5733",
  left: "#33FF57",
  right: "#3357FF",
  top: "#FF33A1",
  bottom: "#A133FF"
};

const currentColors = isClicked ? colorsAfterClick : colorsBeforeClick;

const handleBoxClick = () => {
  setIsClicked(!isClicked);
};


return (
  <Box
    width={50}
    height={50}
    depth={50}
    color={currentColors.main}
    leftFace={currentColors.left}
    rightFace={currentColors.right}
    topFace={currentColors.top}
    bottomFace={currentColors.bottom}
    onClick={handleBoxClick}
  />
);
};

// App Component
const App = () => {
return (
  <Illustration pointerEvents={true} zoom={4}>
    <InteractiveCube />
  </Illustration>
);
};

export default App;
On Demand rendering Example
import React, { useRef, useEffect } from "react";
import { Illustration, useInvalidate, Box } from "react-zdog";

// RotatingCube Component
const RotatingCube = () => {
  const boxRef = useRef();
  const invalidate = useInvalidate();

  useEffect(() => {
    const animate = () => {
      if (boxRef.current) {
        boxRef.current.rotate.x += 0.03;
        boxRef.current.rotate.y += 0.03;
        invalidate(); // Manually trigger a render
      }
    };

    const intervalId = setInterval(animate, 1000); // only renders the scene graph one a second instead of 60 times per second

    return () => intervalId && clearInterval(intervalId);
  }, [invalidate]);

  return (
    <Box
      ref={boxRef}
      width={50}
      height={50}
      depth={50}
      color="#E44"
      leftFace="#4E4"
      rightFace="#44E"
      topFace="#EE4"
      bottomFace="#4EE"
    />
  );
};

// App Component
const App = () => {
  return (
    <Illustration zoom={4} frameloop="demand">
      <RotatingCube />
    </Illustration>
  );
};

export default App;

Roadmap

  • Create more Examples
  • add More events support

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

react-zdog's People

Contributors

aleclarson avatar bhushan6 avatar bhushanwagh22 avatar dependabot[bot] avatar drcmda avatar shahruz avatar talentlessguy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-zdog's Issues

Cannot read properties of undefined (reading 'current') error

I try to use [email protected] with remix.run and with create-react-app.
In both cases i get error

Cannot read properties of undefined (reading 'current')

I also try [email protected]
Basic example works fine:

<Illustration zoom={8}>
    <Shape stroke={20} color="lightblue" rotate={{ x: Math.PI }} />
  </Illustration>

But when i try const {illu, scene, size} = useZdog() I get the same error:

TypeError: Cannot read properties of undefined (reading 'current')
    at useZdog (http://localhost:3000/build/routes/zdog-XXTCYKP4.js:2376:16)
    at Zdog2 (http://localhost:3000/build/routes/zdog-XXTCYKP4.js:2523:7)
    at renderWithHooks (http://localhost:3000/build/_shared/chunk-GIAAE3CH.js:12171:26)
    at mountIndeterminateComponent (http://localhost:3000/build/_shared/chunk-GIAAE3CH.js:14921:21)
    at beginWork (http://localhost:3000/build/_shared/chunk-GIAAE3CH.js:15902:22)
    at beginWork$1 (http://localhost:3000/build/_shared/chunk-GIAAE3CH.js:19749:22)
    at performUnitOfWork (http://localhost:3000/build/_shared/chunk-GIAAE3CH.js:19194:20)
    at workLoopSync (http://localhost:3000/build/_shared/chunk-GIAAE3CH.js:19133:13)
    at renderRootSync (http://localhost:3000/build/_shared/chunk-GIAAE3CH.js:19112:15)
    at recoverFromConcurrentError (http://localhost:3000/build/_shared/chunk-GIAAE3CH.js:18732:28)

zdog-XXTCYKP4.js:2376:16

function useZdog() {
  const state = (0, import_react.useContext)(stateContext);
  return state.current;
}

Typescript support

Hey there! Is there a plan to support Typescript? The original library already has types at @types/zdog. I would help but I'm not sure how to tackle it.

Support for SVG as canvas

I'm currently trying to port something I wrote in plain JS with Zdog to react-zdog, now that I've seen it working. However, I've had a fair few problems with <canvas> elements previously, so an option to use an SVG element instead would be very welcome.

Add 'renderOnce' option to Illustration

For static illustrations the canvas/svg doesn't need to be rendered every frame, page performance could be benefited if there was an option to have the Illustration render one time on mount.

I tried implementing it like this: (line 96)

const Illustration = React.memo(                        // Change here
  ({ children, style, resize, element: Element = 'svg', renderOnce = false, ...rest }) => {

...

let active = !renderOnce // And here
function render(t) {
  const { size, subscribers } = state.current
  if (size.width && size.height) {
    // Run global effects
    globalEffects.forEach(fn => fn(t))
    // Run local effects
    subscribers.forEach(fn => fn(t))
    // Render scene
    state.current.illu.updateRenderGraph()
  }
  if (active) frame = requestAnimationFrame(render)
}

// Start render loop
render()

But it doesn't render anything, even though render() is being called once. Any suggestions?

Infinite resize loop caused by resize observer in root element

I encountered an issue where the root element (svg or canvas) in the react-zdog library grows infinitely due to the resize observer. The problem occurs when the root element is wrapped in a

element that is subscribed to the resize observer. Whenever the size of the
changes, it applies the size changes to the root element (svg or canvas). However, if the root element has margin, padding, or border, its total size becomes larger than the
, triggering the resize observer again. This creates an infinite loop, causing the root element to keep growing in size.

Steps to Reproduce:

  1. It starts on initial render itself.
    react-zdog-size-enlargment-issue

One weird thing I noticed that this happens react 18.0.2 version, while works perfectly fine with version 17.

Proposed Solution:
The solution to then issue is that adding css property box-sizing as "border-box".
By setting the box-sizing property to border-box, the size calculations will include the element's padding and border, preventing it from growing larger than its parent

.

Support for animation libraries

Referring to your issue in the main project, I've tried to do something similar with react-zdog. Since I'm pretty new to animation libraries in general, I've been using this article – react-transition-group and animejs proved pretty tricky to get my head around, but I thought I'd almost got there with Pose when I came upon this error:

Error: No valid DOM ref found. If you're converting an existing component via posed(Component), you must ensure you're passing the ref to the host DOM node via the React.forwardRef function.

It looks like the components aren't passing refs back up in the right way.

I'm aware there's already a demo for react-spring, which I'll probably end up playing with next, but better support and examples for other libraries would be nice for those coming from anime.js and others.

Rotation issue

Creating the canvas with rotate already set results in a different rotation than changing the rotation after the initial render.

I'm going for an isometric perspective sort of thing

    x: -0.33 * TAU,
    y: -0.5 * TAU,
    z: -0.9 * TAU

Rotation unset:
Rotation { x: 0, y: 0, z: 0 }

Rotation unset, then changed to the given values via state change:
Screenshot 2019-06-11 at 14 20 03 (desired outcome)

Rotation set:
Screenshot 2019-06-11 at 14 20 36

I've got a suspicion that by setting the rotation on init, it affects where shapes are placed. However, this isn't the case for Zdog on its own, as what I'm working on is based on something I did in plain Zdog – in that, I initialised the illustration with a rotation, and was still able to create boxes with the same coordinates and get the desired outcome above.

I wanted to rectify this by initialising with all rotation at 0, but trying the useZdog snippet from the README resulted in TypeError: _useZdog is undefined. I was also unclear on how to use the useRender hook.

Thanks again for all your solid work on this port!

Add ESLint

The project is currently missing ESLint. I think it would be a good idea to add it.

I created a diff with the airbnb preset, minus a few rules that don't break the current code style. I think the semi rule should be enforced at least.

Also I moved the index file to a src folder to avoid running lint on the examples folder, plus preparing it for a possible code split.

What are your opinions on this?

dragRotate compatibility

I've tried setting dragRotate={true] on an Illustration, but it doesn't seem to be working. Is this a known issue, if at all?

Support react@18

Hi. I see that this lib is used by react-spring

I use some animation on my projects, and it still works in react@18. but it still show me the warning when it is installed.

npm WARN ERESOLVE overriding peer dependency
npm WARN While resolving: [email protected]
npm WARN Found: [email protected]
npm WARN node_modules/react
npm WARN   react@"^18.2.0" from the root project
npm WARN   106 more (@emotion/react, @emotion/styled, ...)
npm WARN 
npm WARN Could not resolve dependency:
npm WARN peer react@"^16.0.0" from [email protected]
npm WARN node_modules/react-zdog/node_modules/react-reconciler
npm WARN   react-reconciler@"^0.20.4" from [email protected]
npm WARN   node_modules/react-zdog
npm WARN 
npm WARN Conflicting peer dependency: [email protected]
npm WARN node_modules/react
npm WARN   peer react@"^16.0.0" from [email protected]
npm WARN   node_modules/react-zdog/node_modules/react-reconciler
npm WARN     react-reconciler@"^0.20.4" from [email protected]
npm WARN     node_modules/react-zdog

and also update react-reconciler to get rid of this warning

Allow access to the parent Illustration

This has likely got a similar source to #7. useRender can only be used inside of Illustrations, but I'd like to be able to change the rotate attribute of an Illustration on a frame-by-frame basis as you would with:

function animate() {
  illo.rotate.y += 0.08
  requestAnimationFrame(animate)
}

Originally posted by @boardfish in #7 (comment)

dragRotate not working

It looks like dragRotate is not working.

this code:

<Illustration zoom={2} dragRotate={true}>
    <Rect width={120} height={80} stroke={10} color="#E62"/>
</Illustration>

gives this error:

illustration.js:233 Uncaught TypeError: Cannot read property 'x' of undefined
  at Item.Illustration.dragStart (illustration.js:233)
  at Item.Dragger.onmousedown.Dragger.onpointerdown (dragger.js:76)
  at Item.Dragger.handleEvent (dragger.js:71)

New maintainer - I'll put my hand up

Hi @drcmda , I saw you mention on Twitter you were, possibly, looking for a new maintainer?

I'd be happy to take it on, especially considering the small surface area. I already help out around other parts of pmndrs, in particular the Zustand docs.

Doesn't work with cra

create-react-app zdogreacttest

yarn add zdog react-zdog

replace the contents of app.js with the following minimal app

import React from 'react';
import { Illustration, Shape } from 'react-zdog'

function App() {
  return (
      <Illustration zoom={8}>
        <Shape stroke={20} color="lightblue" rotate={{ x: 5 }} />
      </Illustration>
  );
}

export default App;

yarn start

Expected behavior - a shape

Actual behavior - nothing rendered.

No errors in console but when I was fiddling I did once get it to work and saw some errors with event listeners. Cant reproduce anything working now though, so its hard to say.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.