Next.js + Framer Motion scroll progress animation - reactjs

So I want to create a viewport scroll progress circle with Framer Motion in a Next.js app (using tailwind as CSS).
I used the example code from Framer Motion:
https://codesandbox.io/s/framer-motion-viewport-scroll-and-svg-path-animation-mwi35?from-embed=&file=/src/Example.tsx:686-1070
The code I have down here displays the circle, but when I enable strokeDasharray or pathLength (in the style attribute) it disappears. And on scroll I don't see anything happening.
I tried different svg versions, but I didn't get it working. I also looked at my json.config and compare it with the example, but since it uses TypeScript I could not figure it out.
import * as React from 'react';
import { useEffect, useState } from 'react';
import {
motion,
useViewportScroll,
useSpring,
useTransform,
} from 'framer-motion';
const CircleIndicator = () => {
const { scrollYProgress } = useViewportScroll();
const yRange = useTransform(scrollYProgress, [0, 0.9], [0, 1]);
const pathLength = useSpring(yRange, { stiffness: 400, damping: 90 });
useEffect(() => yRange.onChange((v) => setIsComplete(v >= 1)), [yRange]);
return (
<>
<svg className='fixed w-12 h-12 bottom-7 left-7' viewBox='0 0 60 60'>
<motion.path
fill='none'
strokeWidth='2'
stroke='white'
// strokeDasharray='0 1'
d='M 0, 20 a 20, 20 0 1,0 40,0 a 20, 20 0 1,0 -40,0'
style={
{
// pathLength,
// rotate: 90,
// translateX: 5,
// translateY: 5,
// scaleX: -1, // Reverse direction of line animation
}
}
/>
</svg>
</>
);
};
export default CircleIndicator;
It would be great if I could get the animation to work.

If your css codes have below
html,
body: {
width: 100%
}
delete it and try again.
I has the same problem and solved it this way.

Related

Best way to make a custom smooth scroll and scrollto coexist (ReactJs - Framer motion)

Made a smoothscroll component using framer motion that's working well :
export default function SmoothScroll({ children }: Props) {
const { width } = useWindowSize();
const scrollContainer = useRef() as RefObject<HTMLDivElement>;
const [pageHeight, setPageHeight] = useState(0);
useEffect(() => {
setTimeout(() => {
// added a setTimeout so the page has the time to load and it still fits
const scrollContainerSize =
scrollContainer.current?.getBoundingClientRect();
scrollContainerSize && setPageHeight(scrollContainerSize.height);
}, 500);
}, [width]);
const { scrollY } = useScroll(); // measures how many pixels user has scrolled vertically
// as scrollY changes between 0px and the scrollable height, create a negative scroll value...
// ... based on current scroll position to translateY
const transform = useTransform(scrollY, [0, pageHeight], [0, -pageHeight]);
const physics = { damping: 15, mass: 0.17, stiffness: 55 }; // easing of smooth scroll
const spring = useSpring(transform, physics); // apply easing to the negative scroll value
return (
<>
<motion.div
ref={scrollContainer}
style={{ y: spring }} // translateY of scroll container using negative scroll value
className="app fixed overflow-hidden w-screen"
>
{children}
</motion.div>
<motion.div style={{ height: pageHeight }} />
</>
);
}
The thing is, I'd like to scrollTo sections of my page upon click on the navbar but don't really know how to implement it without removing the smoothScroll ...
Tried the following logic but obviously it did not work as the vanilla scroll has been hijacked :
const scrollToSection = (
e: React.MouseEvent<HTMLLIElement, globalThis.MouseEvent>,
anchor?: string
) => {
e.preventDefault();
if (!anchor) return;
const section = document.querySelector(anchor);
section?.scrollIntoView({ behavior: "smooth" });
};
Is it doable ?

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>

Zoom and pan on a SVG map

I have a SVG map as one of my react components, and I am trying to make it so you can zoom in on the map, and pan across so certain sections are easier to see. I have managed to implement being able to zoom in on the map, however the map just enlarges on the screen rather than zooming within the view box I have, and the zoom is quite sensitive and the map will often disappear if it zooms out too far. Does anyone have experience who can help with this. The code for the zoom came from someone who was using it to zoom in on images. `
Here is the code minus all the path data for the SVG as it was too many characters
import React, {useState, useRef, useEffect} from 'react'
export default function Map () {
const [pos, setPos] = useState({ x: 0, y: 0, scale: 1 });
const onScroll = (e) => {
const delta = e.deltaY * -0.01;
const newScale = pos.scale + delta;
const ratio = 1 - newScale / pos.scale;
setPos({
scale: newScale,
x: pos.x + (e.clientX - pos.x) * ratio,
y: pos.y + (e.clientY - pos.y) * ratio
});
};
return (
<>
<div className='viewBox'>
<div onWheelCapture={onScroll}>
<svg baseProfile="tiny" fill="#7c7c7c" height="1982" stroke="#ffffff" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" version="1.2" viewBox="0 0 1000 1982" width="1000" xmlns="http://www.w3.org/2000/svg" style={{transformOrigin: '0 0', transform: `translate(${pos.x}px, ${pos.y}px) scale(${pos.scale})`}}>

Simple testing example of canvas element using Jest

I am trying to create a jest test for the example found in this sandbox:
https://codesandbox.io/s/r3f-basic-demo-forked-297k3?file=/src/App.js
Basically I would like to test the onClick functionality on each box. I have already come across with the fact that jest tests don't run in a real browser. Jest uses jsdom for mocking the necessary parts of the DOM. Consequently I may need canvas support for jsdom, yet I am not quite sure exactly what to do.
App.js
import React, { useRef, useState } from 'react'
import { Canvas, useFrame } from 'react-three-fiber'
function Box(props) {
// This reference will give us direct access to the mesh
const mesh = useRef()
// Set up state for the hovered and active state
const [hovered, setHover] = useState(false)
const [active, setActive] = useState(false)
// Rotate mesh every frame, this is outside of React without overhead
useFrame(() => {
mesh.current.rotation.x = mesh.current.rotation.y += 0.01
})
return (
<mesh
{...props}
ref={mesh}
scale={active ? [1.5, 1.5, 1.5] : [1, 1, 1]}
onClick={(e) => setActive(!active)}
onPointerOver={(e) => setHover(true)}
onPointerOut={(e) => setHover(false)}>
<boxBufferGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}
export const App = () => {
return (
<>
<Canvas>
<ambientLight intensity={0.5} />
<spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
<pointLight position={[-10, -10, -10]} />
<Box position={[-1.2, 0, 0]} />
<Box position={[1.2, 0, 0]} />
</Canvas>
</>
)
}
It's not much of an answer but I found this article useful: https://www.valentinog.com/blog/canvas/.
The suggestion is for proper testing of a canvas element one should not be using Jest (or any unit test) but instead, use some sort of e2e testing so that it can actually render the canvas.
I was able to get some smaller tests working in jest with the following:
Firstly render your page normally using jest. You can either give your canvas a test-id or (as in my case) put a div around your canvas and give the div a test-id.
I ended up with the following:
let canvasContainer = getByTestId("canvas-container");
let canvas = canvasContainer.getElementsByTagName("canvas")[0];
let canvasContext = canvas.getContext("2d");
In either case, your aim is to get the canvas' elements context.
From there you can inspect it with canvasContext.__getDrawCalls()
This will give you some information on all of the "drawCalls" that have been done so far (such as drawing lines or rectangles or what have you).
It takes a little of a bit of inspection into the __getDrawCalls but you should find that every call so far is included.
I wrote some funcitons to help me get the exact details I wanted:
const getLineInfo = (lineInfo) => {
if (lineInfo.type !== "stroke") {
return "not a line";
}
let lineInfoDetail = lineInfo.props.path[3];
return {
lineStart: {
x: lineInfoDetail.transform[4],
y: lineInfoDetail.transform[5],
},
lineEnd: {
x: lineInfoDetail.transform[4] + lineInfoDetail.props.x,
y: lineInfoDetail.transform[5] + lineInfoDetail.props.y,
},
};
};
const getRectInfo = (rectInfo) => {
if (rectInfo.type !== "fill") {
return "not a rect";
}
let rectInfoDetail = rectInfo.props.path[1];
return {
rectStart: {
x: rectInfoDetail.transform[4],
y: rectInfoDetail.transform[5],
},
rectEnd: {
x: rectInfoDetail.transform[4] + rectInfoDetail.props.width,
y: rectInfoDetail.transform[5] + rectInfoDetail.props.height,
},
};
};
So for example if I know that the second draw is a line I can do the following:
expect(getLineInfo(canvasContext.__getDrawCalls()[1])).toStrictEqual({
lineStart: { x: 150, y: 250 },
lineEnd: { x: 150, y: 350 },
});
This is testing that that element is a line with a start point of { x: 150, y: 250 } and an endpoint of { x: 150, y: 350 }
The issue with this though is that you can't trigger click and drag events of the canvas elements within jest and therefore using jest to test canvas seems to be very limited.
Edit:
It looks like https://yonatankra.com/how-to-test-html5-canvas-with-jest/ gives another explanation.

React/Framer Motion {variants} are not working?

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👍

Resources