pixi-react
pixi-react copied to clipboard
Bug: The codepen example for pixi-viewport is outdated and does not work
Current Behavior
I have taken the code from the pixi-viewport codepen example and tried to get it to work in a fresh installation of Next.js 13 (React 18).
I have received a lot of errors. I have come fairly close to solving almost all errors, but ultimately cannot get the section to work that actually uses pixi-viewport. The code in the example is clearly outdated and doesn't use standard import syntax, especially for pixi-viewport, and also lacks accurate types, making it unclear how to use this in a React app in practice.
FYI: A similar example can be found in the React Pixi Custom Components docs.
Expected Behavior
- The example should be up-to-date with the latest versions of pixi and pixi-viewport
- The example should use standard
import {x} from 'y'
syntax, especially for pixi-viewport - The example should use accurate types where relevant
- I should be able to copy the code into a fresh React project and it should work
Steps to Reproduce
- Take the code from the example and copy it into a React app
- It won't work for the reasons described above (imports wrong, types missing, versions outdated)
Environment
I don't know about the versions used in the codepen, but I am using the following:
"pixi.js": "^7.2.4", "pixi-viewport": "^5.0.1", "@pixi/react": "^7.1.0", "react": "18.2.0", "react-dom": "18.2.0", "next": "13.4.3", "typescript": "5.0.4",
Possible Solution
- Fix the example code (as described above)
Additional Information
Here is how far I got with updating the example. This runs. However, if you comment the PixiViewport
component back in, it stops working.
// ========================================================
// Imports
// ========================================================
import { Stage, Container, Sprite, PixiComponent, useApp, useTick } from '@pixi/react';
import { Viewport } from 'pixi-viewport'
import { useState, useEffect, useCallback, useRef, forwardRef } from 'react';
// ========================================================
// Misc
// ========================================================
const stageOptions = {
antialias: true,
autoDensity: true,
backgroundColor: 0x222222,
};
type AreasArrIndex = 'world' | 'center' | 'tl' | 'tr' | 'bl' | 'br';
const areas: {
[K in AreasArrIndex]: Array<number>;
} = {
'world': [1000, 1000, 2000, 2000],
'center': [1000, 1000, 400, 400],
'tl': [100, 100, 200, 200],
'tr': [1900, 100, 200, 200],
'bl': [100, 1900, 200, 200],
'br': [1900, 1900, 200, 200]
}
const useIteration = (incr = 0.1) => {
const [i, setI] = useState(0);
useTick((delta) => {
setI(i => i + incr * delta);
});
return i;
};
const useResize = () => {
const [size, setSize] = useState([global?.window.innerWidth, global?.window.innerHeight]);
useEffect(() => {
const onResize = () => {
requestAnimationFrame(() => {
setSize([global?.window.innerWidth, global?.window.innerHeight])
})
};
global?.window.addEventListener('resize', onResize);
return () => {
global?.window.removeEventListener('resize', onResize);
}
}, []);
return size;
};
// create and instantiate the viewport component
// we share the ticker and interaction from app
// FIXME: This is the culprit!
const PixiViewportComponent = PixiComponent("Viewport", {
create(props) {
const { app, ...viewportProps } = props;
const viewport = new Viewport({
ticker: props.app.ticker,
// interaction: props.app.renderer.plugins.interaction, // deprecated
events: props.app.renderer.events,
...viewportProps
});
//activate plugins
// viewport.drag();
// viewport.pinch();
// viewport.wheel();
// viewport.decelerate();
// (props.plugins || []).forEach((plugin: string) => {
// viewport[plugin]();
// });
return viewport;
},
// applyProps(viewport, _oldProps, _newProps) {
// const { plugins: oldPlugins, children: oldChildren, ...oldProps } = _oldProps;
// const { plugins: newPlugins, children: newChildren, ...newProps } = _newProps;
// // Object.keys(newProps).forEach((p) => {
// // if (oldProps[p] !== newProps[p]) {
// // viewport[p] = newProps[p];
// // }
// // });
// },
didMount() {
console.log("viewport mounted");
}
});
// create a component that can be consumed
// that automatically pass down the app
const PixiViewport = forwardRef(function MyPixiViewport(props: any, ref: any) {
return (
<PixiViewportComponent ref={ref} app={useApp()} {...props} />
)
});
// Wiggling bunny
const Bunny = forwardRef(function MyBunny(props: any, ref: any) {
// abstracted away, see settings>js
const i = useIteration(0.1);
return (
<Sprite
ref={ref}
image="https://s3-us-west-2.amazonaws.com/s.cdpn.io/693612/IaUrttj.png"
anchor={0.5}
scale={2}
rotation={Math.cos(i) * 0.98}
{...props}
/>
);
});
// Bunny moving in a circle
const BunnyFollowingCircle = forwardRef(function MyBunnyFollowingCircle({ x, y, rad }: any, ref: any) {
const i = useIteration(0.02);
return <Bunny ref={ref} x={x + Math.cos(i) * rad} y={y + Math.sin(i) * rad} scale={6} />
});
// 4 squared bunnies
// positioned by its name
const BunniesContainer = ({
pos,
...props
}: {
pos: AreasArrIndex,
[x: string]: any;
}) => {
const [x, y] = areas[pos];
return (
<Container x={x} y={y} {...props}>
<Bunny x={-50} y={-50} />
<Bunny x={50} y={-50} />
<Bunny x={-50} y={50} />
<Bunny x={50} y={50} />
</Container>
);
}
// ========================================================
// Pixi app
// ========================================================
// the main app
export const PixiApp = () => {
// get the actual viewport instance
const viewportRef = useRef();
// get ref of the bunny to follow
const followBunny = useRef();
// get the current window size
const [width, height] = useResize();
// interact with viewport directly
// move and zoom to specified area
const focus = useCallback((p: AreasArrIndex) => {
const viewport = viewportRef.current;
const [x, y, focusWidth, focusHeight] = areas[p];
const f = Math.max(focusWidth / width, focusHeight / height);
const w = width * f;
const h = height * f;
// pause following
//viewport.plugins.pause('follow');
// and snap to selected
// viewport.snapZoom({ width: w, height: h, removeOnComplete: true, ease: 'easeInOutExpo' });
// viewport.snap(x, y, { removeOnComplete: true, ease: 'easeInOutExpo' });
}, [width, height]);
const follow = useCallback(() => {
const viewport = viewportRef.current;
const focusWidth = 1000;
const focusHeight = 1000;
const f = Math.max(focusWidth / width, focusHeight / height);
const w = width * f;
const h = height * f;
// viewport.snapZoom({ width: w, height: h, ease: 'easeInOutExpo' });
// viewport.follow(followBunny.current, { speed: 20 });
}, [width, height]);
return (
<div className="mapContainer">
<div className="buttonsGroup">
<button onClick={() => focus('world')}>Fit</button>
<button onClick={() => focus('center')}>Center</button>
<button onClick={() => focus('tl')}>TL</button>
<button onClick={() => focus('tr')}>TR</button>
<button onClick={() => focus('bl')}>BL</button>
<button onClick={() => focus('br')}>BR</button>
<button onClick={() => follow()}>Follow</button>
</div>
<Stage width={width} height={height} options={stageOptions}>
{/* <PixiViewport
ref={viewportRef}
// plugins={["drag", "pinch", "wheel", "decelerate"]}
screenWidth={width}
screenHeight={height}
worldWidth={width * 4}
worldHeight={height * 4}
> */}
<BunniesContainer pos="tl" />
<BunniesContainer pos="tr" />
<BunniesContainer pos="bl" />
<BunniesContainer pos="br" />
<BunniesContainer pos="center" scale={2} />
<BunnyFollowingCircle x={1000} y={1000} rad={500} ref={followBunny} />
{/* </PixiViewport> */}
</Stage >
</div>
);
}
PS: I have tried to set up an example of the above on StackBlitz, but could not get that to work either due to a different error: Error in /turbo_modules/@pixi/[email protected]/lib/Color.js (6:19) Unexpected token 'export'
Update: This thread, https://github.com/davidfig/pixi-viewport/issues/438, provides a partial solution. However, it leads to an error when leaving the page.