I would like to control when an animation (Framer Motion) can run using window.innerWidth, but in Next.js I get the following error message:
ReferenceError: window is not defined
This is a simplified version of my component named ValuesSection.jsx:
import React, { useEffect } from "react";
import { useInView } from "react-intersection-observer";
import { motion, useAnimation } from "framer-motion";
export default function ValuesSection() {
const controls = useAnimation();
const [ref, inView] = useInView();
const MobileView = {};
const isMobile = window.innerWidth < 768;
if (!isMobile) {
MobileView = {
visible: { y: 0, scale: 1 },
hidden: { y: 250, scale: 0 },
};
}
useEffect(() => {
if (inView) {
controls.start("visible");
}
}, [controls, inView]);
return (
<>
<motion.div
key={value.name}
ref={ref}
animate={controls}
initial="hidden"
variants={MobileView}
>Some content</motion.div>
</>
Can you help me understand what I'm doing wrong? And if you are able to provide me with a working example, it would be great and appreciated.
Best way of using screen size in next.js projects is that you can instead use a Hook from materiial ui that represents true or false when screen width is bigger or smaller than defined value and in my opinion it's better than the window because it has a lot of options that you can use here is what you should do step by step
first install material ui if you didn't already
// with npm
npm install #material-ui/core
// with yarn
yarn add #material-ui/core
then import and define it inside of your component
import { useMediaQuery } from "#material-ui/core";
export default function ValuesSection() {
const IsTabletOrPhone = useMediaQuery("(max-width:1024px)");
now if you screen size is bigger than 1024px it returns false, otherwise it returns true
so you can use it
if (!IsTabletOrPhone) {
MobileView = {
visible: { y: 0, scale: 1 },
hidden: { y: 250, scale: 0 },
};
}
Update:
Maybe it's because i assigned it with a uppercase letter, you can try changing the name to isTabletOrPhone with lowercase, if that didn't work try to change it to let instead
let isTabletOrPhone = useMediaQuery("(max-width:1024px)");
In Next.js pages are prerendered on the server. And on the server there's no window. You could just check if the window exists before to access it
const isMobile = global.window && window.innerWidth < 768;
Note that the user may rotate the phone or resize the window, so you should add (and clean up) a resize event handler into an useEffect
Related
I am developing an app where on mouse wheel up and down navigates the user to different routes in the App. I also have animations corresponding to which way the user scrolls: when scrolling down the next route will intialize from below and the previous route will exit from below, and vice-versa.
this part has been simple enough to implement as per the code below(link to full live working example at the bottom of this post):
Custom Hook UseAppDirection.js
import { useState, useEffect, useContext } from "react";
import { useLocation, useNavigate } from "react-router-dom"
function useAppDirection(navDown, navUp, directionValueDown, directionValueUp) {
const navigate = useNavigate();
const location = useLocation();
const { pathname } = location;
const [isUp, setIsUp] = useState(false);
useEffect(() => {
function handleNavigation(e) {
if (e.deltaY > 1) {
setIsUp(false)
setTimeout(() => {
navigate(navDown, { state: { value: directionValueDown } });
}, 200)
} else if (e.deltaY < 1) {
setIsUp(true)
setTimeout(() => {
navigate(navUp, { state: { value: directionValueUp } });
}, 200)
}
}
window.addEventListener("wheel", handleNavigation);
return () => window.removeEventListener("wheel", handleNavigation);
}, [pathname]);
return { location, isUp }
}
export default useAppDirection
AboutMe.js
import React from "react";
import { motion } from "framer-motion";
import useAppDirection from "../Hooks/useAppDirection";
export default function AboutMe() {
const { isUp, location } = useAppDirection("/work", "/skills", 1000, -1000);
return (
<motion.div
className="aboutme--top"
initial={{ opacity: 0, y: location.state.value, transition: { duration: 0.8 } }}
animate={{ opacity: 1, y: 0, transition: { duration: 0.8 } }}
exit={{ opacity: 0, y: isUp ? 1300 : -1300, transition: { duration: 0.8 } }}
>
</motion.div >
)
}
However one major problem I have ran into is when a user scrolls one direction and in mid transition scrolls the opposite direction; both animations occur simultaneously resulting in a very ugly and janky transition as two components are simultaneously both leaving and exiting.
My question is, how can I modify my code to ensure that only when one transition has completed can the next one start? I am happy to have the listener be disabled for the duration of the transition and only be re-enabled on a set timer (the time taken for the transition to complete ~ 0.8seconds).
Currently the event is able to fire as soon as the transition occurs, and I am unable to find a solution for it.
live working example below:
https://stackblitz.com/edit/react-ts-w42djd?embed=1&file=Hooks/useAppDirection.js
I am using fabricjs and fabricjs-react in react app. I need the text to be edited via input. But it doesn't work. Maybe I'm missing something?
But when I enter text into the input, the canvas is not updated and the text remains the same. What am I doing wrong? How to access existing text in fabricjs?
There is my code https://codesandbox.io/s/sleepy-stitch-yvcr22
UPD: I tried to do https://codesandbox.io/s/clever-christian-zxb5ge with the addition of ID. It works. Maybe there is some more universal solution?
import React, { useEffect, useState } from 'react'
import { FabricJSCanvas, useFabricJSEditor } from 'fabricjs-react'
import { fabric } from 'fabric'
export default function App() {
const { editor, onReady } = useFabricJSEditor()
const [text, setText] = useState('')
let headText = new fabric.Text("test", {
fill: 'black',
top: 50,
left: 50,
})
const _onReady = (canvas) => {
canvas.backgroundColor = 'yellow'
canvas.setDimensions({
width: 200,
height: 200,
})
canvas.add(headText);
canvas.renderAll()
onReady(canvas)
}
useEffect(() => {
if (editor) {
headText.text = text
editor.canvas.renderAll()
}
}, [text])
return (
<>
<input onChange={(e) => setText(e.target.value)} />
<FabricJSCanvas onReady={_onReady} />
</>
)
}
The reason why it doesn't work is because whenever the setText() state is called, the App() function is called again in order to render the state changes.
Here lies the problem with this; any subsequence call to App() you end up creating a new instance of headText
let headText = new fabric.Text("test", {
fill: 'black',
top: 50,
left: 50,
})
Your useEffect is using that new instance whereas the canvas contains the very first instance. So, any changes you attempt is referring to the wrong object.
This quick dirty check proves this is a different object:
useEffect(() => {
if (editor) {
editor.canvas.getObjects()[0].text = text;
editor.canvas.renderAll()
console.log(editor.canvas.getObjects()[0] === headText);
}
}, [text])
The solution is that you need to give headText its own state so that any subsequence calls to App() the object reference is preserved.
const [headText, setTextObject] = useState(
new fabric.Text('test', {
fill: 'black',
top: 50,
left: 50,
}));
I am absolutely clueless regarding an issue i am having with blurry images on iOS.
I am working with react, and i am using #use-gesture library (use-gesture.netlify.app) to enable pinch zoom on some small images and videos. It all works like a charm on desktop, but on my mobile device images are always blurry-bad-quality when pinch-zoomed. Videos are not affected by that quality loss. I donĀ“t think it has anything todo with #use-gesture, since ive been using a lightbox component before that and had the same issue.
I've already read every single thread i could find regarding image scale on iOS but wasnt able to find a solution yet.
What i've gathered/tried so far:
Adding to index.html:
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1">
and adding this to the img tag:
resizeMode="contain"
aspectRatio="1.5"
height= "undefined"
width= "undefined"
Does it maybe have something to do with a difference in image rendering on mobile devices, to me it seems like images dimensions are defined onLoad in the size of my thumbnails and original image size/resolution is ignored? idk..
This is the code of a complete re-build of the issue i am having based on a new react project but i still get the same effect
import test from './imgtest.jpg';
import './App.css';
import React, { useRef, useEffect, useState } from 'react';
import { useSpring, animated } from "react-spring";
import { createUseGesture, dragAction, pinchAction } from '#use-gesture/react'
const useGesture = createUseGesture([dragAction, pinchAction])
function App() {
useEffect(() => {
const handler = e => e.preventDefault()
document.addEventListener('gesturestart', handler)
document.addEventListener('gesturechange', handler)
document.addEventListener('gestureend', handler)
return () => {
document.removeEventListener('gesturestart', handler)
document.removeEventListener('gesturechange', handler)
document.removeEventListener('gestureend', handler)
}
}, [])
const ref = React.useRef(null)
const [style, api] = useSpring(() => ({
x: 0,
y: 0,
scale: 1,
rotateZ: 0,
config: {
},
}))
useGesture(
{
onPinch: ({ origin: [ox, oy], first, active, movement: [ms], offset: [s, a], memo }) => {
if (first) {
const { width, height, x, y } = ref.current.getBoundingClientRect()
const tx = ox - (x + width / 2)
const ty = oy - (y + height / 2)
memo = [style.x.get(), style.y.get(), tx, ty]
}
const x = memo[0] - (ms - 1) * memo[2]
const y = memo[1] - (ms - 1) * memo[3]
api.start({ scale: s , offset: active ? x : 0, offset: active ? y : 0 })
return memo
},
},
{
target: ref,
pinch: { scaleBounds: { min: 1, max: 4 }, rubberband: true },
}
)
return (
<div className="App">
<animated.div ref={ref} style={style}>
<img
resizeMode="contain"
aspectRatio="1.5"
height= "undefined"
width= "undefined"
src={test}
className="App-logo"
/>
</animated.div>
</div>
);
}
export default App;
Please can somebody help me with this?
IOS devices have different DPIs than desktops. You need a bigger image to get a clear image.
also check out this link
For some reason, I just cannot get the most basic example of react-use-gesture to work. What I am trying to do is just have a square follow my mouse location when you drag it. I copy-pasted the example from their documentation multiple times (https://github.com/pmndrs/react-use-gesture) and still cannot get it to work. I just don't understand it anymore. I created a stackblitz to show you my code. What am I still doing wrong?
Stackblitz with code: https://stackblitz.com/edit/react-mg2u8p?file=src/Square.js
I will also include the most relevant code here:
import React from "react";
import { useSpring, animated } from "react-spring";
import { useDrag } from "react-use-gesture";
const Square = () => {
const [{ x, y }, set] = useSpring(() => ({ x: 0, y: 0 }));
const bind = useDrag(({ down, movement: [mx, my] }) => {
set({ x: down ? mx : 0, y: down ? my : 0 });
});
return (
<animated.div
{...bind()}
className="Square"
style={{ x, y, touchAction: "none" }}
/>
);
};
export default Square;
It's a version problem.
Your example uses code for react-spring version 9+, but the version of react-spring you're using in the example is 8.0.27.
The example the documentation gives for version 8 is this:
import { useSpring, animated } from 'react-spring'
import { useDrag } from 'react-use-gesture'
function PullRelease() {
const [{ xy }, set] = useSpring(() => ({ xy: [0, 0] }))
// Set the drag hook and define component movement based on gesture data
const bind = useDrag(({ down, movement }) => {
set({ xy: down ? movement : [0, 0] })
})
// Bind it to a component
return (
<animated.div
{...bind()}
style={{
transform: xy.interpolate((x, y) => `translate3d(${x}px, ${y}px, 0)`),
}}
/>
)
}
So in your case you would only need to change PullRelease to Square and add className="Square" to the animated.div like you had it in your question.
For both the documentation on the v8 and v9 implementation of this using React UseGesture see this.
If you want to use the v9 version, you currently need to install react-spring#next according to the documentation (see previous link).
I've set up some animated routes in React Native 0.59 using react-native-navigation and I've wanted to know what I could do to ensure the animation is the exact same for every route, regardless of the route stack.
I've tinkered with StackActions.reset() but it only negatively impacted the performance of my application, and removed the animations as a whole, so I chose not to use it. I figure there's probably some piece of code that I'm missing or perhaps I need to overhaul my entire routing system.
I've included some code I'm using:
This is my complete NavigationManager.js class:
import { NavigationActions } from "react-navigation";
export default class NavigationManager {
navigator = null; // The navigator itself.
/**
* Function for setting the navigator (generally top reference).
*/
static set(ref) {
navigator = ref; // Set the navigator.
}
/**
* Function for going to the desired screen.
*/
static go(routeName, params) {
navigator.dispatch(NavigationActions.navigate( { routeName, params } ));
}
}
And this my implementation in my SwitchBoard.js class:
const AppStack = createStackNavigator({
Portfolio : PortfolioScreen,
Home : HomeScreen,
Search : SearchScreen,
Settings : SettingsScreen,
}, {
defaultNavigationOptions : {
gesturesEnabled: false,
}, headerMode : "none",
transitionConfig : () => ({
transitionSpec : {
duration : 400,
easing : Theme.ANIMATION_EASING,
timing : Animated.timing,
}, screenInterpolator : sceneProps => {
const { layout, position, scene } = sceneProps;
const { index } = scene; // The index of the stack.
const opacity = position.interpolate({
inputRange : [index - 1, index, index + 1],
outputRange : [0, 1, 1],
});
return { opacity };
},
}),
});
const Container = createAppContainer(
createSwitchNavigator({
AppStack : AppStack,
})
);
My Screen classes consist of basic extended React Native Component classes.
The animation when clicking back to the initial route (in this example, the PortfolioScreen route) traverses through all the routes in the stack and it's visible in the animation.
Here is a .gif image showing the error. It's the last button I press (after the red screen) that resets the stack and animates in a weird way.
Thank you in advance.