React/Framer Motion {variants} are not working? - reactjs

So I started playing with Framer Motion in React and it's really nice.
I am able to create motions with initial/animate/exit, but for some reason I can't get variants to work?
So I've got this example:
<motion.span initial={{y:300}} animate={{y:0}} exit={{y: 300}} transition={transition} >A</motion.span>
and if I refresh the page (AnimatePresence initial is true, not false), it works, I get a letter that moves from y:300 to y:0 with a defined transition.
but then if I want to apply that to multiple letter, so transform it into variants like this:
import React, { useEffect, useState } from "react";
import { motion, useViewportScroll, useTransform } from "framer-motion";
import { Link } from "react-router-dom";
import Scrollbar from 'react-smooth-scrollbar';
import scrolldown from '../images/scrolldown.svg';
import work1 from '../images/p1.png';
import work2 from '../images/p3.png';
import work3 from '../images/p2.png';
const transition = { duration: 1.7, ease: [0.43, 0.13, 0.23, 0.96] };
const letter = {
initial: {
y: 300,
},
animate: {
y: 0,
transition: { duration: 1, ...transition },
},
};
const Home = () => (
and then use it like this:
<motion.span variants={letter}>A</motion.span>
it just won't work? am I missing something here?
I don't have any other motion defined/parent or anything. and it works when using inline code, but not with variants?
letter isn't moving on refresh/page change when using variants?
Thanks for taking the time guys!

so it seems that I needed to have a parent with these set:
<motion.div className="site-hero"
initial='initial'
animate='animate'
exit='exit'>
>
my bad for not reading the documentation properly.
maybe this helps anyone

You have to use like this
<motion.span variants={letter} initial="initial" animate="animate">
A
</motion.span>

The problem is that a span is an inline element so you can move it up or down. to fix it just added a style of display:inline-block. Now the span behaves as a block but yet still inline👍

Related

Why y and x axis parameter didn't work for staggerChildren animation in framer motion?

My question is fairly simple but i'm struggling to find the solution. Here i'm trying to create a staggering animation for each letter in a sentence, i want to use the y axis as a parameter to animate but i'm not getting the result i wanted as the sentence fully mounted without animating. But when i tried to use opacity as a parameter, it works exactly fine. What did i do wrong here? Any help would be appreciated :)
// import "./styles.css";
import { motion } from "framer-motion";
export default function App() {
const quotes = "Hello World.";
const parent = {
animate: {
transition: {
staggerChildren: 0.1,
},
},
};
const child = {
initial: { y: 400 },
animate: {
y: 0,
},
};
return (
<div>
<motion.div variants={parent} initial='initial' animate='animate'>
{
quotes.split('').map((item, index) => (
<motion.span variants={child} key={index}>{item}</motion.span>
))
}
</motion.div>
</div>
);
}
To help you see my problem, here is codesandbox example
https://codesandbox.io/s/busy-mountain-fv58xc?file=/src/App.js:0-620
Animating x and y doesn't work for <span> because it's and inline element. It flows with the content and doesn't have an explicit x and y position to animate.
You can change your spans to a block-level element (like div), or you could add some styling to tell the spans to display as blocks:
<motion.span style={{display: "inline-block"}} variants={child} key={index}>
{item}
</motion.span>

Best way to scroll article out of view and hide it

I'm building an article view where you can navigate to the next article by clicking a link in the bottom of the current one. This will trigger the new article being loaded under the current one, the current one will be faded out and then I animate the height of the current article to zero, so you end up with a page transition where the new article is now at the top of the screen.
My problem now is that it will try to maintain my scroll position, so depending on how far I've scrolled, I will start from a different position in the new article. I would like to start from the top. Do I need to use javascript to animate a scroll to the top at the same time or is there an easier way to achieve this?
So far I've added this CSS:
section {
transition: opacity 1s, max-height 1s;
opacity: 1;
max-height: 10000px;
}
.hidden {
opacity: 0;
max-height: 0;
}
Here is a Code Sandbox where you can see the problem in practice.
Adding the .hidden class will trigger content fading out and collapsing. The max-height property is not hardware accelerated, though and the procedure about setting an initial max-height that is always higher than page contents (which I might not know) seems awkward and hacky, though.
Is there a more obvious way to achieve this and always end up being scrolled to the top of the 2nd section when the first one is hidden?
You can use conditional rendering (your animation is lost but u can fix that with framer-motion):
import React, { useState } from "react";
import { motion, AnimatePresence, useAnimation } from "framer-motion";
import "./styles.css";
export default function App() {
const [hideSection, setHideSection] = useState(false);
const controls = useAnimation();
const handleClick = () => {
if (hideSection) {
controls.start({
y: [-1400, 0]
});
} else {
controls.start({
y: [1400, 0]
});
}
setHideSection(!hideSection);
};
return (
<motion.div animate={controls}>
<AnimatePresence>
{!hideSection && (
<motion.section
key="First section"
exit={{ opacity: -1 }}
transition={{ default: { duration: 1, delay: 0 } }}
>
First section
</motion.section>
)}
</AnimatePresence>
<motion.section>
Second section{" "}
<button onClick={handleClick}>Toggle first section</button>
</motion.section>
</motion.div>
);
}

How do I turn off Framer Motion animations in mobile view?

I am trying to create a React website using Framer Motion, the problem is that my animation looks good in desktop view, but not really in mobile. Now I want to disable the animation. How do I do this?
Without knowing further details, I would recommend using Variants for this.
Inside your functional component, create a variable that checks for mobile devices. Then, only fill the variants if this variable is false.
Note that it doesn't work when you resize the page. The component must get rerendered.
I've created a codesandbox for you to try it out!
For further information on Variants, check this course
Another simple way I just did myself when this exact question came up. Below we are using a ternary operatory to generate a object which we then spread using the spread syntax
const attributes = isMobile ? {
drag: "x",
dragConstraints: { left: 0, right: 0 },
animate: { x: myVariable },
onDragEnd: myFunction
} : { onMouseOver, onMouseLeave };
<motion.div {...attributes}> {/* stuff */} </motion.div>
As you can see I want onMouseEnter & onMouseLeave on desktop with no animations. On mobile I want the opposite. This is working of me perfectly.
Hope this helps too.
Daniel
This is how we've done it:
import {
type ForwardRefComponent,
type HTMLMotionProps,
motion as Motion,
} from 'framer-motion';
import { forwardRef } from 'react';
const ReducedMotionDiv: ForwardRefComponent<
HTMLDivElement,
HTMLMotionProps<'div'>
> = forwardRef((props, ref) => {
const newProps = {
...props,
animate: undefined,
initial: undefined,
transition: undefined,
variants: undefined,
whileDrag: undefined,
whileFocus: undefined,
whileHover: undefined,
whileInView: undefined,
whileTap: undefined,
};
return <Motion.div {...newProps} ref={ref} />;
});
export const motion = new Proxy(Motion, {
get: (target, key) => {
if (key === 'div') {
return ReducedMotionDiv;
}
// #ts-expect-error - This is a proxy, so we can't be sure what the key is.
return target[key];
},
});
export {
AnimatePresence,
type Variants,
type HTMLMotionProps,
type MotionProps,
type TargetAndTransition,
type Transition,
type Spring,
} from 'framer-motion';
Same principle as other answers, just complete example.

Clearing map layers with react-leaflet and hooks

I'm building a custom plugin for react-leaflet to locate the user using leaflet's locate method.
It works basically, but with the one problem of the layer not clearing between turning location off and back on again. Each time the locate button is toggled, locate should start fresh.
Here is a codesandbox of the problem. As you toggle the button, the circle becomes darker as the layers are stacked on top of each other.
Here is the component:
import React, { useEffect, useState, useRef } from 'react'
import L from 'leaflet'
import { useLeaflet } from 'react-leaflet'
import LocationSearchingIcon from '#material-ui/icons/LocationSearching';
import MapButton from './MapButton'
function Locate() {
const { map } = useLeaflet();
const [locate, setLocate] = useState(false);
function toggleLocate() {
setLocate(!locate)
}
console.log(locate)
const layerRef = useRef(L.layerGroup());
useEffect(() => {
if (locate) {
map.removeLayer(layerRef.current)
map.locate({ setView: false, watch: true, enableHighAccuracy: true }) /* This will return map so you can do chaining */
.on('locationfound', function (e) {
L.circleMarker([e.latitude, e.longitude], {
radius: 10,
weight: 3,
color: 'white',
fillColor: 'blue',
fillOpacity: 1
}).addTo(layerRef.current);
L.circle([e.latitude, e.longitude], e.accuracy / 2, {
weight: 1,
color: 'blue',
fillColor: 'blue',
fillOpacity: 0.2
}).addTo(layerRef.current);
window.localStorage.setItem('accuracy', e.accuracy)
map.setView([e.latitude, e.longitude], 16)
layerRef.current.addTo(map)
})
.on('locationerror', function (e) {
alert("Location error or access denied.");
})
} if (!locate) {
map.stopLocate();
map.removeLayer(layerRef.current);
}
}, [locate, map]);
return (
<MapButton
title={locate ? 'Click to disable location' : 'Click to locate'}
onClick={toggleLocate}
left
top={102}
>
<LocationSearchingIcon fontSize="small" style={{ color: locate ? 'orange' : '' }} />
</MapButton>
)
}
export default Locate
I would appreciate any solution or tips to stop the layers stacking, and clear properly went the button is toggled. Thanks
As Falke Design's mentions in a comment above:
Add the circle to a featureGroup and then every time locationfound is fired, you call featuregroup.clearLayers() and then add the circle new to the featureGroup
This solved the problem.
The working codesandbox is here

React Materialize Carousel Autoplay

Given the code below, I would like to make the carousel autoplay within the said interval. However, being new in React, I have no idea on how to do it. I have tried searching for answers and have tried using carousel('next'), it didn't work. I am still finding answers and is hoping to clear this problem. Thanks!
import React from 'react';
import { Carousel } from 'react-materialize';
const Header = () => {
const imgArr = [
'https://picsum.photos/200/300?image=0',
'https://picsum.photos/200/300?image=1',
'https://picsum.photos/200/300?image=2',
];
return (
<Carousel
className='tabs'
options={{ duration: 100, indicators: true }}
images={imgArr}
/>
);
);
You could use this plugin instead: http://www.linxtion.com/demo/react-image-gallery/
You can use the prop slideInterval to decide when the next slide will occur.

Resources