Framer Motion text gets distorted when animating with shared layouts - framer-motion

I am in a Next.js enviornment and have wrapped my _app.js with .
Inside a page I have a basic routing set up to jump from page 1 to page 2.
On each page I have a motion h1 which looks like. So there are two components with matching ID's.
const stats = {
visible: {
opacity: 1,
},
hidden: {
opacity: 1,
},
exit: {
opacity: 0,
y: 50,
},
}
<motion.h1
initial="hidden"
animate="visible"
variants={stats}
layout
className="text-3xl text-gray-800 font-bold"
layoutId={`product-title-${data.title}`}
>
{data.title}
</motion.h1>
When I navigate pages the elements animate from their counter parts previous position.. but the text gets all distorted when animating.
How do I fix the distorted text?

You can try giving the value of "position" to your layout prop, instead of true.
layout="position"
As referred in the framer motion documentation
If layout is set to "position", the size of the component will change instantly and only its position will animate.
Since you are animating only position and opacity, it could solve your issue.

Related

Nextjs x Framer Motion inertial scrolling with page transition

I've been working on an inertial scrolling implementation within Nextjs using Framer Motion but running into a bit of trouble when animation page transitions using animate presence. I'm hooking into the page scroll progress via useScroll and animating the Y transform with spring physics. The issue, however, is when resetting the window scroll position when navigating between pages. After the component has unmounted, I call window.scrollTo(0,0) via the AnimatePresence onExitComplete callback, but because I'm transforming the page position via spring physics, the scroll reset doesn't complete immediately. So when new pages are mounted, the page transform to reset the scroll is still running.
Additionally, I've included scroll={false} in my Next Link components to prevent the default scroll to top functionality so I can handle this manually via the AnimatePresence component as mentioned above. But this doesn't seem to be working.
There's a bit of code involved so I created a minimal reproduction repo here.
I also notice that passing scrollY to the y property of the variant object prevents the unwanted scrolling behavior on page transition. But passing something like scrollY - 128 breaks the behavior:
const variants = {
hidden: { opacity: 0, x: 0, y: 64 },
enter: { opacity: 1, x: 0, y: 0 },
exit: { opacity: 0, x: 0, y: scrollY },
};
y value in exit variant wasn't being updated to match latest scrollY value. Solution:
useEffect(() => {
return scrollY.onChange((latest) => {
variants.exit.y = -scrollY.current - 128;
});
});

How to show fixed elements inside a web-animation-api transformed parent

I have a parent div transformed with the web-animations-api.
This makes the containing block, of any child fixed elements the parent rather than the viewport (as expected).
The parent transforms to translate3d(0, 0, 0) ... so just removing the animation/transform on complete would be perfect.
I cant find a simple* way to do this through the web-animations-api, is there one?
Previously this was done via CSS, or inlined styles & hence easy to remove on complete.
I have tried ..
amination.cancel(), inside animation.finished, but rapidly applying another animation to the
same el, breaks ... looks like fill mode flips from 'forward' to 'none'
adding an additional transform on animation.finished. This seems to work but messy.
el.animate([
{ transform: 'unset' },
{ transform: 'unset' },],
{
duration: 0,
fill: 'forwards'
}
)

Drawer rendering inside AppBar element

I created a container to limit my app to 600px(sm) width, but when I did it the drawer breaks. Instead of rendering in the container page side, it is inside the appBar.
When the width is more than 600px (the page limit), the drawer is rendering inside the appBar (picture incorrect). When the page is smaller than 600px it renders correct (picture correct).
I am using material-ui components, the app bar is position as absolute, because I am implementing on scroll events. To centralize the app bar I use:
maxWidth: 600,
[theme.breakpoints.up("sm")]: {
left: "50%",
transform: "translate(-50%, 0)"
}
Here is codesandbox example code:
https://codesandbox.io/s/material-drawer-e6vrc?file=/header.js
You should make drawer sibling with AppBar instead of it's children
return (
<div className={classes.grow}>
<AppBar className={classes.app_bar}>
...
</AppBar>
{renderDrawer}
</div>
);

How can I rotate a css cursor

Here is what you need to do in order to have a clear view of what I want
Go on this editor
create a shape
select it
rotate it
place your mouse on one of the resize control point then click
you'll see the cursor rotated with the angle of the shape.
I can't find any CSS properties to achieve this kind of thing, how could this be done?
You can't and it doesn't. It just displays the different resize icons. See for example: http://css-cursor.techstream.org/
I needed a rotating arrow cursor for a carousel. Here's what I came up with:
https://codepen.io/addison912/pen/MWwmpoz
Start by hiding the default cursor in css:
body * {
cursor: none;
}
add the image of the cursor you'd like to use to html:
<div id="cursor">
<img alt="Cursor Arrow" src="https://upload.wikimedia.org/wikipedia/commons/9/9d/Arrow-down.svg"/>
</div>
This image needs to track cursor position:
let currentCursorPos;
let cursorEl = document.querySelector("#cursor");
window.addEventListener("mousemove", event => {
currentCursorPos = { x: event.clientX, y: event.clientY };
cursorEl.style.transform = `translate(${currentCursorPos.x}px, ${currentCursorPos.y}px)`;
})
Now you can rotate the #cursor img as needed.
Yes, in modern browsers (Chrome, Firefox, Safari) you can use an SVG encoded as a data URI as the CSS cursor:
cursor: url("data:image/svg+xml, ... ") 16 16, auto;
Encoding the SVG can be tricky, but is well documented here:
https://codepen.io/tigt/post/optimizing-svgs-in-data-uris
The trick is then modifying the encoded SVG's transform property on the fly:
<svg viewBox="32 32"><g transform="rotate(45, 16, 16)">...</g></svg>
For instance, in the above example you'd swap out 45 for your desired angle.

Use Web Animations API to expand div height 0 --> 'auto'

I'm trying to get my head around the web animations standard and their polyfill, as I saw it work nicely in the Angular animations library (you set an animation end value to '*' and this becomes 100% of the div size, but that uses a special Angular Animations DSL).
I thought I would start with something simple, so all I want to do is expand a div from 0 height to 'auto'. i know there are thousands of other ways to do this, but I was trying to use web-animations-js with this code
The code below (which resembles an MDN example) causes the div to expand directly to 'auto' but after a 1 second delay, whereas I want it smoothly to expand.
let formDiv = document.querySelector("#new-present-form");
formDiv.animate([
{ height: 0},
{ height: 'auto'}
], {
easing: 'ease-in-out',
duration: 1000,
fill: 'forwards' // ensures menu stays open at end of animation
})
By contrast this
formDiv.animate({
height: [0, '100%'],
easing: 'ease-in-out'
}, {
duration: 1000,
fill: 'forwards' // ensures menu stays open at end of animation
})
causes the div to expand immediately, but again with no smooth transition.
This does give a smooth transition, but requires a carefully chosen value to replace '300px' and is precisely what I want to avoid.
formDiv.animate([
{ "height": '0px', offset: 0},
{ "height": '300px', offset: 1}
], {
duration: 1000,
iterations: 1,
fill: 'forwards' // ensure form stays open at end of animation
})
Unfortunately, it is not directly possible to do this with Web Animations or CSS animations/transitions yet. That is because CSS does not have a means to represent an intermediate state between an 'auto' length and a fixed length. The proposal to fix this involves allowing 'auto' inside calc(), e.g. calc(auto + 36px). You can follow the progress of this development on the CSS Transitions Github issue.
In the interim, many people have been able to work around this by animating max-height instead. For example, if you expect your 'auto' height to be somewhere between 300px and 600px, then you could leave height as 'auto', and animate max-height from '0' to '700px'. It's not perfect, but for a short animation it's often close enough.
Alternatively, one could set the height to auto, get the used value of the height using getComputedStyle, and, supposing it returned 375px, create an animation from, height: '0' to height: '375px'. If you do not specify a fill on the animation then when it completes, the computed value of the height will switch from height: 375px to height: auto (which should make no visual difference but mean that the element's height responds to future changes in the content size).
Regarding the error about partial keyframes, that is a short-term issue where both Firefox and Chrome have not shipped support for omitting the first or last keyframe. Both Firefox and Chrome have implemented this feature and it should ship this year, however, it still won't fix this issue until auto is permitted in calc().
Update 22 May with (completely untested) code samples as requested:
// Option 1: Use max-height
formDiv.animate(
{ maxHeight: ['0', '700px'] },
{ easing: 'ease-in-out', duration: 1000 }
);
// Option 2: Use used height
//
// (Beware, flushes layout initially so will probably not be very performant.)
formDiv.style.height = 'auto';
const finalHeight = getComputedStyle(formDiv).height;
formDiv.style.height = '0';
formDiv.animate(
{ height: ['0', finalHeight] },
{ easing: 'ease-in-out', duration: 1000 }
);
Of course if you don't actually have anything below the div you might be able to
get away with a transform animation which will definitely be the most
performant.
// Option 3: Use scale-y transform
formDiv.style.transformOrigin = '50% 0%';
formDiv.animate(
{ transform: ['scaleY(0)', 'scaleY(1)'] },
{ easing: 'ease-in-out', duration: 1000 }
);
If height is not mandatory you can try giving padding to the div.
#keyframes example {
from{padding:0px;}
to{padding: 50px;}
}
Vote up if it was help full.

Resources